diff --git a/.VSCodeCounter/2025-02-17_20-55-51/results.json b/.VSCodeCounter/2025-02-17_20-55-51/results.json
index 3bf140b7..12d67706 100644
--- a/.VSCodeCounter/2025-02-17_20-55-51/results.json
+++ b/.VSCodeCounter/2025-02-17_20-55-51/results.json
@@ -1 +1,1478 @@
-{"file:///c%3A/JoystickGremlin-develop/action_plugins/__init__.py":{"language":"Python","code":0,"comment":15,"blank":2},"file:///c%3A/JoystickGremlin-develop/container_plugins/chain/__init__.py":{"language":"Python","code":197,"comment":30,"blank":59},"file:///c%3A/JoystickGremlin-develop/.env":{"language":"Properties","code":0,"comment":0,"blank":1},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/Attributes.py":{"language":"Python","code":523,"comment":382,"blank":94},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnectManager.py":{"language":"Python","code":1703,"comment":131,"blank":461},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/Constants.py":{"language":"Python","code":41,"comment":10,"blank":16},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/__init__.py":{"language":"Python","code":2804,"comment":290,"blank":1074},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/FacilitiesList.py":{"language":"Python","code":91,"comment":3,"blank":19},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/EventList.py":{"language":"Python","code":1264,"comment":2,"blank":36},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/simconnect_simvars.xml":{"language":"XML","code":4763,"comment":0,"blank":1},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/Enum.py":{"language":"Python","code":480,"comment":76,"blank":162},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_osc/__init__.py":{"language":"Python","code":682,"comment":46,"blank":224},"file:///c%3A/JoystickGremlin-develop/action_plugins/switch_mode/__init__.py":{"language":"Python","code":137,"comment":19,"blank":49},"file:///c%3A/JoystickGremlin-develop/generate_wix.py":{"language":"Python","code":361,"comment":50,"blank":70},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/SimConnectBridge.py":{"language":"Python","code":255,"comment":49,"blank":73},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/__init__.py":{"language":"Python","code":12,"comment":0,"blank":6},"file:///c%3A/JoystickGremlin-develop/action_plugins/toggle_pause/__init__.py":{"language":"Python","code":52,"comment":22,"blank":30},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/SimConnect.py":{"language":"Python","code":1079,"comment":103,"blank":270},"file:///c%3A/JoystickGremlin-develop/action_plugins/temporary_mode_switch/__init__.py":{"language":"Python","code":148,"comment":27,"blank":46},"file:///c%3A/JoystickGremlin-develop/action_plugins/text_to_speech/__init__.py":{"language":"Python","code":198,"comment":25,"blank":73},"file:///c%3A/JoystickGremlin-develop/action_plugins/run_process/__init__.py":{"language":"Python","code":180,"comment":23,"blank":75},"file:///c%3A/JoystickGremlin-develop/action_plugins/split_axis/__init__.py":{"language":"Python","code":185,"comment":24,"blank":45},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/RequestList.py":{"language":"Python","code":981,"comment":25,"blank":31},"file:///c%3A/JoystickGremlin-develop/.qmllint.ini":{"language":"Ini","code":18,"comment":0,"blank":1},"file:///c%3A/JoystickGremlin-develop/action_plugins/system/__init__.py":{"language":"Python","code":0,"comment":210,"blank":80},"file:///c%3A/JoystickGremlin-develop/container_plugins/__init__.py":{"language":"Python","code":3,"comment":15,"blank":3},"file:///c%3A/JoystickGremlin-develop/action_plugins/resume/__init__.py":{"language":"Python","code":52,"comment":22,"blank":30},"file:///c%3A/JoystickGremlin-develop/action_plugins/response_curve/grid.svg":{"language":"XML","code":136,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/action_plugins/response_curve_ex/__init__.py":{"language":"Python","code":121,"comment":22,"blank":55},"file:///c%3A/JoystickGremlin-develop/.github/FUNDING.yml":{"language":"YAML","code":1,"comment":1,"blank":3},"file:///c%3A/JoystickGremlin-develop/container_plugins/tick/__init__.py":{"language":"Python","code":326,"comment":27,"blank":101},"file:///c%3A/JoystickGremlin-develop/action_plugins/response_curve/__init__.py":{"language":"Python","code":1136,"comment":83,"blank":327},"file:///c%3A/JoystickGremlin-develop/action_plugins/remap/__init__.py":{"language":"Python","code":410,"comment":47,"blank":110},"file:///c%3A/JoystickGremlin-develop/container_plugins/tempoEx/__init__.py":{"language":"Python","code":466,"comment":61,"blank":143},"file:///c%3A/JoystickGremlin-develop/container_plugins/tempo/__init__.py":{"language":"Python","code":296,"comment":35,"blank":73},"file:///c%3A/JoystickGremlin-develop/action_plugins/previous_mode/__init__.py":{"language":"Python","code":52,"comment":22,"blank":30},"file:///c%3A/JoystickGremlin-develop/action_plugins/play_sound/__init__.py":{"language":"Python","code":168,"comment":25,"blank":46},"file:///c%3A/JoystickGremlin-develop/action_plugins/pause/__init__.py":{"language":"Python","code":124,"comment":24,"blank":51},"file:///c%3A/JoystickGremlin-develop/container_plugins/switch/__init__.py":{"language":"Python","code":486,"comment":43,"blank":159},"file:///c%3A/JoystickGremlin-develop/action_plugins/noop/__init__.py":{"language":"Python","code":48,"comment":22,"blank":31},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_gamepad/__init__.py":{"language":"Python","code":245,"comment":33,"blank":77},"file:///c%3A/JoystickGremlin-develop/container_plugins/range/__init__.py":{"language":"Python","code":555,"comment":70,"blank":199},"file:///c%3A/JoystickGremlin-develop/container_plugins/double_tap/__init__.py":{"language":"Python","code":294,"comment":37,"blank":72},"file:///c%3A/JoystickGremlin-develop/container_plugins/hat_buttons/__init__.py":{"language":"Python","code":257,"comment":29,"blank":64},"file:///c%3A/JoystickGremlin-develop/dark_manage_modes.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/action_plugins/merged_axis/__init__.py":{"language":"Python","code":447,"comment":57,"blank":195},"file:///c%3A/JoystickGremlin-develop/container_plugins/sequence/__init__.py":{"language":"Python","code":244,"comment":28,"blank":79},"file:///c%3A/JoystickGremlin-develop/container_plugins/smart_toggle/__init__.py":{"language":"Python","code":201,"comment":33,"blank":59},"file:///c%3A/JoystickGremlin-develop/vjoy/vjoy.py":{"language":"Python","code":677,"comment":47,"blank":180},"file:///c%3A/JoystickGremlin-develop/container_plugins/button/__init__.py":{"language":"Python","code":212,"comment":26,"blank":56},"file:///c%3A/JoystickGremlin-develop/scripts/msfs.py":{"language":"Python","code":483,"comment":140,"blank":156},"file:///c%3A/JoystickGremlin-develop/test/test_action_tempo.py":{"language":"Python","code":65,"comment":15,"blank":17},"file:///c%3A/JoystickGremlin-develop/test/conftest.py":{"language":"Python","code":15,"comment":0,"blank":7},"file:///c%3A/JoystickGremlin-develop/test/test_action_remap.py":{"language":"Python","code":123,"comment":15,"blank":21},"file:///c%3A/JoystickGremlin-develop/scripts/osc.py":{"language":"Python","code":1427,"comment":139,"blank":446},"file:///c%3A/JoystickGremlin-develop/container_plugins/basic/__init__.py":{"language":"Python","code":174,"comment":26,"blank":50},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_vjoy/__init__.py":{"language":"Python","code":2921,"comment":221,"blank":965},"file:///c%3A/JoystickGremlin-develop/scripts/util.py":{"language":"Python","code":183,"comment":25,"blank":58},"file:///c%3A/JoystickGremlin-develop/test/test_action_condition.py":{"language":"Python","code":113,"comment":18,"blank":26},"file:///c%3A/JoystickGremlin-develop/test/test_config.py":{"language":"Python","code":71,"comment":15,"blank":25},"file:///c%3A/JoystickGremlin-develop/scripts/sc.py":{"language":"Python","code":573,"comment":92,"blank":219},"file:///c%3A/JoystickGremlin-develop/test/test_tree.py":{"language":"Python","code":223,"comment":15,"blank":52},"file:///c%3A/JoystickGremlin-develop/scripts/configuration.py":{"language":"Python","code":171,"comment":24,"blank":56},"file:///c%3A/JoystickGremlin-develop/test/test_util.py":{"language":"Python","code":115,"comment":15,"blank":18},"file:///c%3A/JoystickGremlin-develop/test/test_profile.py":{"language":"Python","code":223,"comment":17,"blank":43},"file:///c%3A/JoystickGremlin-develop/vjoy/__init__.py":{"language":"Python","code":0,"comment":15,"blank":2},"file:///c%3A/JoystickGremlin-develop/vigem/vigem_gamepad.py":{"language":"Python","code":406,"comment":4,"blank":113},"file:///c%3A/JoystickGremlin-develop/examples/ch%20stick%20and%20pedals/sc.py":{"language":"Python","code":142,"comment":3,"blank":45},"file:///c%3A/JoystickGremlin-develop/vigem/vigem_client.py":{"language":"Python","code":277,"comment":9,"blank":49},"file:///c%3A/JoystickGremlin-develop/vigem/vigem_commons.py":{"language":"Python","code":165,"comment":4,"blank":33},"file:///c%3A/JoystickGremlin-develop/test/action_condition_complex.xml":{"language":"XML","code":171,"comment":0,"blank":3},"file:///c%3A/JoystickGremlin-develop/test/test_action_description.py":{"language":"Python","code":22,"comment":59,"blank":14},"file:///c%3A/JoystickGremlin-develop/test/action_condition_simple.xml":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/vjoy/vjoy_interface.py":{"language":"Python","code":153,"comment":44,"blank":33},"file:///c%3A/JoystickGremlin-develop/requirements.txt":{"language":"pip requirements","code":12,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/examples/ch%20stick%20and%20pedals/profile.xml":{"language":"XML","code":105,"comment":0,"blank":1},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_mouse/__init__.py":{"language":"Python","code":429,"comment":40,"blank":94},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_keyboard/__init__.py":{"language":"Python","code":158,"comment":27,"blank":54},"file:///c%3A/JoystickGremlin-develop/examples/ch%20stick%20and%20pedals/jkb.xml":{"language":"XML","code":250,"comment":64,"blank":41},"file:///c%3A/JoystickGremlin-develop/joystick_gremlin.py":{"language":"Python","code":2810,"comment":376,"blank":1112},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_keyboard_ex/__init__.py":{"language":"Python","code":564,"comment":63,"blank":165},"file:///c%3A/JoystickGremlin-develop/simconnect_simvars.xml":{"language":"XML","code":4987,"comment":0,"blank":1},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/__init__.py":{"language":"Python","code":239,"comment":26,"blank":42},"file:///c%3A/JoystickGremlin-develop/examples/ch%20stick%20and%20pedals/empty.xml":{"language":"XML","code":366,"comment":0,"blank":1},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/macro_record.svg":{"language":"XML","code":48,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_mouse_ex/__init__.py":{"language":"Python","code":716,"comment":49,"blank":206},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/macro_add_pause.svg":{"language":"XML","code":48,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/qml_notes.md":{"language":"Markdown","code":287,"comment":0,"blank":125},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/macro_record_on.svg":{"language":"XML","code":48,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/pause.svg":{"language":"XML","code":73,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/layout.xml":{"language":"XML","code":245,"comment":0,"blank":2},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_axis.svg":{"language":"XML","code":70,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_axis_on.svg":{"language":"XML","code":70,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/press.svg":{"language":"XML","code":65,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_button.svg":{"language":"XML","code":70,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/gremlin/actions.py":{"language":"Python","code":487,"comment":40,"blank":165},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_button_on.svg":{"language":"XML","code":70,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_hat.svg":{"language":"XML","code":70,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/gremlin/base_buttons.py":{"language":"Python","code":143,"comment":3,"blank":37},"file:///c%3A/JoystickGremlin-develop/gremlin/base_classes.py":{"language":"Python","code":255,"comment":21,"blank":111},"file:///c%3A/JoystickGremlin-develop/action_plugins/gated_axis/__init__.py":{"language":"Python","code":99,"comment":22,"blank":43},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_key.svg":{"language":"XML","code":70,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/gremlin/base_conditions.py":{"language":"Python","code":505,"comment":14,"blank":164},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_hat_on.svg":{"language":"XML","code":70,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/logs/custom_widgets.log":{"language":"Log","code":13,"comment":0,"blank":1},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_key_on.svg":{"language":"XML","code":70,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/gremlin/cheatsheet.py":{"language":"Python","code":423,"comment":45,"blank":125},"file:///c%3A/JoystickGremlin-develop/README.md":{"language":"Markdown","code":1810,"comment":0,"blank":1002},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_mouse.svg":{"language":"XML","code":70,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/gfx/about.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/clipboard.py":{"language":"Python","code":172,"comment":23,"blank":54},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_mouse_on.svg":{"language":"XML","code":70,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/release.svg":{"language":"XML","code":65,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/gfx/activate_on.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gfx/activate.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/qml/helpers.js":{"language":"JavaScript","code":15,"comment":0,"blank":2},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/time.svg":{"language":"XML","code":104,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/time_on.svg":{"language":"XML","code":99,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/gremlin/base_profile.py":{"language":"Python","code":2830,"comment":202,"blank":771},"file:///c%3A/JoystickGremlin-develop/gremlin/code_runner.py":{"language":"Python","code":410,"comment":93,"blank":170},"file:///c%3A/JoystickGremlin-develop/gremlin/common.py":{"language":"Python","code":113,"comment":18,"blank":30},"file:///c%3A/JoystickGremlin-develop/action_plugins/common.py":{"language":"Python","code":0,"comment":15,"blank":2},"file:///c%3A/JoystickGremlin-develop/gfx/button_copy.svg":{"language":"XML","code":11,"comment":1,"blank":1},"file:///c%3A/JoystickGremlin-develop/gremlin/curve_handler_instructions.md":{"language":"Markdown","code":35,"comment":0,"blank":39},"file:///c%3A/JoystickGremlin-develop/gfx/curve_grid_ex.svg":{"language":"XML","code":153,"comment":0,"blank":16},"file:///c%3A/JoystickGremlin-develop/gfx/dark_about.svg":{"language":"XML","code":14,"comment":1,"blank":0},"file:///c%3A/JoystickGremlin-develop/gfx/calibration.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/control_action.py":{"language":"Python","code":54,"comment":16,"blank":29},"file:///c%3A/JoystickGremlin-develop/gfx/button_paste.svg":{"language":"XML","code":11,"comment":1,"blank":1},"file:///c%3A/JoystickGremlin-develop/gremlin/config.py":{"language":"Python","code":1420,"comment":66,"blank":421},"file:///c%3A/JoystickGremlin-develop/gfx/dark_activate.svg":{"language":"XML","code":17,"comment":1,"blank":0},"file:///c%3A/JoystickGremlin-develop/deploy.bat":{"language":"Batch","code":16,"comment":0,"blank":6},"file:///c%3A/JoystickGremlin-develop/gremlin/curve_handler.py":{"language":"Python","code":1770,"comment":109,"blank":538},"file:///c%3A/JoystickGremlin-develop/checklist.md":{"language":"Markdown","code":10,"comment":0,"blank":4},"file:///c%3A/JoystickGremlin-develop/resources.py":{"language":"Python","code":15930,"comment":57,"blank":9},"file:///c%3A/JoystickGremlin-develop/gremlin/gated_handler_instructions.md":{"language":"Markdown","code":35,"comment":0,"blank":24},"file:///c%3A/JoystickGremlin-develop/gfx/dark_manage_modes.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gfx/dark_list_add.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/gamepad_handling.py":{"language":"Python","code":64,"comment":3,"blank":22},"file:///c%3A/JoystickGremlin-develop/gfx/dark_input_viewer.svg":{"language":"XML","code":23,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/gremlin/fsm.py":{"language":"Python","code":43,"comment":15,"blank":16},"file:///c%3A/JoystickGremlin-develop/gfx/dark_input_repeater.svg":{"language":"XML","code":14,"comment":1,"blank":0},"file:///c%3A/JoystickGremlin-develop/gfx/dark_device_information.svg":{"language":"XML","code":14,"comment":1,"blank":0},"file:///c%3A/JoystickGremlin-develop/gfx/dark_curve_grid_ex.svg":{"language":"XML","code":138,"comment":1,"blank":11},"file:///c%3A/JoystickGremlin-develop/gfx/dark_manage_modules.svg":{"language":"XML","code":14,"comment":1,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/hid_guardian.py":{"language":"Python","code":246,"comment":36,"blank":64},"file:///c%3A/JoystickGremlin-develop/gremlin/execution_graph.py":{"language":"Python","code":749,"comment":68,"blank":217},"file:///c%3A/JoystickGremlin-develop/gfx/dark_button_copy.svg":{"language":"XML","code":13,"comment":1,"blank":0},"file:///c%3A/JoystickGremlin-develop/gfx/dark_button_paste.svg":{"language":"XML","code":12,"comment":1,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/gated_handler.py":{"language":"Python","code":3472,"comment":301,"blank":1124},"file:///c%3A/JoystickGremlin-develop/gfx/dark_activate_on.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/error.py":{"language":"Python","code":42,"comment":15,"blank":37},"file:///c%3A/JoystickGremlin-develop/gremlin/event_handler.py":{"language":"Python","code":1459,"comment":213,"blank":506},"file:///c%3A/JoystickGremlin-develop/gfx/dark_profile_open.svg":{"language":"XML","code":14,"comment":1,"blank":0},"file:///c%3A/JoystickGremlin-develop/gfx/delete.svg":{"language":"XML","code":1,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/action_plugins/control/__init__.py":{"language":"Python","code":296,"comment":21,"blank":91},"file:///c%3A/JoystickGremlin-develop/gfx/dark_options.svg":{"language":"XML","code":138,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/sendinput.py":{"language":"Python","code":360,"comment":23,"blank":115},"file:///c%3A/JoystickGremlin-develop/gremlin/singleton_decorator.py":{"language":"Python","code":9,"comment":15,"blank":8},"file:///c%3A/JoystickGremlin-develop/gremlin/shared_state.py":{"language":"Python","code":307,"comment":60,"blank":129},"file:///c%3A/JoystickGremlin-develop/gremlin/profile.py":{"language":"Python","code":831,"comment":68,"blank":182},"file:///c%3A/JoystickGremlin-develop/gremlin/spline.py":{"language":"Python","code":139,"comment":23,"blank":49},"file:///c%3A/JoystickGremlin-develop/gremlin/repeater.py":{"language":"Python","code":139,"comment":41,"blank":40},"file:///c%3A/JoystickGremlin-develop/gremlin/threading.py":{"language":"Python","code":16,"comment":18,"blank":17},"file:///c%3A/JoystickGremlin-develop/gremlin/tree.py":{"language":"Python","code":195,"comment":17,"blank":56},"file:///c%3A/JoystickGremlin-develop/gfx/generic.svg":{"language":"XML","code":5,"comment":0,"blank":1},"file:///c%3A/JoystickGremlin-develop/gremlin/signal.py":{"language":"Python","code":8,"comment":15,"blank":9},"file:///c%3A/JoystickGremlin-develop/gremlin/process_monitor.py":{"language":"Python","code":122,"comment":22,"blank":36},"file:///c%3A/JoystickGremlin-develop/gfx/generate.svg":{"language":"XML","code":5,"comment":0,"blank":1},"file:///c%3A/JoystickGremlin-develop/gremlin/tts.py":{"language":"Python","code":199,"comment":23,"blank":61},"file:///c%3A/JoystickGremlin-develop/gfx/device_information.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/types.py":{"language":"Python","code":873,"comment":17,"blank":166},"file:///c%3A/JoystickGremlin-develop/about/about.html":{"language":"HTML","code":12,"comment":0,"blank":3},"file:///c%3A/JoystickGremlin-develop/gremlin/__init__.py":{"language":"Python","code":0,"comment":44,"blank":7},"file:///c%3A/JoystickGremlin-develop/about/third_party_licenses.html":{"language":"HTML","code":16,"comment":0,"blank":3},"file:///c%3A/JoystickGremlin-develop/about/reportlab.html":{"language":"HTML","code":33,"comment":0,"blank":1},"file:///c%3A/JoystickGremlin-develop/gremlin/util.py":{"language":"Python","code":1389,"comment":87,"blank":359},"file:///c%3A/JoystickGremlin-develop/about/vjoy.html":{"language":"HTML","code":21,"comment":0,"blank":5},"file:///c%3A/JoystickGremlin-develop/about/joystick_gremlin.html":{"language":"HTML","code":528,"comment":0,"blank":109},"file:///c%3A/JoystickGremlin-develop/about/qt5.html":{"language":"HTML","code":132,"comment":0,"blank":37},"file:///c%3A/JoystickGremlin-develop/about/pywin32.html":{"language":"HTML","code":29,"comment":0,"blank":6},"file:///c%3A/JoystickGremlin-develop/about/modernuiicons.html":{"language":"HTML","code":56,"comment":0,"blank":11},"file:///c%3A/JoystickGremlin-develop/gfx/list_add.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/input_viewer.py":{"language":"Python","code":391,"comment":26,"blank":146},"file:///c%3A/JoystickGremlin-develop/about/pyqt.html":{"language":"HTML","code":557,"comment":0,"blank":122},"file:///c%3A/JoystickGremlin-develop/gfx/input_viewer.svg":{"language":"XML","code":51,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gfx/input_repeater.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/joystick_device.py":{"language":"Python","code":874,"comment":115,"blank":330},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/merge_axis.py":{"language":"Python","code":231,"comment":31,"blank":52},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/keyboard_device.py":{"language":"Python","code":491,"comment":60,"blank":193},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/device.py":{"language":"Python","code":523,"comment":43,"blank":151},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/deadzone.py":{"language":"Python","code":347,"comment":26,"blank":91},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/input_item.py":{"language":"Python","code":1983,"comment":175,"blank":708},"file:///c%3A/JoystickGremlin-develop/gfx/list_down.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gfx/list_delete.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/dialogs.py":{"language":"Python","code":2202,"comment":153,"blank":768},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/mode_device.py":{"language":"Python","code":252,"comment":31,"blank":116},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/config.py":{"language":"Python","code":116,"comment":15,"blank":43},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/backend.py":{"language":"Python","code":255,"comment":39,"blank":64},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/midi_device.py":{"language":"Python","code":1489,"comment":122,"blank":490},"file:///c%3A/JoystickGremlin-develop/gremlin/windows_event_hook.py":{"language":"Python","code":329,"comment":60,"blank":91},"file:///c%3A/JoystickGremlin-develop/gfx/list_up.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/axis_calibration.py":{"language":"Python","code":682,"comment":41,"blank":211},"file:///c%3A/JoystickGremlin-develop/gfx/manage_modes.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/profile_creator.py":{"language":"Python","code":274,"comment":28,"blank":68},"file:///c%3A/JoystickGremlin-develop/gfx/hat_n.svg":{"language":"XML","code":65,"comment":1,"blank":2},"file:///c%3A/JoystickGremlin-develop/gremlin/plugin_manager.py":{"language":"Python","code":261,"comment":28,"blank":79},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/profile.py":{"language":"Python","code":547,"comment":35,"blank":125},"file:///c%3A/JoystickGremlin-develop/gremlin/user_plugin.py":{"language":"Python","code":651,"comment":27,"blank":173},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/osc_device.py":{"language":"Python","code":2719,"comment":193,"blank":881},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/profile_settings.py":{"language":"Python","code":248,"comment":29,"blank":89},"file:///c%3A/JoystickGremlin-develop/gfx/dark_profile_save_as.svg":{"language":"XML","code":14,"comment":1,"blank":0},"file:///c%3A/JoystickGremlin-develop/gfx/manage_modules.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/qdatawidget.py":{"language":"Python","code":13,"comment":0,"blank":6},"file:///c%3A/JoystickGremlin-develop/gfx/dark_profile_save.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gfx/mode_delete.svg":{"language":"XML","code":47,"comment":0,"blank":1},"file:///c%3A/JoystickGremlin-develop/gremlin/macro_handler.py":{"language":"Python","code":1375,"comment":74,"blank":351},"file:///c%3A/JoystickGremlin-develop/gremlin/macro.py":{"language":"Python","code":860,"comment":80,"blank":252},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/qsliderwidget.py":{"language":"Python","code":808,"comment":106,"blank":247},"file:///c%3A/JoystickGremlin-develop/gfx/dark_profile_new.svg":{"language":"XML","code":14,"comment":1,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/theme.py":{"language":"Python","code":14,"comment":0,"blank":5},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/ui_about.py":{"language":"Python","code":60,"comment":7,"blank":13},"file:///c%3A/JoystickGremlin-develop/gremlin/keyboard.py":{"language":"Python","code":972,"comment":97,"blank":243},"file:///c%3A/JoystickGremlin-develop/action_plugins/description/__init__.py":{"language":"Python","code":56,"comment":24,"blank":31},"file:///c%3A/JoystickGremlin-develop/gfx/options.svg":{"language":"XML","code":138,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/action_plugins/cycle_modes/__init__.py":{"language":"Python","code":214,"comment":28,"blank":101},"file:///c%3A/JoystickGremlin-develop/dinput/__init__.py":{"language":"Python","code":682,"comment":28,"blank":176},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/ui_activation_condition.py":{"language":"Python","code":893,"comment":38,"blank":306},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/ui_gremlin.py":{"language":"Python","code":207,"comment":20,"blank":26},"file:///c%3A/JoystickGremlin-develop/gremlin/joystick_handling.py":{"language":"Python","code":760,"comment":70,"blank":234},"file:///c%3A/JoystickGremlin-develop/gremlin/input_types.py":{"language":"Python","code":93,"comment":16,"blank":21},"file:///c%3A/JoystickGremlin-develop/gfx/profile_new.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/hints.py":{"language":"Python","code":8,"comment":16,"blank":8},"file:///c%3A/JoystickGremlin-develop/gremlin/input_devices.py":{"language":"Python","code":1715,"comment":118,"blank":492},"file:///c%3A/JoystickGremlin-develop/gfx/profile_open.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/import_profile.py":{"language":"Python","code":1936,"comment":174,"blank":632},"file:///c%3A/JoystickGremlin-develop/gfx/profile_save.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gfx/profile_save_as.svg":{"language":"XML","code":47,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/ui_common.py":{"language":"Python","code":4699,"comment":272,"blank":1383},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/ui_labeled.py":{"language":"Python","code":582,"comment":22,"blank":140},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/ui_util.py":{"language":"Python","code":179,"comment":41,"blank":45},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/user_plugin_management.py":{"language":"Python","code":371,"comment":50,"blank":116},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/__init__.py":{"language":"Python","code":0,"comment":15,"blank":2},"file:///c%3A/JoystickGremlin-develop/gfx/warning.svg":{"language":"XML","code":1,"comment":0,"blank":0},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/virtual_button.py":{"language":"Python","code":255,"comment":15,"blank":76},"file:///c%3A/JoystickGremlin-develop/gremlin/ui/virtual_keyboard.py":{"language":"Python","code":766,"comment":61,"blank":202}}
\ No newline at end of file
+{
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/__init__.py": {
+ "language": "Python",
+ "code": 0,
+ "comment": 15,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/container_plugins/chain/__init__.py": {
+ "language": "Python",
+ "code": 197,
+ "comment": 30,
+ "blank": 59
+ },
+ "file:///c%3A/JoystickGremlin-develop/.env": {
+ "language": "Properties",
+ "code": 0,
+ "comment": 0,
+ "blank": 1
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/Attributes.py": {
+ "language": "Python",
+ "code": 523,
+ "comment": 382,
+ "blank": 94
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnectManager.py": {
+ "language": "Python",
+ "code": 1703,
+ "comment": 131,
+ "blank": 461
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/Constants.py": {
+ "language": "Python",
+ "code": 41,
+ "comment": 10,
+ "blank": 16
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/__init__.py": {
+ "language": "Python",
+ "code": 2804,
+ "comment": 290,
+ "blank": 1074
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/FacilitiesList.py": {
+ "language": "Python",
+ "code": 91,
+ "comment": 3,
+ "blank": 19
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/EventList.py": {
+ "language": "Python",
+ "code": 1264,
+ "comment": 2,
+ "blank": 36
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/simconnect_simvars.xml": {
+ "language": "XML",
+ "code": 4763,
+ "comment": 0,
+ "blank": 1
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/Enum.py": {
+ "language": "Python",
+ "code": 480,
+ "comment": 76,
+ "blank": 162
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_osc/__init__.py": {
+ "language": "Python",
+ "code": 682,
+ "comment": 46,
+ "blank": 224
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/switch_mode/__init__.py": {
+ "language": "Python",
+ "code": 137,
+ "comment": 19,
+ "blank": 49
+ },
+ "file:///c%3A/JoystickGremlin-develop/generate_wix.py": {
+ "language": "Python",
+ "code": 361,
+ "comment": 50,
+ "blank": 70
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/SimConnectBridge.py": {
+ "language": "Python",
+ "code": 255,
+ "comment": 49,
+ "blank": 73
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/__init__.py": {
+ "language": "Python",
+ "code": 12,
+ "comment": 0,
+ "blank": 6
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/toggle_pause/__init__.py": {
+ "language": "Python",
+ "code": 52,
+ "comment": 22,
+ "blank": 30
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/SimConnect.py": {
+ "language": "Python",
+ "code": 1079,
+ "comment": 103,
+ "blank": 270
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/temporary_mode_switch/__init__.py": {
+ "language": "Python",
+ "code": 148,
+ "comment": 27,
+ "blank": 46
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/text_to_speech/__init__.py": {
+ "language": "Python",
+ "code": 198,
+ "comment": 25,
+ "blank": 73
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/run_process/__init__.py": {
+ "language": "Python",
+ "code": 180,
+ "comment": 23,
+ "blank": 75
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/split_axis/__init__.py": {
+ "language": "Python",
+ "code": 185,
+ "comment": 24,
+ "blank": 45
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_simconnect/SimConnect/RequestList.py": {
+ "language": "Python",
+ "code": 981,
+ "comment": 25,
+ "blank": 31
+ },
+ "file:///c%3A/JoystickGremlin-develop/.qmllint.ini": {
+ "language": "Ini",
+ "code": 18,
+ "comment": 0,
+ "blank": 1
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/system/__init__.py": {
+ "language": "Python",
+ "code": 0,
+ "comment": 210,
+ "blank": 80
+ },
+ "file:///c%3A/JoystickGremlin-develop/container_plugins/__init__.py": {
+ "language": "Python",
+ "code": 3,
+ "comment": 15,
+ "blank": 3
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/resume/__init__.py": {
+ "language": "Python",
+ "code": 52,
+ "comment": 22,
+ "blank": 30
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/response_curve/grid.svg": {
+ "language": "XML",
+ "code": 136,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/response_curve_ex/__init__.py": {
+ "language": "Python",
+ "code": 121,
+ "comment": 22,
+ "blank": 55
+ },
+ "file:///c%3A/JoystickGremlin-develop/.github/FUNDING.yml": {
+ "language": "YAML",
+ "code": 1,
+ "comment": 1,
+ "blank": 3
+ },
+ "file:///c%3A/JoystickGremlin-develop/container_plugins/tick/__init__.py": {
+ "language": "Python",
+ "code": 326,
+ "comment": 27,
+ "blank": 101
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/response_curve/__init__.py": {
+ "language": "Python",
+ "code": 1136,
+ "comment": 83,
+ "blank": 327
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/remap/__init__.py": {
+ "language": "Python",
+ "code": 410,
+ "comment": 47,
+ "blank": 110
+ },
+ "file:///c%3A/JoystickGremlin-develop/container_plugins/tempoEx/__init__.py": {
+ "language": "Python",
+ "code": 466,
+ "comment": 61,
+ "blank": 143
+ },
+ "file:///c%3A/JoystickGremlin-develop/container_plugins/tempo/__init__.py": {
+ "language": "Python",
+ "code": 296,
+ "comment": 35,
+ "blank": 73
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/previous_mode/__init__.py": {
+ "language": "Python",
+ "code": 52,
+ "comment": 22,
+ "blank": 30
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/play_sound/__init__.py": {
+ "language": "Python",
+ "code": 168,
+ "comment": 25,
+ "blank": 46
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/pause/__init__.py": {
+ "language": "Python",
+ "code": 124,
+ "comment": 24,
+ "blank": 51
+ },
+ "file:///c%3A/JoystickGremlin-develop/container_plugins/switch/__init__.py": {
+ "language": "Python",
+ "code": 486,
+ "comment": 43,
+ "blank": 159
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/noop/__init__.py": {
+ "language": "Python",
+ "code": 48,
+ "comment": 22,
+ "blank": 31
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_gamepad/__init__.py": {
+ "language": "Python",
+ "code": 245,
+ "comment": 33,
+ "blank": 77
+ },
+ "file:///c%3A/JoystickGremlin-develop/container_plugins/range/__init__.py": {
+ "language": "Python",
+ "code": 555,
+ "comment": 70,
+ "blank": 199
+ },
+ "file:///c%3A/JoystickGremlin-develop/container_plugins/double_tap/__init__.py": {
+ "language": "Python",
+ "code": 294,
+ "comment": 37,
+ "blank": 72
+ },
+ "file:///c%3A/JoystickGremlin-develop/container_plugins/hat_buttons/__init__.py": {
+ "language": "Python",
+ "code": 257,
+ "comment": 29,
+ "blank": 64
+ },
+ "file:///c%3A/JoystickGremlin-develop/dark_manage_modes.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/merged_axis/__init__.py": {
+ "language": "Python",
+ "code": 447,
+ "comment": 57,
+ "blank": 195
+ },
+ "file:///c%3A/JoystickGremlin-develop/container_plugins/sequence/__init__.py": {
+ "language": "Python",
+ "code": 244,
+ "comment": 28,
+ "blank": 79
+ },
+ "file:///c%3A/JoystickGremlin-develop/container_plugins/smart_toggle/__init__.py": {
+ "language": "Python",
+ "code": 201,
+ "comment": 33,
+ "blank": 59
+ },
+ "file:///c%3A/JoystickGremlin-develop/vjoy/vjoy.py": {
+ "language": "Python",
+ "code": 677,
+ "comment": 47,
+ "blank": 180
+ },
+ "file:///c%3A/JoystickGremlin-develop/container_plugins/button/__init__.py": {
+ "language": "Python",
+ "code": 212,
+ "comment": 26,
+ "blank": 56
+ },
+ "file:///c%3A/JoystickGremlin-develop/scripts/msfs.py": {
+ "language": "Python",
+ "code": 483,
+ "comment": 140,
+ "blank": 156
+ },
+ "file:///c%3A/JoystickGremlin-develop/test/test_action_tempo.py": {
+ "language": "Python",
+ "code": 65,
+ "comment": 15,
+ "blank": 17
+ },
+ "file:///c%3A/JoystickGremlin-develop/test/conftest.py": {
+ "language": "Python",
+ "code": 15,
+ "comment": 0,
+ "blank": 7
+ },
+ "file:///c%3A/JoystickGremlin-develop/test/test_action_remap.py": {
+ "language": "Python",
+ "code": 123,
+ "comment": 15,
+ "blank": 21
+ },
+ "file:///c%3A/JoystickGremlin-develop/scripts/osc.py": {
+ "language": "Python",
+ "code": 1427,
+ "comment": 139,
+ "blank": 446
+ },
+ "file:///c%3A/JoystickGremlin-develop/container_plugins/basic/__init__.py": {
+ "language": "Python",
+ "code": 174,
+ "comment": 26,
+ "blank": 50
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_vjoy/__init__.py": {
+ "language": "Python",
+ "code": 2921,
+ "comment": 221,
+ "blank": 965
+ },
+ "file:///c%3A/JoystickGremlin-develop/scripts/util.py": {
+ "language": "Python",
+ "code": 183,
+ "comment": 25,
+ "blank": 58
+ },
+ "file:///c%3A/JoystickGremlin-develop/test/test_action_condition.py": {
+ "language": "Python",
+ "code": 113,
+ "comment": 18,
+ "blank": 26
+ },
+ "file:///c%3A/JoystickGremlin-develop/test/test_config.py": {
+ "language": "Python",
+ "code": 71,
+ "comment": 15,
+ "blank": 25
+ },
+ "file:///c%3A/JoystickGremlin-develop/scripts/sc.py": {
+ "language": "Python",
+ "code": 573,
+ "comment": 92,
+ "blank": 219
+ },
+ "file:///c%3A/JoystickGremlin-develop/test/test_tree.py": {
+ "language": "Python",
+ "code": 223,
+ "comment": 15,
+ "blank": 52
+ },
+ "file:///c%3A/JoystickGremlin-develop/scripts/configuration.py": {
+ "language": "Python",
+ "code": 171,
+ "comment": 24,
+ "blank": 56
+ },
+ "file:///c%3A/JoystickGremlin-develop/test/test_util.py": {
+ "language": "Python",
+ "code": 115,
+ "comment": 15,
+ "blank": 18
+ },
+ "file:///c%3A/JoystickGremlin-develop/test/test_profile.py": {
+ "language": "Python",
+ "code": 223,
+ "comment": 17,
+ "blank": 43
+ },
+ "file:///c%3A/JoystickGremlin-develop/vjoy/__init__.py": {
+ "language": "Python",
+ "code": 0,
+ "comment": 15,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/vigem/vigem_gamepad.py": {
+ "language": "Python",
+ "code": 406,
+ "comment": 4,
+ "blank": 113
+ },
+ "file:///c%3A/JoystickGremlin-develop/examples/ch%20stick%20and%20pedals/sc.py": {
+ "language": "Python",
+ "code": 142,
+ "comment": 3,
+ "blank": 45
+ },
+ "file:///c%3A/JoystickGremlin-develop/vigem/vigem_client.py": {
+ "language": "Python",
+ "code": 277,
+ "comment": 9,
+ "blank": 49
+ },
+ "file:///c%3A/JoystickGremlin-develop/vigem/vigem_commons.py": {
+ "language": "Python",
+ "code": 165,
+ "comment": 4,
+ "blank": 33
+ },
+ "file:///c%3A/JoystickGremlin-develop/test/action_condition_complex.xml": {
+ "language": "XML",
+ "code": 171,
+ "comment": 0,
+ "blank": 3
+ },
+ "file:///c%3A/JoystickGremlin-develop/test/test_action_description.py": {
+ "language": "Python",
+ "code": 22,
+ "comment": 59,
+ "blank": 14
+ },
+ "file:///c%3A/JoystickGremlin-develop/test/action_condition_simple.xml": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/vjoy/vjoy_interface.py": {
+ "language": "Python",
+ "code": 153,
+ "comment": 44,
+ "blank": 33
+ },
+ "file:///c%3A/JoystickGremlin-develop/requirements.txt": {
+ "language": "pip requirements",
+ "code": 12,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/examples/ch%20stick%20and%20pedals/profile.xml": {
+ "language": "XML",
+ "code": 105,
+ "comment": 0,
+ "blank": 1
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_mouse/__init__.py": {
+ "language": "Python",
+ "code": 429,
+ "comment": 40,
+ "blank": 94
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_keyboard/__init__.py": {
+ "language": "Python",
+ "code": 158,
+ "comment": 27,
+ "blank": 54
+ },
+ "file:///c%3A/JoystickGremlin-develop/examples/ch%20stick%20and%20pedals/jkb.xml": {
+ "language": "XML",
+ "code": 250,
+ "comment": 64,
+ "blank": 41
+ },
+ "file:///c%3A/JoystickGremlin-develop/joystick_gremlin.py": {
+ "language": "Python",
+ "code": 2810,
+ "comment": 376,
+ "blank": 1112
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_keyboard_ex/__init__.py": {
+ "language": "Python",
+ "code": 564,
+ "comment": 63,
+ "blank": 165
+ },
+ "file:///c%3A/JoystickGremlin-develop/simconnect_simvars.xml": {
+ "language": "XML",
+ "code": 4987,
+ "comment": 0,
+ "blank": 1
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/__init__.py": {
+ "language": "Python",
+ "code": 239,
+ "comment": 26,
+ "blank": 42
+ },
+ "file:///c%3A/JoystickGremlin-develop/examples/ch%20stick%20and%20pedals/empty.xml": {
+ "language": "XML",
+ "code": 366,
+ "comment": 0,
+ "blank": 1
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/macro_record.svg": {
+ "language": "XML",
+ "code": 48,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/map_to_mouse_ex/__init__.py": {
+ "language": "Python",
+ "code": 716,
+ "comment": 49,
+ "blank": 206
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/macro_add_pause.svg": {
+ "language": "XML",
+ "code": 48,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/qml_notes.md": {
+ "language": "Markdown",
+ "code": 287,
+ "comment": 0,
+ "blank": 125
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/macro_record_on.svg": {
+ "language": "XML",
+ "code": 48,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/pause.svg": {
+ "language": "XML",
+ "code": 73,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/layout.xml": {
+ "language": "XML",
+ "code": 245,
+ "comment": 0,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_axis.svg": {
+ "language": "XML",
+ "code": 70,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_axis_on.svg": {
+ "language": "XML",
+ "code": 70,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/press.svg": {
+ "language": "XML",
+ "code": 65,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_button.svg": {
+ "language": "XML",
+ "code": 70,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/actions.py": {
+ "language": "Python",
+ "code": 487,
+ "comment": 40,
+ "blank": 165
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_button_on.svg": {
+ "language": "XML",
+ "code": 70,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_hat.svg": {
+ "language": "XML",
+ "code": 70,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/base_buttons.py": {
+ "language": "Python",
+ "code": 143,
+ "comment": 3,
+ "blank": 37
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/base_classes.py": {
+ "language": "Python",
+ "code": 255,
+ "comment": 21,
+ "blank": 111
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/gated_axis/__init__.py": {
+ "language": "Python",
+ "code": 99,
+ "comment": 22,
+ "blank": 43
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_key.svg": {
+ "language": "XML",
+ "code": 70,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/base_conditions.py": {
+ "language": "Python",
+ "code": 505,
+ "comment": 14,
+ "blank": 164
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_hat_on.svg": {
+ "language": "XML",
+ "code": 70,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/logs/custom_widgets.log": {
+ "language": "Log",
+ "code": 13,
+ "comment": 0,
+ "blank": 1
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_key_on.svg": {
+ "language": "XML",
+ "code": 70,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/cheatsheet.py": {
+ "language": "Python",
+ "code": 423,
+ "comment": 45,
+ "blank": 125
+ },
+ "file:///c%3A/JoystickGremlin-develop/README.md": {
+ "language": "Markdown",
+ "code": 1810,
+ "comment": 0,
+ "blank": 1002
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_mouse.svg": {
+ "language": "XML",
+ "code": 70,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/about.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/clipboard.py": {
+ "language": "Python",
+ "code": 172,
+ "comment": 23,
+ "blank": 54
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/record_mouse_on.svg": {
+ "language": "XML",
+ "code": 70,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/release.svg": {
+ "language": "XML",
+ "code": 65,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/activate_on.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/activate.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/qml/helpers.js": {
+ "language": "JavaScript",
+ "code": 15,
+ "comment": 0,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/time.svg": {
+ "language": "XML",
+ "code": 104,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/macro/gfx/time_on.svg": {
+ "language": "XML",
+ "code": 99,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/base_profile.py": {
+ "language": "Python",
+ "code": 2830,
+ "comment": 202,
+ "blank": 771
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/code_runner.py": {
+ "language": "Python",
+ "code": 410,
+ "comment": 93,
+ "blank": 170
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/common.py": {
+ "language": "Python",
+ "code": 113,
+ "comment": 18,
+ "blank": 30
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/common.py": {
+ "language": "Python",
+ "code": 0,
+ "comment": 15,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/button_copy.svg": {
+ "language": "XML",
+ "code": 11,
+ "comment": 1,
+ "blank": 1
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/curve_handler_instructions.md": {
+ "language": "Markdown",
+ "code": 35,
+ "comment": 0,
+ "blank": 39
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/curve_grid_ex.svg": {
+ "language": "XML",
+ "code": 153,
+ "comment": 0,
+ "blank": 16
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_about.svg": {
+ "language": "XML",
+ "code": 14,
+ "comment": 1,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/calibration.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/control_action.py": {
+ "language": "Python",
+ "code": 54,
+ "comment": 16,
+ "blank": 29
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/button_paste.svg": {
+ "language": "XML",
+ "code": 11,
+ "comment": 1,
+ "blank": 1
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/config.py": {
+ "language": "Python",
+ "code": 1420,
+ "comment": 66,
+ "blank": 421
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_activate.svg": {
+ "language": "XML",
+ "code": 17,
+ "comment": 1,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/deploy.bat": {
+ "language": "Batch",
+ "code": 16,
+ "comment": 0,
+ "blank": 6
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/curve_handler.py": {
+ "language": "Python",
+ "code": 1770,
+ "comment": 109,
+ "blank": 538
+ },
+ "file:///c%3A/JoystickGremlin-develop/checklist.md": {
+ "language": "Markdown",
+ "code": 10,
+ "comment": 0,
+ "blank": 4
+ },
+ "file:///c%3A/JoystickGremlin-develop/resources.py": {
+ "language": "Python",
+ "code": 15930,
+ "comment": 57,
+ "blank": 9
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/gated_handler_instructions.md": {
+ "language": "Markdown",
+ "code": 35,
+ "comment": 0,
+ "blank": 24
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_manage_modes.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_list_add.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/gamepad_handling.py": {
+ "language": "Python",
+ "code": 64,
+ "comment": 3,
+ "blank": 22
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_input_viewer.svg": {
+ "language": "XML",
+ "code": 23,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/fsm.py": {
+ "language": "Python",
+ "code": 43,
+ "comment": 15,
+ "blank": 16
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_input_repeater.svg": {
+ "language": "XML",
+ "code": 14,
+ "comment": 1,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_device_information.svg": {
+ "language": "XML",
+ "code": 14,
+ "comment": 1,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_curve_grid_ex.svg": {
+ "language": "XML",
+ "code": 138,
+ "comment": 1,
+ "blank": 11
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_manage_modules.svg": {
+ "language": "XML",
+ "code": 14,
+ "comment": 1,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/hid_guardian.py": {
+ "language": "Python",
+ "code": 246,
+ "comment": 36,
+ "blank": 64
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/execution_graph.py": {
+ "language": "Python",
+ "code": 749,
+ "comment": 68,
+ "blank": 217
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_button_copy.svg": {
+ "language": "XML",
+ "code": 13,
+ "comment": 1,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_button_paste.svg": {
+ "language": "XML",
+ "code": 12,
+ "comment": 1,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/gated_handler.py": {
+ "language": "Python",
+ "code": 3472,
+ "comment": 301,
+ "blank": 1124
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_activate_on.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/error.py": {
+ "language": "Python",
+ "code": 42,
+ "comment": 15,
+ "blank": 37
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/event_handler.py": {
+ "language": "Python",
+ "code": 1459,
+ "comment": 213,
+ "blank": 506
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_profile_open.svg": {
+ "language": "XML",
+ "code": 14,
+ "comment": 1,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/delete.svg": {
+ "language": "XML",
+ "code": 1,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/control/__init__.py": {
+ "language": "Python",
+ "code": 296,
+ "comment": 21,
+ "blank": 91
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_options.svg": {
+ "language": "XML",
+ "code": 138,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/sendinput.py": {
+ "language": "Python",
+ "code": 360,
+ "comment": 23,
+ "blank": 115
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/singleton_decorator.py": {
+ "language": "Python",
+ "code": 9,
+ "comment": 15,
+ "blank": 8
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/shared_state.py": {
+ "language": "Python",
+ "code": 307,
+ "comment": 60,
+ "blank": 129
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/profile.py": {
+ "language": "Python",
+ "code": 831,
+ "comment": 68,
+ "blank": 182
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/spline.py": {
+ "language": "Python",
+ "code": 139,
+ "comment": 23,
+ "blank": 49
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/repeater.py": {
+ "language": "Python",
+ "code": 139,
+ "comment": 41,
+ "blank": 40
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/threading.py": {
+ "language": "Python",
+ "code": 16,
+ "comment": 18,
+ "blank": 17
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/tree.py": {
+ "language": "Python",
+ "code": 195,
+ "comment": 17,
+ "blank": 56
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/generic.svg": {
+ "language": "XML",
+ "code": 5,
+ "comment": 0,
+ "blank": 1
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/signal.py": {
+ "language": "Python",
+ "code": 8,
+ "comment": 15,
+ "blank": 9
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/process_monitor.py": {
+ "language": "Python",
+ "code": 122,
+ "comment": 22,
+ "blank": 36
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/generate.svg": {
+ "language": "XML",
+ "code": 5,
+ "comment": 0,
+ "blank": 1
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/tts.py": {
+ "language": "Python",
+ "code": 199,
+ "comment": 23,
+ "blank": 61
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/device_information.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/types.py": {
+ "language": "Python",
+ "code": 873,
+ "comment": 17,
+ "blank": 166
+ },
+ "file:///c%3A/JoystickGremlin-develop/about/about.html": {
+ "language": "HTML",
+ "code": 12,
+ "comment": 0,
+ "blank": 3
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/__init__.py": {
+ "language": "Python",
+ "code": 0,
+ "comment": 44,
+ "blank": 7
+ },
+ "file:///c%3A/JoystickGremlin-develop/about/third_party_licenses.html": {
+ "language": "HTML",
+ "code": 16,
+ "comment": 0,
+ "blank": 3
+ },
+ "file:///c%3A/JoystickGremlin-develop/about/reportlab.html": {
+ "language": "HTML",
+ "code": 33,
+ "comment": 0,
+ "blank": 1
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/util.py": {
+ "language": "Python",
+ "code": 1389,
+ "comment": 87,
+ "blank": 359
+ },
+ "file:///c%3A/JoystickGremlin-develop/about/vjoy.html": {
+ "language": "HTML",
+ "code": 21,
+ "comment": 0,
+ "blank": 5
+ },
+ "file:///c%3A/JoystickGremlin-develop/about/joystick_gremlin.html": {
+ "language": "HTML",
+ "code": 528,
+ "comment": 0,
+ "blank": 109
+ },
+ "file:///c%3A/JoystickGremlin-develop/about/qt5.html": {
+ "language": "HTML",
+ "code": 132,
+ "comment": 0,
+ "blank": 37
+ },
+ "file:///c%3A/JoystickGremlin-develop/about/pywin32.html": {
+ "language": "HTML",
+ "code": 29,
+ "comment": 0,
+ "blank": 6
+ },
+ "file:///c%3A/JoystickGremlin-develop/about/modernuiicons.html": {
+ "language": "HTML",
+ "code": 56,
+ "comment": 0,
+ "blank": 11
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/list_add.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/input_viewer.py": {
+ "language": "Python",
+ "code": 391,
+ "comment": 26,
+ "blank": 146
+ },
+ "file:///c%3A/JoystickGremlin-develop/about/pyqt.html": {
+ "language": "HTML",
+ "code": 557,
+ "comment": 0,
+ "blank": 122
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/input_viewer.svg": {
+ "language": "XML",
+ "code": 51,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/input_repeater.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/joystick_device.py": {
+ "language": "Python",
+ "code": 874,
+ "comment": 115,
+ "blank": 330
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/merge_axis.py": {
+ "language": "Python",
+ "code": 231,
+ "comment": 31,
+ "blank": 52
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/keyboard_device.py": {
+ "language": "Python",
+ "code": 491,
+ "comment": 60,
+ "blank": 193
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/device.py": {
+ "language": "Python",
+ "code": 523,
+ "comment": 43,
+ "blank": 151
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/deadzone.py": {
+ "language": "Python",
+ "code": 347,
+ "comment": 26,
+ "blank": 91
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/input_item.py": {
+ "language": "Python",
+ "code": 1983,
+ "comment": 175,
+ "blank": 708
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/list_down.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/list_delete.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/dialogs.py": {
+ "language": "Python",
+ "code": 2202,
+ "comment": 153,
+ "blank": 768
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/mode_device.py": {
+ "language": "Python",
+ "code": 252,
+ "comment": 31,
+ "blank": 116
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/config.py": {
+ "language": "Python",
+ "code": 116,
+ "comment": 15,
+ "blank": 43
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/backend.py": {
+ "language": "Python",
+ "code": 255,
+ "comment": 39,
+ "blank": 64
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/midi_device.py": {
+ "language": "Python",
+ "code": 1489,
+ "comment": 122,
+ "blank": 490
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/windows_event_hook.py": {
+ "language": "Python",
+ "code": 329,
+ "comment": 60,
+ "blank": 91
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/list_up.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/axis_calibration.py": {
+ "language": "Python",
+ "code": 682,
+ "comment": 41,
+ "blank": 211
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/manage_modes.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/profile_creator.py": {
+ "language": "Python",
+ "code": 274,
+ "comment": 28,
+ "blank": 68
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/hat_n.svg": {
+ "language": "XML",
+ "code": 65,
+ "comment": 1,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/plugin_manager.py": {
+ "language": "Python",
+ "code": 261,
+ "comment": 28,
+ "blank": 79
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/profile.py": {
+ "language": "Python",
+ "code": 547,
+ "comment": 35,
+ "blank": 125
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/user_plugin.py": {
+ "language": "Python",
+ "code": 651,
+ "comment": 27,
+ "blank": 173
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/osc_device.py": {
+ "language": "Python",
+ "code": 2719,
+ "comment": 193,
+ "blank": 881
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/profile_settings.py": {
+ "language": "Python",
+ "code": 248,
+ "comment": 29,
+ "blank": 89
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_profile_save_as.svg": {
+ "language": "XML",
+ "code": 14,
+ "comment": 1,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/manage_modules.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/qdatawidget.py": {
+ "language": "Python",
+ "code": 13,
+ "comment": 0,
+ "blank": 6
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_profile_save.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/mode_delete.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 1
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/macro_handler.py": {
+ "language": "Python",
+ "code": 1375,
+ "comment": 74,
+ "blank": 351
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/macro.py": {
+ "language": "Python",
+ "code": 860,
+ "comment": 80,
+ "blank": 252
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/qsliderwidget.py": {
+ "language": "Python",
+ "code": 808,
+ "comment": 106,
+ "blank": 247
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/dark_profile_new.svg": {
+ "language": "XML",
+ "code": 14,
+ "comment": 1,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/theme.py": {
+ "language": "Python",
+ "code": 14,
+ "comment": 0,
+ "blank": 5
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/ui_about.py": {
+ "language": "Python",
+ "code": 60,
+ "comment": 7,
+ "blank": 13
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/keyboard.py": {
+ "language": "Python",
+ "code": 972,
+ "comment": 97,
+ "blank": 243
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/description/__init__.py": {
+ "language": "Python",
+ "code": 56,
+ "comment": 24,
+ "blank": 31
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/options.svg": {
+ "language": "XML",
+ "code": 138,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/action_plugins/cycle_modes/__init__.py": {
+ "language": "Python",
+ "code": 214,
+ "comment": 28,
+ "blank": 101
+ },
+ "file:///c%3A/JoystickGremlin-develop/dinput/__init__.py": {
+ "language": "Python",
+ "code": 682,
+ "comment": 28,
+ "blank": 176
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/ui_activation_condition.py": {
+ "language": "Python",
+ "code": 893,
+ "comment": 38,
+ "blank": 306
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/ui_gremlin.py": {
+ "language": "Python",
+ "code": 207,
+ "comment": 20,
+ "blank": 26
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/joystick_handling.py": {
+ "language": "Python",
+ "code": 760,
+ "comment": 70,
+ "blank": 234
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/input_types.py": {
+ "language": "Python",
+ "code": 93,
+ "comment": 16,
+ "blank": 21
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/profile_new.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/hints.py": {
+ "language": "Python",
+ "code": 8,
+ "comment": 16,
+ "blank": 8
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/input_devices.py": {
+ "language": "Python",
+ "code": 1715,
+ "comment": 118,
+ "blank": 492
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/profile_open.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/import_profile.py": {
+ "language": "Python",
+ "code": 1936,
+ "comment": 174,
+ "blank": 632
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/profile_save.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/profile_save_as.svg": {
+ "language": "XML",
+ "code": 47,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/ui_common.py": {
+ "language": "Python",
+ "code": 4699,
+ "comment": 272,
+ "blank": 1383
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/ui_labeled.py": {
+ "language": "Python",
+ "code": 582,
+ "comment": 22,
+ "blank": 140
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/ui_util.py": {
+ "language": "Python",
+ "code": 179,
+ "comment": 41,
+ "blank": 45
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/user_plugin_management.py": {
+ "language": "Python",
+ "code": 371,
+ "comment": 50,
+ "blank": 116
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/__init__.py": {
+ "language": "Python",
+ "code": 0,
+ "comment": 15,
+ "blank": 2
+ },
+ "file:///c%3A/JoystickGremlin-develop/gfx/warning.svg": {
+ "language": "XML",
+ "code": 1,
+ "comment": 0,
+ "blank": 0
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/virtual_button.py": {
+ "language": "Python",
+ "code": 255,
+ "comment": 15,
+ "blank": 76
+ },
+ "file:///c%3A/JoystickGremlin-develop/gremlin/ui/virtual_keyboard.py": {
+ "language": "Python",
+ "code": 766,
+ "comment": 61,
+ "blank": 202
+ }
+}
\ No newline at end of file
diff --git a/.env b/.env
deleted file mode 100644
index e69de29b..00000000
diff --git a/.python-version b/.python-version
new file mode 100644
index 00000000..e4fba218
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.12
diff --git a/action_plugins/__init__.py b/action_plugins/__init__.py
index bc219ec2..0cdcf0ee 100644
--- a/action_plugins/__init__.py
+++ b/action_plugins/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/action_plugins/common.py b/action_plugins/common.py
index 0468910b..6b42754a 100644
--- a/action_plugins/common.py
+++ b/action_plugins/common.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/action_plugins/control/__init__.py b/action_plugins/control/__init__.py
index a9312c25..20d08eb6 100644
--- a/action_plugins/control/__init__.py
+++ b/action_plugins/control/__init__.py
@@ -1,10 +1,8 @@
from __future__ import annotations
import logging
-import threading
-import time
+# noinspection PyPep8Naming
from lxml import etree as ElementTree
-
-from PySide6 import QtWidgets, QtCore, QtGui
+from PySide6 import QtWidgets, QtCore
import gremlin.actions
import gremlin.base_conditions
import gremlin.base_profile
@@ -15,20 +13,14 @@
import gremlin.joystick_handling
import gremlin.shared_state
import gremlin.types
-from gremlin.util import load_icon
-
-from gremlin.base_conditions import InputActionCondition
from gremlin.input_types import InputType
-from gremlin import input_devices, joystick_handling, util
-from gremlin.error import ProfileError
-from gremlin.util import safe_format, safe_read
import gremlin.ui.ui_common
-import gremlin.ui.input_item
-import os
-import enum
-from gremlin.input_devices import ControlAction, remote_state
-from gremlin.util import *
-import gremlin.util
+from gremlin.ui.input_item import AbstractActionWidget
+from gremlin.input_devices import ControlAction
+from gremlin.util import parse_guid
+# import gremlin.util
+# noinspection PyProtectedMember
+from gremlin.ui.ui_common import _inheritance_tree_to_labels
syslog = logging.getLogger("system")
@@ -43,15 +35,16 @@ def __init__(self, action_data, parent=None):
:param parent the parent of this widget
"""
super().__init__(action_data, parent=parent)
- assert(isinstance(action_data, Control))
-
+ assert isinstance(action_data, Control)
def _create(self, action_data):
- ''' initialization '''
- self.profile : gremlin.base_profile.Profile = gremlin.shared_state.current_profile
+ """initialization"""
+ self.profile: gremlin.base_profile.Profile = (
+ gremlin.shared_state.current_profile
+ )
el = gremlin.event_handler.EventListener()
el.edit_mode_changed.connect(self._profile_edit_mode_changed)
-
+
def _create_ui(self):
"""Creates the UI components."""
self.action_widget = gremlin.ui.ui_common.NoWheelComboBox()
@@ -61,7 +54,7 @@ def _create_ui(self):
self.action_widget.addItem(ControlAction.to_display_name(action), action)
if set_index is None and action == self.action_data.action:
set_index = index
- index+=1
+ index += 1
if set_index:
self.action_widget.setCurrentIndex(set_index)
@@ -94,26 +87,21 @@ def _create_ui(self):
row = 0
self.grid_layout.addWidget(QtWidgets.QLabel("Action:"), row, 0)
self.grid_layout.addWidget(self.action_widget, row, 1)
- row +=1
+ row += 1
self.grid_layout.addWidget(QtWidgets.QLabel("Mode:"), row, 0)
self.grid_layout.addWidget(self.mode_selector, row, 1)
- row +=1
+ row += 1
self.grid_layout.addWidget(QtWidgets.QLabel("Device:"), row, 0)
self.grid_layout.addWidget(self.device_widget, row, 1)
- row +=1
+ row += 1
self.grid_layout.addWidget(QtWidgets.QLabel("Input:"), row, 0)
self.grid_layout.addWidget(self.input_widget, row, 1)
- self.grid_layout.addWidget(QtWidgets.QLabel(),row, 2)
- self.grid_layout.setColumnStretch(3,1)
+ self.grid_layout.addWidget(QtWidgets.QLabel(), row, 2)
+ self.grid_layout.setColumnStretch(3, 1)
self.main_layout.addWidget(self.grid_widget)
-
-
-
-
-
def _populate_ui(self):
pass
@@ -122,17 +110,18 @@ def _update_mode_list(self):
with QtCore.QSignalBlocker(self.mode_selector):
self.mode_selector.clear()
-
+
# Create mode name labels visualizing the tree structure
inheritance_tree = self.profile.build_inheritance_tree()
labels = []
- gremlin.ui.ui_common._inheritance_tree_to_labels(labels, inheritance_tree, 0)
+ _inheritance_tree_to_labels(
+ labels, inheritance_tree, 0
+ )
# Filter the mode names such that they only occur once below
# their correct parent
mode_names = []
display_names = []
- mode_list = []
for entry in labels:
if entry[0] in mode_names:
idx = mode_names.index(entry[0])
@@ -145,7 +134,7 @@ def _update_mode_list(self):
mode_names.append(entry[0])
display_names.append(entry[1])
- mode = self.action_data.mode
+ mode = self.action_data.mode
index = 1
set_index = None
self.mode_selector.addItem("Any Mode", None)
@@ -156,11 +145,12 @@ def _update_mode_list(self):
if set_index:
self.mode_selector.setCurrentIndex(set_index)
-
def _update_device_list(self):
# device list
- device_list : list [gremlin.base_profile.Device] = self.profile.get_ordered_device_list()
+ device_list: list[gremlin.base_profile.Device] = (
+ self.profile.get_ordered_device_list()
+ )
device_guid = self.action_data.device_guid
with QtCore.QSignalBlocker(self.device_widget):
@@ -169,7 +159,11 @@ def _update_device_list(self):
set_index = None
for index, device in enumerate(device_list):
self.device_widget.addItem(device.name, device)
- if set_index is None and device_guid is not None and device.device_guid == device_guid:
+ if (
+ set_index is None
+ and device_guid is not None
+ and device.device_guid == device_guid
+ ):
set_index = index
if set_index:
@@ -179,10 +173,8 @@ def _update_input_list(self):
# updates the list of inputs for the current device
device = self.device_widget.currentData()
device_profile = self.profile.get_device_modes(
- device.device_guid,
- device.type,
- device.name
- )
+ device.device_guid, device.type, device.name
+ )
use_prefix = False
if self.action_data.mode is None:
mode_list = device_profile.modes.keys()
@@ -190,15 +182,15 @@ def _update_input_list(self):
else:
mode_list = [self.action_data.mode]
- self._index_map = {} # map of index to value
+ self._index_map = {} # map of index to value
self._item_map = {} # map of values to their index
index = 0
- self.input_widget.clear()
+ self.input_widget.clear()
processed = []
for mode in mode_list:
input_items = device_profile.modes[mode]
input_item = self.action_data.target_input_item
-
+
with QtCore.QSignalBlocker(self.device_widget):
set_index = None
for input_type in input_items.config.keys():
@@ -213,35 +205,43 @@ def _update_input_list(self):
# data.device_type,
# data.input_name
# )
- if not data in processed:
- if use_prefix and input_type not in (InputType.JoystickAxis, InputType.JoystickButton, InputType.JoystickHat):
- self.input_widget.addItem(f"[{mode}] {data.input_name}", data)
+ if data not in processed:
+ if use_prefix and input_type not in (
+ InputType.JoystickAxis,
+ InputType.JoystickButton,
+ InputType.JoystickHat,
+ ):
+ self.input_widget.addItem(
+ f"[{mode}] {data.input_name}", data
+ )
else:
self.input_widget.addItem(data.input_name, data)
- if set_index is None and input_item is not None and data.input_id == input_item:
+ if (
+ set_index is None
+ and input_item is not None
+ and data.input_id == input_item
+ ):
set_index = index
index += 1
processed.append(data)
if set_index:
self.input_widget.setCurrentIndex(set_index)
-
@QtCore.Slot()
def _action_changed_cb(self):
action = self.action_widget.currentData()
self.action_data.action = action
-
@QtCore.Slot()
def _mode_changed_cb(self):
mode = self.mode_selector.currentData()
- self.action_data.mode = mode # None means an mode
+ self.action_data.mode = mode # None means an mode
self._update_device_list()
self._update_input_list()
@QtCore.Slot()
def _profile_edit_mode_changed(self):
- ''' called when the list of modes changes in the profile '''
+ """called when the list of modes changes in the profile"""
modes = self.profile.get_modes()
if self.action_data.mode in modes:
# nothing to do
@@ -274,9 +274,9 @@ def __init__(self, action_data, parent = None):
super().__init__(action_data, parent)
self.action_data = action_data
-
- def process_event(self, event, action_value : gremlin.actions.Value, extra_data = None):
-
+ def process_event(
+ self, event, action_value: gremlin.actions.Value, extra_data=None
+ ):
if event.is_pressed is None:
return
is_pressed = event.is_pressed
@@ -293,32 +293,34 @@ def process_event(self, event, action_value : gremlin.actions.Value, extra_data
for mode_name in dev.modes.keys():
mode = dev.modes[mode_name]
for input_type in mode.config.keys():
- item : gremlin.base_profile.InputItem
+ item: gremlin.base_profile.InputItem
for item in mode.config[input_type].values():
if item.input_id == input_id:
match action:
case ControlAction.DisableInput:
- if verbose: syslog.info(f"Control: disable input {item.display_name}")
+ if verbose:
+ syslog.info(
+ f"Control: disable input {item.display_name}"
+ )
item.enabled = False
case ControlAction.EnableInput:
- if verbose: syslog.info(f"Control: enable input {item.display_name}")
+ if verbose:
+ syslog.info(
+ f"Control: enable input {item.display_name}"
+ )
item.enabled = True
case ControlAction.ToggleInput:
item.enabled = not item.enabled
- if verbose: syslog.info(f"Control: toggle input {item.display_name} -> {item.enabled}")
+ if verbose:
+ syslog.info(
+ f"Control: toggle input {item.display_name} -> {item.enabled}"
+ )
return True
-
- return True
-
-
-
-
-
+ return True
class Control(gremlin.base_profile.AbstractAction):
-
"""Action remapping physical joystick inputs to vJoy inputs."""
name = "Control"
@@ -328,7 +330,7 @@ class Control(gremlin.base_profile.AbstractAction):
functor = ControlFunctor
widget = ControlWidget
-
+
input_types = [
InputType.JoystickButton,
InputType.JoystickHat,
@@ -336,45 +338,40 @@ class Control(gremlin.base_profile.AbstractAction):
InputType.KeyboardLatched,
InputType.OpenSoundControl,
InputType.Midi,
-
]
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
- self.action : ControlAction = ControlAction.ToggleInput
- self.mode = gremlin.shared_state.edit_mode # selected mode
+ self.action: ControlAction = ControlAction.ToggleInput
+ self.mode = gremlin.shared_state.edit_mode # selected mode
self.device_guid = None
self.target_input_item = None
def icon(self):
return "fa6s.gears"
-
+
def requires_virtual_button(self):
return False
-
-
-
- def _parse_xml(self, node, data = None):
+
+ def _parse_xml(self, node, data=None):
self.mode = None
self.device_guid = None
self.target_input_item = None
- #input_items = self._get_input_items()
-
+ # input_items = self._get_input_items()
+
if "mode" in node.attrib:
self.mode = node.get("mode")
if "device_guid" in node.attrib:
self.device_guid = parse_guid(node.get("device_guid"))
- #device_type = gremlin.types.DeviceType.to_enum(node.get("device_type"))
+ # device_type = gremlin.types.DeviceType.to_enum(node.get("device_type"))
for node_target in node:
input_item = gremlin.base_profile.InputItem()
input_item.from_xml(node_target, data)
self.target_input_item = input_item
break
-
-
def _generate_xml(self):
node = ElementTree.Element(Control.tag)
if self.mode is not None:
@@ -387,14 +384,12 @@ def _generate_xml(self):
else:
pass
-
if self.target_input_item is not None:
node_target = self.target_input_item.to_xml()
node_target.set("target_type", type(self.target_input_item).__name__)
node.append(node_target)
return node
-
def _is_valid(self):
if self.device_guid is not None and self.target_input_item is not None:
@@ -402,7 +397,6 @@ def _is_valid(self):
return False
-
version = 1
name = "Control"
-create = Control
\ No newline at end of file
+create = Control
diff --git a/action_plugins/cycle_modes/__init__.py b/action_plugins/cycle_modes/__init__.py
index db19e930..460dd570 100644
--- a/action_plugins/cycle_modes/__init__.py
+++ b/action_plugins/cycle_modes/__init__.py
@@ -16,34 +16,29 @@
# along with this program. If not, see .
-import os
-from PySide6 import QtCore, QtGui, QtWidgets
+from PySide6 import QtCore, QtWidgets
from lxml import etree as ElementTree
-
-from PySide6.QtGui import QIcon
from PySide6.QtCore import QModelIndex
-
-
-import gremlin.base_profile
+import gremlin.base_profile
import gremlin.event_handler
+from gremlin.execution_graph import ExecutionContext
from gremlin.input_types import InputType
import gremlin.shared_state
import gremlin.ui.input_item
import gremlin.ui.ui_common
-
class CycleModeModel(QtCore.QAbstractItemModel):
def __init__(self):
super().__init__()
self._data = {}
-
- def rowCount(self, parent = None):
+
+ def rowCount(self, parent=None):
return len(self._data)
- def columnCount(self, parent = None):
+ def columnCount(self, parent=None):
return 1
-
+
def clear(self):
self._data.clear()
@@ -53,17 +48,17 @@ def addItem(self, display, mode):
self._data[count] = (mode, display)
self.endInsertRows()
- def data(self, index : QModelIndex, role = QtCore.Qt.DisplayRole):
+ def data(self, index: QModelIndex, role=QtCore.Qt.ItemDataRole.DisplayRole):
if not index.isValid():
return None
-
- if role == QtCore.Qt.DisplayRole:
+
+ if role == QtCore.Qt.ItemDataRole.DisplayRole:
row = index.row()
return self._data[row][1]
return None
-
+
def swap(self, index_a, index_b):
- ''' swaps two indices '''
+ """swaps two indices"""
data_a = self._data[index_a]
data_b = self._data[index_b]
self._data[index_a] = data_b
@@ -90,63 +85,62 @@ def remove(self, index):
del self._data[index]
def modes(self):
- ''' gets the modes in the list'''
+ """gets the modes in the list"""
return [data[0] for data in self._data.values()]
-
+
def index(self, row, column, parent=QModelIndex()):
- if not row in self._data:
+ if row not in self._data:
return QModelIndex()
return self.createIndex(row, column, row)
-
+
def parent(self, index):
return QModelIndex()
def __len__(self):
return len(self._data)
-class CycleModesWidget(gremlin.ui.input_item.AbstractActionWidget):
+class CycleModesWidget(gremlin.ui.input_item.AbstractActionWidget):
"""Widget allowing the configuration of a list of modes to cycle."""
locked = False
def __init__(self, action_data, parent=None):
super().__init__(action_data, parent=parent)
- assert(isinstance(action_data, CycleModes))
-
-
-
-
+ assert isinstance(action_data, CycleModes)
def _create_ui(self):
-
from gremlin.util import load_icon
- self.ec = gremlin.execution_graph.ExecutionContext()
+ self.ec = ExecutionContext()
self.model = CycleModeModel()
self.view = QtWidgets.QListView()
self.view.setModel(self.model)
-
- self.view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
+
+ self.view.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
# Add widgets which allow modifying the mode list
self.mode_list_widget = gremlin.ui.ui_common.NoWheelComboBox()
prefix = "dark_" if gremlin.shared_state.is_dark_theme else ""
- self.add = QtWidgets.QPushButton(load_icon(f"{prefix}list_add.svg"), "Add")
+ self.add = QtWidgets.QPushButton(load_icon(f"{prefix}list_add.svg"), "Add")
self.add.clicked.connect(self._add_cb)
- self.delete = QtWidgets.QPushButton(load_icon(f"{prefix}list_delete.svg"), "Delete")
-
+ self.delete = QtWidgets.QPushButton(
+ load_icon(f"{prefix}list_delete.svg"), "Delete"
+ )
+
self.delete.clicked.connect(self._remove_cb)
self.up = QtWidgets.QPushButton(load_icon(f"{prefix}list_up.svg"), "Up")
-
+
self.up.clicked.connect(self._up_cb)
self.down = QtWidgets.QPushButton(load_icon(f"{prefix}list_down.svg"), "Down")
self.down.clicked.connect(self._down_cb)
self.button_container_widget = QtWidgets.QWidget()
- self.button_container_layout = QtWidgets.QHBoxLayout(self.button_container_widget)
+ self.button_container_layout = QtWidgets.QHBoxLayout(
+ self.button_container_widget
+ )
self.button_container_layout.addWidget(QtWidgets.QLabel("Mode:"))
self.button_container_layout.addWidget(self.mode_list_widget)
self.button_container_layout.addStretch()
@@ -154,8 +148,6 @@ def _create_ui(self):
self.button_container_layout.addWidget(self.delete)
self.button_container_layout.addWidget(self.up)
self.button_container_layout.addWidget(self.down)
-
-
self.main_layout.addWidget(self.view)
self.main_layout.addWidget(self.button_container_widget)
@@ -164,8 +156,6 @@ def _create_ui(self):
eh = gremlin.event_handler.EventListener()
eh.edit_mode_changed.connect(self._edit_mode_changed)
-
-
def _populate_ui(self):
self._update_mode_list()
@@ -173,48 +163,41 @@ def save_changes(self):
"""Saves UI state to the profile."""
mode_list = self.model.modes()
self.action_data.mode_list = mode_list
- # self.action_modified.emit()
+ self.action_modified.emit()
def _update_mode_list(self):
-
-
with QtCore.QSignalBlocker(self.mode_list_widget):
self.mode_list_widget.clear()
- mode_list = gremlin.shared_state.current_profile.get_modes()
+
+ mode_data = self.ec.getModeNames(as_tuple=True)
+ # modes = gremlin.shared_state.current_profile.get_modes()
self.model.clear()
- for mode in mode_list:
- self.mode_list_widget.addItem(mode, mode)
+ for mode, display in mode_data:
+ self.mode_list_widget.addItem(display, mode)
# verify the modes in the cycle are valid
mode_list = self.action_data.mode_list
modes = gremlin.shared_state.current_profile.get_modes()
for mode in mode_list:
- if not mode in modes:
+ if mode not in modes:
mode_list.remove(mode)
self.model.clear()
for mode in mode_list:
- self.model.addItem(mode, mode)
-
-
-
-
-
-
-
+ node = self.ec.searchModeTree(mode)
+ self.model.addItem(node.display, mode)
@QtCore.Slot()
def _edit_mode_changed(self):
- ''' occurs when the modes are edited or changed '''
+ """occurs when the modes are edited or changed"""
self._update_mode_list()
-
@QtCore.Slot()
def _add_cb(self):
"""Adds the currently selected mode to the list of modes."""
-
+
mode = self.mode_list_widget.currentData()
display = self.mode_list_widget.currentText()
-
+
self.model.addItem(display, mode)
self.save_changes()
@@ -241,13 +224,11 @@ def _remove_cb(self):
class CycleModesFunctor(gremlin.base_profile.AbstractFunctor):
-
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
- self.action_data : CycleModes = action
-
+ self.action_data: CycleModes = action
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
if event.is_pressed:
mode_list = self.action_data.mode_list
index = self.action_data.mode_index
@@ -257,7 +238,8 @@ def process_event(self, event, value, extra_data = None):
index = 0
next_mode = mode_list[index]
- current_mode = gremlin.shared_state.current_mode
+ # noinspection PyProtectedMember
+ current_mode = gremlin.shared_state._current_mode
if current_mode in mode_list and current_mode == next_mode:
# find the next mode as the current mode is alredy the mode to cycle to so pick the next one
index = mode_list.index(current_mode)
@@ -269,18 +251,11 @@ def process_event(self, event, value, extra_data = None):
self.action_data.mode_index = index
gremlin.event_handler.EventHandler().change_mode(next_mode)
-
-
-
-
-
-
return True
class CycleModes(gremlin.base_profile.AbstractAction):
-
"""Action allowing the switching through a list of modes."""
name = "Cycle Modes"
@@ -294,7 +269,7 @@ class CycleModes(gremlin.base_profile.AbstractAction):
# InputType.JoystickButton,
# InputType.JoystickHat,
# InputType.Keyboard,
-
+
# ]
functor = CycleModesFunctor
@@ -304,19 +279,16 @@ def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.mode_list = []
- self.mode_index = 0 # index of the current cycle mode
+ self.mode_index = 0 # index of the current cycle mode
def icon(self):
return "ei.fork"
- #return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
+ # return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
def requires_virtual_button(self):
- return self.get_input_type() in [
- InputType.JoystickAxis,
- InputType.JoystickHat
- ]
+ return self.get_input_type() in [InputType.JoystickAxis, InputType.JoystickHat]
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
for child in node:
self.mode_list.append(child.get("name"))
@@ -335,7 +307,8 @@ def _generate_xml(self):
def priority(self):
# priority relative to other actions in this sequence - 0 is the default for all actions unless specified - higher numbers run last
return 999
-
+
+
version = 1
name = "cycle-modes"
create = CycleModes
diff --git a/action_plugins/description/__init__.py b/action_plugins/description/__init__.py
index 3f98e534..490492ff 100644
--- a/action_plugins/description/__init__.py
+++ b/action_plugins/description/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -18,26 +18,25 @@
import os
from PySide6 import QtWidgets
+# noinspection PyPep8Naming
from lxml import etree as ElementTree
+from gremlin.ui.input_item import AbstractActionWidget
+from gremlin.base_profile import AbstractFunctor, AbstractAction
+from gremlin.util import safe_read
-import gremlin.base_classes
-from gremlin.input_types import InputType
-import gremlin.ui.input_item
-
-
-class DescriptionActionWidget(gremlin.ui.input_item.AbstractActionWidget):
+class DescriptionActionWidget(AbstractActionWidget):
"""Widget for the description action."""
def __init__(self, action_data, parent=None):
super().__init__(action_data, parent=parent)
- assert(isinstance(action_data, DescriptionAction))
+ assert isinstance(action_data, DescriptionAction)
def _create_ui(self):
self.inner_layout = QtWidgets.QHBoxLayout()
self.label = QtWidgets.QLabel("Action description")
self.description = QtWidgets.QLineEdit()
- #self.description.setReadOnly(self.action_data.descriptionReadOnly)
+ # self.description.setReadOnly(self.action_data.descriptionReadOnly)
self.description.textChanged.connect(self._update_description)
self.inner_layout.addWidget(self.label)
self.inner_layout.addWidget(self.description)
@@ -45,30 +44,28 @@ def _create_ui(self):
def _populate_ui(self):
self.description.setText(self.action_data.description)
- #self.description.setReadOnly(self.action_data.)
+ # self.description.setReadOnly(self.action_data.)
def _update_description(self, value):
self.action_data.description = value
-class DescriptionActionFunctor(gremlin.base_profile.AbstractFunctor):
-
- def __init__(self, action, parent = None):
+class DescriptionActionFunctor(AbstractFunctor):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
return True
-class DescriptionAction(gremlin.base_profile.AbstractAction):
-
+class DescriptionAction(AbstractAction):
"""Action for adding a description to a set of actions."""
name = "Description"
tag = "description"
default_button_activation = (True, False)
-
+
# override allowed input types if different from default
# input_types = [
# InputType.JoystickAxis,
@@ -91,10 +88,8 @@ def icon(self):
def requires_virtual_button(self):
return False
- def _parse_xml(self, node, data = None):
- self.description = gremlin.profile.safe_read(
- node, "description", str, ""
- )
+ def _parse_xml(self, node, data=None):
+ self.description = safe_read(node, "description", str, "")
def _generate_xml(self):
node = ElementTree.Element("description")
diff --git a/action_plugins/gated_axis/__init__.py b/action_plugins/gated_axis/__init__.py
index 80e014cf..aa0e0f53 100644
--- a/action_plugins/gated_axis/__init__.py
+++ b/action_plugins/gated_axis/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,8 +16,8 @@
# along with this program. If not, see .
from __future__ import annotations
-import os
-from PySide6 import QtWidgets, QtCore
+from PySide6 import QtWidgets
+# noinspection PyPep8Naming
from lxml import etree as ElementTree
import gremlin.base_profile
@@ -30,37 +30,31 @@
syslog = logging.getLogger("system")
-class GatedAxisWidget(gremlin.ui.input_item.AbstractActionWidget):
+class GatedAxisWidget(gremlin.ui.input_item.AbstractActionWidget):
"""Widget associated with the action of switching to the previous mode."""
def __init__(self, action_data, parent=None):
self.action_data = action_data
self.gate_widget = None
super().__init__(action_data, parent=parent)
-
-
-
def _create_ui(self):
-
self.container_widget = QtWidgets.QWidget()
self.container_layout = QtWidgets.QVBoxLayout(self.container_widget)
- self.container_widget.setContentsMargins(0,0,0,0)
+ self.container_widget.setContentsMargins(0, 0, 0, 0)
- self.gate_widget = gremlin.gated_handler.GatedAxisWidget(action_data = self.action_data,
- show_configuration=False,
- parent=self
- )
- #cache.register(self.action_data, widget)
+ self.gate_widget = gremlin.gated_handler.GatedAxisWidget(
+ action_data=self.action_data, show_configuration=False, parent=self
+ )
+ # cache.register(self.action_data, widget)
self.main_layout.addWidget(self.gate_widget)
-
def _populate_ui(self):
pass
def _cleanup_ui(self):
- ''' cleanup the UI and widget hooks '''
+ """cleanup the UI and widget hooks"""
if self.gate_widget:
self.gate_widget.unhook()
@@ -68,40 +62,33 @@ def _cleanup_ui(self):
self.gate_widget.deleteLater()
self.gate_widget = None
-
-
class GatedAxisFunctor(gremlin.base_profile.AbstractContainerActionFunctor):
-
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
- self.manual_callback = True # indicate this functor only uses manual callbacks
+ self.manual_callback = True # indicate this functor only uses manual callbacks
# def profile_start(self):
# ''' register the gated functor'''
# #self.action_data.gate_data.setActionId(self.action_data.id)
# pass
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
# all the work happens in the gate widget hook function - nothing to do
- #self.action_data.gate_data.process_event(event, value)
- #syslog.info("Gated Axis: trigger")
- return True # prevent child from executing
+ # self.action_data.gate_data.process_event(event, value)
+ # syslog.info("Gated Axis: trigger")
+ return True # prevent child from executing
-class GatedAxis(gremlin.base_profile.AbstractAction):
- """ action data for the GatedAxis action """
+class GatedAxis(gremlin.base_profile.AbstractAction):
+ """action data for the GatedAxis action"""
name = "Gated Axis"
tag = "gated-axis"
default_button_activation = (True, False)
# override default allowed input types here if not all
- input_types = [
- InputType.JoystickAxis,
- InputType.OpenSoundControl,
- InputType.Midi
- ]
+ input_types = [InputType.JoystickAxis, InputType.OpenSoundControl, InputType.Midi]
functor = GatedAxisFunctor
widget = GatedAxisWidget
@@ -109,15 +96,17 @@ class GatedAxis(gremlin.base_profile.AbstractAction):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
- self.singleton = True # this action can only appear once per input
+ self.singleton = True # this action can only appear once per input
# gate data
- gate_data = gremlin.gated_handler.GateData(profile_mode = gremlin.shared_state.current_mode, action_data=self)
+ gate_data = gremlin.gated_handler.GateData(
+ profile_mode=gremlin.shared_state.current_mode, action_data=self
+ )
self.gate_data = gate_data
self.gates = [gate_data]
def _cleanup(self):
- ''' clean ourselves up '''
+ """clean ourselves up"""
super()._cleanup()
if self.gates:
self.gates.clear()
@@ -125,27 +114,28 @@ def _cleanup(self):
self.gate_data.unhook()
self.gate_data = None
-
def icon(self):
return "ph.sliders"
def requires_virtual_button(self):
return False
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
# load gate data
import gremlin.util
-
+
gates = []
- gate_node = gremlin.util.get_xml_child(node,"gates")
+ gate_node = gremlin.util.get_xml_child(node, "gates")
profile_mode = gremlin.util.get_xml_mode(node)
if not profile_mode:
# paste operation
profile_mode = gremlin.shared_state.current_mode
- if not gate_node is None:
+ if gate_node is not None:
for child in gate_node:
- gate_data = gremlin.gated_handler.GateData(profile_mode, action_data = self)
-
+ gate_data = gremlin.gated_handler.GateData(
+ profile_mode, action_data=self
+ )
+
gate_data.from_xml(child, data)
gates.append(gate_data)
@@ -154,7 +144,7 @@ def _parse_xml(self, node, data = None):
self.gate_data = gates[0]
def _generate_xml(self):
- # save gate data
+ # save gate data
node = ElementTree.Element(GatedAxis.tag)
if self.gates:
node_gate = ElementTree.SubElement(node, "gates")
diff --git a/action_plugins/macro/__init__.py b/action_plugins/macro/__init__.py
index 41dfa286..deeba637 100644
--- a/action_plugins/macro/__init__.py
+++ b/action_plugins/macro/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,35 +16,28 @@
# along with this program. If not, see .
-import collections
-import logging
-import os
-import pickle
-import time
-from PySide6 import QtCore, QtGui, QtWidgets
from lxml import etree as ElementTree
-
-from PySide6.QtGui import QIcon
-
+import logging
import gremlin.base_profile
-from gremlin.input_types import InputType
import gremlin.keyboard
import gremlin.macro
+from gremlin.input_types import InputType
+from gremlin.macro_handler import MacroWidget
from gremlin.profile import safe_format, safe_read, parse_guid, write_guid
import gremlin.ui.input_item
import gremlin.input_devices
from gremlin.input_devices import VjoyAction
from gremlin.keyboard import key_from_code, key_from_name
import gremlin.types
-from gremlin.macro_handler import *
+#from gremlin.macro_handler import *
syslog = logging.getLogger("system")
-class MacroFunctor(gremlin.base_profile.AbstractFunctor):
+class MacroFunctor(gremlin.base_profile.AbstractFunctor):
manager = gremlin.macro.MacroManager()
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
self.macro = gremlin.macro.Macro()
for seq in action.sequence:
@@ -52,18 +45,16 @@ def __init__(self, action, parent = None):
self.macro.exclusive = action.exclusive
self.macro.repeat = action.repeat
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
MacroFunctor.manager.queue_macro(self.macro)
if isinstance(self.macro.repeat, gremlin.macro.HoldRepeat):
gremlin.input_devices.ButtonReleaseActions().register_callback(
- lambda: MacroFunctor.manager.terminate_macro(self.macro),
- event
+ lambda: MacroFunctor.manager.terminate_macro(self.macro), event
)
return True
class Macro(gremlin.base_profile.AbstractAction):
-
"""Represents a macro action."""
name = "Macro"
@@ -95,21 +86,18 @@ def __init__(self, parent):
self.force_remote = False
def display_name(self):
- ''' returns a display string for the current configuration '''
- #TODO: build something more meaningful
- return f"Macro sequence"
+ """returns a display string for the current configuration"""
+ # TODO: build something more meaningful
+ return "Macro sequence"
def icon(self):
return "ei.cogs"
- #return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
+ # return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
def requires_virtual_button(self):
- return self.get_input_type() in [
- InputType.JoystickAxis,
- InputType.JoystickHat
- ]
+ return self.get_input_type() in [InputType.JoystickAxis, InputType.JoystickHat]
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Parses the XML node corresponding to a macro action.
:param node the XML node to parse.
@@ -135,9 +123,7 @@ def _parse_xml(self, node, data = None):
elif repeat_type == "hold":
self.repeat = gremlin.macro.HoldRepeat()
else:
- syslog.warning(
- f"Invalid macro repeat type: {repeat_type}"
- )
+ syslog.warning(f"Invalid macro repeat type: {repeat_type}")
if self.repeat:
self.repeat.from_xml(child, data)
@@ -147,9 +133,7 @@ def _parse_xml(self, node, data = None):
if child.tag == "joystick":
joy_action = gremlin.macro.JoystickAction(
parse_guid(child.get("device-guid")),
- InputType.to_enum(
- safe_read(child, "input-type")
- ),
+ InputType.to_enum(safe_read(child, "input-type")),
safe_read(child, "input-id", int),
safe_read(child, "value"),
)
@@ -159,40 +143,37 @@ def _parse_xml(self, node, data = None):
key_action = gremlin.macro.KeyAction(
key_from_code(
int(child.get("scan-code")),
- gremlin.profile.parse_bool(child.get("extended"))
+ gremlin.profile.parse_bool(child.get("extended")),
),
- gremlin.profile.parse_bool(child.get("press"))
+ gremlin.profile.parse_bool(child.get("press")),
)
self.sequence.append(key_action)
elif child.tag == "mouse":
mouse_action = gremlin.macro.MouseButtonAction(
gremlin.types.MouseButton(safe_read(child, "button", int)),
- gremlin.profile.parse_bool(child.get("press"))
+ gremlin.profile.parse_bool(child.get("press")),
)
self.sequence.append(mouse_action)
elif child.tag == "mouse-motion":
mouse_motion = gremlin.macro.MouseMotionAction(
- safe_read(child, "dx", int, 0),
- safe_read(child, "dy", int, 0)
+ safe_read(child, "dx", int, 0), safe_read(child, "dy", int, 0)
)
self.sequence.append(mouse_motion)
elif child.tag == "pause":
- self.sequence.append (
+ self.sequence.append(
gremlin.macro.PauseAction(
- float(child.get("duration")),
- safe_read(child, "duration_max", float, 0),
- gremlin.profile.parse_bool(child.get("is_random"))
- )
+ float(child.get("duration")),
+ safe_read(child, "duration_max", float, 0),
+ gremlin.profile.parse_bool(child.get("is_random")),
+ )
)
elif child.tag == "vjoy":
vjoy_action = gremlin.macro.VJoyMacroAction(
safe_read(child, "vjoy-id", int),
- InputType.to_enum(
- safe_read(child, "input-type")
- ),
+ InputType.to_enum(safe_read(child, "input-type")),
safe_read(child, "input-id", int),
safe_read(child, "value"),
- safe_read(child, "axis-type", str, "absolute")
+ safe_read(child, "axis-type", str, "absolute"),
)
self._str_to_joy_value(vjoy_action)
self.sequence.append(vjoy_action)
@@ -203,7 +184,6 @@ def _parse_xml(self, node, data = None):
remote_control_action.command = VjoyAction.from_string(cmd)
self.sequence.append(remote_control_action)
-
def _generate_xml(self):
"""Generates a XML node corresponding to this object.
@@ -220,7 +200,6 @@ def _generate_xml(self):
prop_node = ElementTree.Element("force_remote")
properties.append(prop_node)
-
node.append(properties)
action_list = ElementTree.Element("actions")
@@ -228,10 +207,7 @@ def _generate_xml(self):
if isinstance(entry, gremlin.macro.JoystickAction):
joy_node = ElementTree.Element("joystick")
joy_node.set("device-guid", write_guid(entry.device_guid))
- joy_node.set(
- "input-type",
- InputType.to_string(entry.input_type)
- )
+ joy_node.set("input-type", InputType.to_string(entry.input_type))
joy_node.set("input-id", str(entry.input_id))
joy_node.set("value", self._joy_value_to_str(entry))
action_list.append(joy_node)
@@ -260,10 +236,7 @@ def _generate_xml(self):
elif isinstance(entry, gremlin.macro.VJoyMacroAction):
vjoy_node = ElementTree.Element("vjoy")
vjoy_node.set("vjoy-id", str(entry.vjoy_id))
- vjoy_node.set(
- "input-type",
- InputType.to_string(entry.input_type)
- )
+ vjoy_node.set("input-type", InputType.to_string(entry.input_type))
vjoy_node.set("input-id", str(entry.input_id))
vjoy_node.set("value", self._joy_value_to_str(entry))
if entry.input_type == InputType.JoystickAxis:
@@ -271,7 +244,7 @@ def _generate_xml(self):
action_list.append(vjoy_node)
elif isinstance(entry, gremlin.macro.RemoteControlAction):
action_node = ElementTree.Element("remote_control")
- action_node.set("command",entry.command.name)
+ action_node.set("command", entry.command.name)
action_list.append(action_node)
node.append(action_list)
diff --git a/action_plugins/map_to_gamepad/__init__.py b/action_plugins/map_to_gamepad/__init__.py
index 21320298..4ac81528 100644
--- a/action_plugins/map_to_gamepad/__init__.py
+++ b/action_plugins/map_to_gamepad/__init__.py
@@ -17,8 +17,6 @@
import logging
-import math
-import os
from lxml import etree as ElementTree
from PySide6 import QtCore, QtWidgets
@@ -28,7 +26,7 @@
import gremlin.event_handler
from gremlin.input_types import InputType
-from gremlin.profile import read_bool, safe_read, safe_format
+from gremlin.profile import safe_read
from gremlin.util import rad2deg
import gremlin.ui.ui_common
import gremlin.ui.input_item
@@ -39,20 +37,15 @@
from gremlin.input_devices import ButtonReleaseActions
-
-# import vigem.vigem_gamepad as vg
+import vigem.vigem_gamepad as vg
import vigem.vigem_commons as vc
-from enum import Enum, auto
-
import gremlin.util
-from gremlin.types import GamePadOutput
syslog = logging.getLogger("system")
class MapToGamepadWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""UI widget for mapping inputs to mouse motion or buttons."""
def __init__(self, action_data, parent=None):
@@ -72,20 +65,20 @@ def _create_ui(self):
self.output_widget = QtWidgets.QWidget()
self.output_layout = QtWidgets.QHBoxLayout(self.output_widget)
-
+
self.device_selector = gremlin.ui.ui_common.NoWheelComboBox()
self.output_selector = gremlin.ui.ui_common.NoWheelComboBox()
-
+
self.output_layout.addWidget(QtWidgets.QLabel("Device:"))
self.output_layout.addWidget(self.device_selector)
self.output_layout.addWidget(QtWidgets.QLabel("Output:"))
self.output_layout.addWidget(self.output_selector)
self.output_layout.addStretch()
-
+
self.output_selector.currentIndexChanged.connect(self._output_mode_changed)
self.device_selector.currentIndexChanged.connect(self._device_changed)
- #self.output_widget.setContentsMargins(0,0,0,0)
- self.output_layout.setContentsMargins(0,0,0,0)
+ # self.output_widget.setContentsMargins(0,0,0,0)
+ self.output_layout.setContentsMargins(0, 0, 0, 0)
self.main_layout.addWidget(self.output_widget)
@@ -97,21 +90,32 @@ def _populate_ui(self):
with QtCore.QSignalBlocker(self.device_selector):
self.device_selector.clear()
for index, device in enumerate(devices):
- self.device_selector.addItem(f"Controller (XBOX 360 For Windows) [{index+1}]", index)
-
+ self.device_selector.addItem(
+ f"Controller (XBOX 360 For Windows) [{index+1}]", index
+ )
with QtCore.QSignalBlocker(self.output_selector):
self.output_selector.clear()
if self.action_data.get_input_type() == InputType.JoystickAxis:
# axis or trigger
- self.output_selector.addItem("Left Stick X Axis", GamePadOutput.LeftStickX)
- self.output_selector.addItem("Left Stick Y Axis", GamePadOutput.LeftStickY)
- self.output_selector.addItem("Right Stick X Axis", GamePadOutput.RightStickX)
- self.output_selector.addItem("Right Stick Y Axis", GamePadOutput.RightStickY)
+ self.output_selector.addItem(
+ "Left Stick X Axis", GamePadOutput.LeftStickX
+ )
+ self.output_selector.addItem(
+ "Left Stick Y Axis", GamePadOutput.LeftStickY
+ )
+ self.output_selector.addItem(
+ "Right Stick X Axis", GamePadOutput.RightStickX
+ )
+ self.output_selector.addItem(
+ "Right Stick Y Axis", GamePadOutput.RightStickY
+ )
self.output_selector.addItem("Left Trigger", GamePadOutput.LeftTrigger)
- self.output_selector.addItem("Right Trigger", GamePadOutput.RightTrigger)
+ self.output_selector.addItem(
+ "Right Trigger", GamePadOutput.RightTrigger
+ )
else:
- #button
+ # button
self.output_selector.addItem("Button A", GamePadOutput.ButtonA)
self.output_selector.addItem("Button B", GamePadOutput.ButtonB)
self.output_selector.addItem("Button X", GamePadOutput.ButtonX)
@@ -119,14 +123,30 @@ def _populate_ui(self):
self.output_selector.addItem("Button Start", GamePadOutput.ButtonStart)
self.output_selector.addItem("Button Back", GamePadOutput.ButtonBack)
self.output_selector.addItem("Button Guide", GamePadOutput.ButtonGuide)
- self.output_selector.addItem("Button Left Thumb", GamePadOutput.ButtonThumbLeft)
- self.output_selector.addItem("Button Right Thumb", GamePadOutput.ButtonThumbRight)
- self.output_selector.addItem("Button Shoulder Left", GamePadOutput.ButtonShoulderLeft)
- self.output_selector.addItem("Button Shoulder Right", GamePadOutput.ButtonShoulderRight)
- self.output_selector.addItem("Button DPad Left", GamePadOutput.ButtonDpadLeft)
- self.output_selector.addItem("Button DPad Right", GamePadOutput.ButtonDpadRight)
- self.output_selector.addItem("Button DPad Up", GamePadOutput.ButtonDpadUp)
- self.output_selector.addItem("Button DPad Down", GamePadOutput.ButtonDpadDown)
+ self.output_selector.addItem(
+ "Button Left Thumb", GamePadOutput.ButtonThumbLeft
+ )
+ self.output_selector.addItem(
+ "Button Right Thumb", GamePadOutput.ButtonThumbRight
+ )
+ self.output_selector.addItem(
+ "Button Shoulder Left", GamePadOutput.ButtonShoulderLeft
+ )
+ self.output_selector.addItem(
+ "Button Shoulder Right", GamePadOutput.ButtonShoulderRight
+ )
+ self.output_selector.addItem(
+ "Button DPad Left", GamePadOutput.ButtonDpadLeft
+ )
+ self.output_selector.addItem(
+ "Button DPad Right", GamePadOutput.ButtonDpadRight
+ )
+ self.output_selector.addItem(
+ "Button DPad Up", GamePadOutput.ButtonDpadUp
+ )
+ self.output_selector.addItem(
+ "Button DPad Down", GamePadOutput.ButtonDpadDown
+ )
index = self.device_selector.findData(self.action_data.device_index)
if index != -1:
@@ -135,7 +155,6 @@ def _populate_ui(self):
# pick the first entry
self.device_selector.setCurrentIndex(0)
self.action_data.device_index = 0
-
output_id = self.action_data.output_mode
output_index = self.output_selector.findData(output_id)
@@ -143,24 +162,24 @@ def _populate_ui(self):
if index != -1:
self.output_selector.setCurrentIndex(output_index)
-
@QtCore.Slot()
def _gamepad_count_changed(self):
- ''' number of devices changed '''
+ """number of devices changed"""
self._populate_ui()
@QtCore.Slot()
def _output_mode_changed(self):
self.action_data.output_mode = self.output_selector.currentData()
verbose = gremlin.config.Configuration().verbose
- if verbose: syslog.info(f"OUTPUT MODE: changed to {self.action_data.output_mode}")
+ if verbose:
+ syslog.info(f"OUTPUT MODE: changed to {self.action_data.output_mode}")
@QtCore.Slot()
def _device_changed(self):
self.action_data.device_index = self.device_selector.currentData()
-class MapToGamepadFunctor(gremlin.base_profile.AbstractFunctor):
+class MapToGamepadFunctor(gremlin.base_profile.AbstractFunctor):
"""Implements the functionality required to move a mouse cursor.
This moves the mouse cursor by issuing relative motion commands. This is
@@ -168,7 +187,7 @@ class MapToGamepadFunctor(gremlin.base_profile.AbstractFunctor):
properly with a single input, at least partially.
"""
- def __init__(self, action_data, parent = None):
+ def __init__(self, action_data, parent=None):
"""Creates a new functor with the provided data.
:param action contains parameters to use with the functor
@@ -176,121 +195,148 @@ def __init__(self, action_data, parent = None):
super().__init__(action_data, parent)
self.action_data = action_data
- def process_event(self, event, value, extra_data = None):
-
+ def process_event(self, event, value, extra_data=None):
verbose = gremlin.config.Configuration().verbose_mode_outputs
- if verbose: syslog.error(f"VIGEM: event: {str(event)}")
+ if verbose:
+ syslog.error(f"VIGEM: event: {str(event)}")
-
(is_local, is_remote) = input_devices.remote_state.state
if event.force_remote:
# force remote mode on if specified in the event
- is_remote = True
is_local = False
if is_local:
-
vigem = gremlin.gamepad_handling.getGamepad(self.action_data.device_index)
if vigem is None:
- if verbose: syslog.error(f"VIGEM: no device found index: {self.action_data.device_index}")
+ if verbose:
+ syslog.error(
+ f"VIGEM: no device found index: {self.action_data.device_index}"
+ )
return False
-
-
+
output_mode = self.action_data.output_mode
if output_mode == GamePadOutput.NotSet:
- return True # nothing to do
- # vigem : vg.VX360Gamepad
+ return True # nothing to do
+ vigem : vg.VX360Gamepad = vg.VX360Gamepad()
if event.is_axis:
if is_local:
vscaled = value.current
if output_mode == GamePadOutput.LeftStickX:
- if verbose: syslog.error(f"VIGEM: left X: {vscaled}")
+ if verbose:
+ syslog.error(f"VIGEM: left X: {vscaled}")
vigem.left_joystick_float_x(vscaled)
elif output_mode == GamePadOutput.LeftStickY:
- if verbose: syslog.error(f"VIGEM: left Y: {vscaled}")
+ if verbose:
+ syslog.error(f"VIGEM: left Y: {vscaled}")
vigem.left_joystick_float_y(vscaled)
if output_mode == GamePadOutput.RightStickX:
- if verbose: syslog.error(f"VIGEM: right X: {vscaled}")
+ if verbose:
+ syslog.error(f"VIGEM: right X: {vscaled}")
vigem.right_joystick_float_x(vscaled)
elif output_mode == GamePadOutput.RightStickY:
- if verbose: syslog.error(f"VIGEM: right Y: {vscaled}")
+ if verbose:
+ syslog.error(f"VIGEM: right Y: {vscaled}")
vigem.right_joystick_float_y(vscaled)
if output_mode == GamePadOutput.LeftTrigger:
- vscaled = gremlin.util.scale_to_range(value.current,target_min=0.0, target_max=1.0)
- if verbose: syslog.error(f"VIGEM: left trigger: {vscaled}")
+ vscaled = gremlin.util.scale_to_range(
+ value.current, target_min=0.0, target_max=1.0
+ )
+ if verbose:
+ syslog.error(f"VIGEM: left trigger: {vscaled}")
vigem.left_trigger_float(vscaled)
if output_mode == GamePadOutput.RightTrigger:
- if verbose: syslog.error(f"VIGEM: right trigger: {vscaled}")
- vscaled = gremlin.util.scale_to_range(value.current,target_min=0.0, target_max=1.0)
+ if verbose:
+ syslog.error(f"VIGEM: right trigger: {vscaled}")
+ vscaled = gremlin.util.scale_to_range(
+ value.current, target_min=0.0, target_max=1.0
+ )
vigem.right_trigger_float(vscaled)
else:
# remote
- input_devices.remote_client.send_gamepad_axis(self.action_data.device_index, output_mode, vscaled)
+ vscaled = gremlin.util.scale_to_range(
+ value.current, target_min=0.0, target_max=1.0
+ )
+ input_devices.remote_client.send_gamepad_axis(
+ self.action_data.device_index, output_mode, vscaled
+ )
return True
- else: # momentary
+ else: # momentary
if output_mode == GamePadOutput.ButtonA:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_A
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_A
elif output_mode == GamePadOutput.ButtonB:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_B
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_B
elif output_mode == GamePadOutput.ButtonX:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_X
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_X
elif output_mode == GamePadOutput.ButtonY:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_Y
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_Y
elif output_mode == GamePadOutput.ButtonStart:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_START
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_START
elif output_mode == GamePadOutput.ButtonBack:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_BACK
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_BACK
elif output_mode == GamePadOutput.ButtonGuide:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_GUIDE
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_GUIDE
elif output_mode == GamePadOutput.ButtonThumbRight:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_RIGHT_THUMB
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_RIGHT_THUMB
elif output_mode == GamePadOutput.ButtonThumbLeft:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_LEFT_THUMB
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_LEFT_THUMB
elif output_mode == GamePadOutput.ButtonShoulderLeft:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_LEFT_SHOULDER
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_LEFT_SHOULDER
elif output_mode == GamePadOutput.ButtonShoulderRight:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_RIGHT_SHOULDER
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_RIGHT_SHOULDER
elif output_mode == GamePadOutput.ButtonDpadDown:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_DPAD_DOWN
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_DPAD_DOWN
elif output_mode == GamePadOutput.ButtonDpadUp:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_DPAD_UP
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_DPAD_UP
elif output_mode == GamePadOutput.ButtonDpadLeft:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_DPAD_LEFT
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_DPAD_LEFT
elif output_mode == GamePadOutput.ButtonDpadRight:
- button =vc.XUSB_BUTTON.XUSB_GAMEPAD_DPAD_RIGHT
+ button = vc.XUSB_BUTTON.XUSB_GAMEPAD_DPAD_RIGHT
else:
button = None
if button is not None:
is_pressed = event.is_pressed
if is_pressed:
- auto_release = event.event_type in [InputType.Keyboard, InputType.KeyboardLatched, InputType.Midi, InputType.OpenSoundControl]
+ auto_release = event.event_type in [
+ InputType.Keyboard,
+ InputType.KeyboardLatched,
+ InputType.Midi,
+ InputType.OpenSoundControl,
+ ]
if auto_release:
- if verbose: syslog.info(f"VjoyRemap: autorelease enabled for {str(event)}")
- event_release = event.clone()
+ if verbose:
+ syslog.info(
+ f"VjoyRemap: autorelease enabled for {str(event)}"
+ )
+ event_release = event.clone()
event_release.is_pressed = False
- callback = lambda : self.process_event(event_release, value)
- ButtonReleaseActions().register_callback(callback, event_release)
+ def callback():
+ return self.process_event(event_release, value)
+ ButtonReleaseActions().register_callback(
+ callback, event_release
+ )
if is_local:
if is_pressed:
- if verbose: syslog.error(f"VIGEM: button {button.name}: press")
+ if verbose:
+ syslog.error(f"VIGEM: button {button.name}: press")
vigem.press_button(button)
else:
- if verbose: syslog.error(f"VIGEM: button {button.name}: release")
+ if verbose:
+ syslog.error(f"VIGEM: button {button.name}: release")
vigem.release_button(button)
else:
- input_devices.remote_client.send_gamepad_button(self.action_data.device_index, button, value.is_pressed)
+ input_devices.remote_client.send_gamepad_button(
+ self.action_data.device_index, button, value.is_pressed
+ )
return True
-
- vigem.update() # sends the data to the controller
+ vigem.update() # sends the data to the controller
return True
class MapToGamepad(gremlin.base_profile.AbstractAction):
-
"""Action data for the map to mouse action.
Map to mouse allows controlling of the mouse cursor using either a joystick
@@ -299,7 +345,6 @@ class MapToGamepad(gremlin.base_profile.AbstractAction):
name = "Map to GamePad"
tag = "map-to-gamepad"
-
default_button_activation = (True, True)
# override allowed input types if different from default
@@ -327,22 +372,21 @@ def __init__(self, parent):
@property
def output_mode(self) -> GamePadOutput:
return self._output_mode
+
@output_mode.setter
- def output_mode(self, value : GamePadOutput):
+ def output_mode(self, value: GamePadOutput):
self._output_mode = value
-
def display_name(self):
- ''' returns a display string for the current configuration '''
+ """returns a display string for the current configuration"""
return f"[{GamePadOutput.to_display_name(self.output_mode)}]"
-
+
def icon(self):
"""Returns the icon to use for this action.
:return icon representing this action
"""
return "fa6s.gamepad"
-
def requires_virtual_button(self):
"""Returns whether or not an activation condition is needed.
@@ -355,7 +399,7 @@ def requires_virtual_button(self):
# return True
return False
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Reads the contents of an XML node to populate this instance.
:param node the node whose content should be used to populate this
@@ -366,9 +410,7 @@ def _parse_xml(self, node, data = None):
mode = node.get("mode")
self.output_mode = GamePadOutput.to_enum(mode)
- self.device_index = safe_read(node,"device_index", int, 0)
-
-
+ self.device_index = safe_read(node, "device_index", int, 0)
def _generate_xml(self):
"""Returns an XML node containing this instance's information.
@@ -378,7 +420,7 @@ def _generate_xml(self):
node = ElementTree.Element(self.tag)
mode = GamePadOutput.to_string(self.output_mode)
node.set("mode", mode)
- node.set("device_index",str(self.device_index))
+ node.set("device_index", str(self.device_index))
return node
def _is_valid(self):
@@ -388,6 +430,7 @@ def _is_valid(self):
"""
return gremlin.gamepad_handling.gamepadAvailable()
+
version = 1
name = "map-to-gamepad"
create = MapToGamepad
diff --git a/action_plugins/map_to_keyboard/__init__.py b/action_plugins/map_to_keyboard/__init__.py
index 91303302..19cf79f6 100644
--- a/action_plugins/map_to_keyboard/__init__.py
+++ b/action_plugins/map_to_keyboard/__init__.py
@@ -16,7 +16,6 @@
# along with this program. If not, see .
-import os
from lxml import etree as ElementTree
from PySide6 import QtWidgets, QtGui
@@ -29,8 +28,8 @@
import gremlin.ui.input_item
from gremlin.keyboard import key_from_code
-class MapToKeyboardWidget(gremlin.ui.input_item.AbstractActionWidget):
+class MapToKeyboardWidget(gremlin.ui.input_item.AbstractActionWidget):
"""UI widget for mapping inputs to keyboard key combinations."""
def __init__(self, action_data, parent=None):
@@ -52,8 +51,16 @@ def _create_ui(self):
self.main_layout.addWidget(self.record_button)
warning_color = gremlin.ui.ui_common.Color.warningColor()
- warning_widget = gremlin.ui.ui_common.QIconLabel("ph.shield-warning-fill",use_qta=True,icon_color=QtGui.QColor(warning_color),text="Legacy mapper - consider using Map to Keyboard Ex for additional functionality", use_wrap=False)
- warning_container, warning_layout = gremlin.ui.ui_common.getHContainer(warning_widget)
+ warning_widget = gremlin.ui.ui_common.QIconLabel(
+ "ph.shield-warning-fill",
+ use_qta=True,
+ icon_color=QtGui.QColor(warning_color),
+ text="Legacy mapper - consider using Map to Keyboard Ex for additional functionality",
+ use_wrap=False,
+ )
+ warning_container, warning_layout = gremlin.ui.ui_common.getHContainer(
+ warning_widget
+ )
self.main_layout.addWidget(warning_container)
def _populate_ui(self):
@@ -61,7 +68,7 @@ def _populate_ui(self):
text = "Current key combination: "
names = []
for key in self.action_data.keys:
- names.append(key_from_code(key[0],key[1]).name)
+ names.append(key_from_code(key[0], key[1]).name)
text += " + ".join(names)
self.key_combination.setText(text)
@@ -71,20 +78,14 @@ def _update_keys(self, keys):
:param keys the keys to use in the key combination
"""
- self.action_data.keys = [
- (key.scan_code, key.is_extended) for key in keys
- ]
+ self.action_data.keys = [(key.scan_code, key.is_extended) for key in keys]
self.action_modified.emit()
def _record_keys_cb(self):
"""Prompts the user to press the desired key combination."""
-
-
self.button_press_dialog = gremlin.ui.ui_common.InputListenerWidget(
- [InputType.Keyboard],
- return_kb_event=False,
- multi_keys=True
+ [InputType.Keyboard], return_kb_event=False, multi_keys=True
)
self.button_press_dialog.item_selected.connect(self._update_keys)
@@ -99,14 +100,13 @@ def _record_keys_cb(self):
int(geom.x() + geom.width() / 2 - 150),
int(geom.y() + geom.height() / 2 - 75),
300,
- 150
+ 150,
)
self.button_press_dialog.show()
class MapToKeyboardFunctor(gremlin.base_profile.AbstractFunctor):
-
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
self.press = gremlin.macro.Macro()
self.needs_auto_release = action.input_is_axis()
@@ -118,18 +118,18 @@ def __init__(self, action, parent = None):
for key in reversed(action.keys):
self.release.release(key_from_code(key[0], key[1]))
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
if value.current:
gremlin.macro.MacroManager().queue_macro(self.press)
# print("press")
if self.needs_auto_release:
# print ("auto release event ")
- event_release = event.clone()
+ event_release = event.clone()
event_release.is_pressed = False
ButtonReleaseActions().register_callback(
lambda: gremlin.macro.MacroManager().queue_macro(self.release),
- event_release
+ event_release,
)
else:
if not self.needs_auto_release:
@@ -139,7 +139,6 @@ def process_event(self, event, value, extra_data = None):
class MapToKeyboard(gremlin.base_profile.AbstractAction):
-
"""Action data for the map to keyboard action.
Map to keyboard presses and releases a set of keys in sync with another
@@ -176,7 +175,7 @@ def icon(self):
:return icon representing this action
"""
return "fa6s.keyboard"
- #return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
+ # return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
def requires_virtual_button(self):
"""Returns whether or not an activation condition is needed.
@@ -184,12 +183,9 @@ def requires_virtual_button(self):
:return True if an activation condition is required for this particular
action instance, False otherwise
"""
- return self.get_input_type() in [
- InputType.JoystickAxis,
- InputType.JoystickHat
- ]
+ return self.get_input_type() in [InputType.JoystickAxis, InputType.JoystickHat]
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Reads the contents of an XML node to populate this instance.
:param node the node whose content should be used to populate this
@@ -198,10 +194,12 @@ def _parse_xml(self, node, data = None):
self.keys = []
for child in node.findall("key"):
- self.keys.append((
- int(child.get("scan-code")),
- gremlin.profile.parse_bool(child.get("extended"))
- ))
+ self.keys.append(
+ (
+ int(child.get("scan-code")),
+ gremlin.profile.parse_bool(child.get("extended")),
+ )
+ )
pass
@@ -224,16 +222,17 @@ def _is_valid(self):
:return True if the action is configured correctly, False otherwise
"""
return len(self.keys) > 0
-
+
def display_name(self):
- ''' friendly display name '''
+ """friendly display name"""
names = []
text = ""
for key in self.keys:
- names.append(key_from_code(key[0],key[1]).name)
+ names.append(key_from_code(key[0], key[1]).name)
text += " + ".join(names)
return text
+
version = 1
name = "map-to-keyboard"
create = MapToKeyboard
diff --git a/action_plugins/map_to_keyboard_ex/__init__.py b/action_plugins/map_to_keyboard_ex/__init__.py
index e09d8d7d..ab48e36f 100644
--- a/action_plugins/map_to_keyboard_ex/__init__.py
+++ b/action_plugins/map_to_keyboard_ex/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@
from PySide6 import QtWidgets, QtCore, QtGui
import gremlin.base_profile
-import gremlin.config
-import gremlin.config
import gremlin.config
import gremlin.event_handler
from gremlin.input_types import InputType
@@ -49,8 +47,8 @@
syslog = logging.getLogger("system")
-class MapToKeyboardExWidget(gremlin.ui.input_item.AbstractActionWidget):
+class MapToKeyboardExWidget(gremlin.ui.input_item.AbstractActionWidget):
"""UI widget for mapping inputs to keyboard key combinations - adds extra functionality to the base module ."""
def __init__(self, action_data, parent=None):
@@ -65,14 +63,12 @@ def __init__(self, action_data, parent=None):
def _create_ui(self):
"""Creates the UI components."""
-
self.key_combination = QtWidgets.QLabel("Current key combination:")
- self.key_combination_widget = QtWidgets.QWidget() # holds the keys
+ self.key_combination_widget = QtWidgets.QWidget() # holds the keys
self.key_combination_layout = QtWidgets.QHBoxLayout(self.key_combination_widget)
self.action_widget = QtWidgets.QWidget()
self.action_layout = QtWidgets.QHBoxLayout(self.action_widget)
-
self.record_button = QtWidgets.QPushButton("Listen")
self.record_button.clicked.connect(self._record_keys_cb)
@@ -81,7 +77,6 @@ def _create_ui(self):
self._options_layout = QtWidgets.QHBoxLayout()
self._options_widget.setLayout(self._options_layout)
-
self.rb_press = QtWidgets.QRadioButton("Press")
self.rb_release = QtWidgets.QRadioButton("Release")
self.rb_both = QtWidgets.QRadioButton("Pulse")
@@ -129,7 +124,6 @@ def _create_ui(self):
self.description_widget = QtWidgets.QLabel()
self.description_widget.setWordWrap(True)
-
self.rb_press.clicked.connect(self._mode_changed)
self.rb_release.clicked.connect(self._mode_changed)
self.rb_both.clicked.connect(self._mode_changed)
@@ -148,7 +142,6 @@ def _create_ui(self):
self._options_layout.addStretch(1)
-
self.delay_container_layout.addWidget(delay_label)
self.delay_container_layout.addWidget(self.delay_box)
self.delay_container_layout.addWidget(quarter_sec_button)
@@ -166,7 +159,6 @@ def _create_ui(self):
self.action_layout.addWidget(self.show_keyboard_widget)
self.action_layout.addStretch(1)
-
self.main_layout.addWidget(self.key_combination)
self.main_layout.addWidget(self.key_combination_widget)
self.main_layout.addWidget(self.action_widget)
@@ -174,15 +166,17 @@ def _create_ui(self):
self.main_layout.addWidget(self.delay_container_widget)
self.main_layout.addWidget(self.description_widget)
-
self.main_layout.addStretch()
- self._mode_changed() # update UI based on mode
+ self._mode_changed() # update UI based on mode
def _select_keys_cb(self):
- ''' display the keyboard input dialog '''
+ """display the keyboard input dialog"""
import gremlin.shared_state
+
gremlin.shared_state.push_suspend_ui_keyinput()
- self._keyboard_dialog = InputKeyboardDialog(sequence = self.action_data.keys, parent = self)
+ self._keyboard_dialog = InputKeyboardDialog(
+ sequence=self.action_data.keys, parent=self
+ )
self._keyboard_dialog.accepted.connect(self._keyboard_dialog_ok_cb)
self._keyboard_dialog.closed.connect(self._keyboard_dialog_closed_cb)
self._keyboard_dialog.setModal(True)
@@ -190,28 +184,28 @@ def _select_keys_cb(self):
def _keyboard_dialog_closed_cb(self):
import gremlin.shared_state
+
gremlin.shared_state.pop_suspend_ui_keyinput()
def _keyboard_dialog_ok_cb(self):
- ''' callled when the virtual dialog completes '''
+ """callled when the virtual dialog completes"""
# grab the new data
self.action_data.keys = gremlin.keyboard.sort_keys(self._keyboard_dialog.keys)
self.action_modified.emit()
gremlin.shared_state.pop_suspend_ui_keyinput()
-
def _populate_ui(self):
"""Populates the UI components."""
- #text = "Current key combination:"
+ # text = "Current key combination:"
# text += f"{self.action_data._get_display_keys()}"
- #self.key_combination.setText(text)
+ # self.key_combination.setText(text)
gremlin.util.clear_layout(self.key_combination_layout)
max_width = 32
background_color = gremlin.ui.ui_common.Color.keyBackgroundColor()
border_color = gremlin.ui.ui_common.Color.keyBorderColor()
-
+
for index, name in enumerate(self.action_data._get_display_keys(as_list=True)):
if index:
lbl = QtWidgets.QLabel("+")
@@ -222,22 +216,17 @@ def _populate_ui(self):
css_pad = ""
if w < max_width:
delta = (max_width - w) // 2
- css_pad = f" padding-left: {delta}px; padding-right:{delta}px;"
-
-
- lbl.setStyleSheet(f"background: {background_color}; border: 2px solid {border_color}; border-radius: 4px 8px;{css_pad}")
+ css_pad = f" padding-left: {delta}px; padding-right:{delta}px;"
+
+ lbl.setStyleSheet(
+ f"background: {background_color}; border: 2px solid {border_color}; border-radius: 4px 8px;{css_pad}"
+ )
lbl.setMinimumHeight(24)
lbl.setMinimumWidth(32)
self.key_combination_layout.addWidget(lbl)
self.key_combination_layout.addStretch()
-
-
-
-
-
-
def _update_keys(self, keys):
"""Updates the storage with a new set of keys.
@@ -259,7 +248,6 @@ def _update_keys(self, keys):
self.action_data.keys = gremlin.keyboard.sort_keys(data)
self.action_modified.emit()
-
def _mode_changed(self):
delay_enabled = False
autorepeat_visible = False
@@ -293,32 +281,23 @@ def _mode_changed(self):
def _delay_changed(self):
self.action_data.delay = self.delay_box.value()
-
def _autorepeat_changed(self):
self.action_data.autorepeat_delay = self.autorepeat_delay_box.value()
-
def _quarter_sec_delay(self):
self.delay_box.setValue(250)
-
def _half_sec_delay(self):
self.delay_box.setValue(500)
-
def _sec_delay(self):
self.delay_box.setValue(1000)
-
-
-
def _record_keys_cb(self):
"""Prompts the user to press the desired key combination."""
button_press_dialog = gremlin.ui.ui_common.InputListenerWidget(
- [InputType.Keyboard],
- return_kb_event=False,
- multi_keys=True
+ [InputType.Keyboard], return_kb_event=False, multi_keys=True
)
button_press_dialog.item_selected.connect(self._update_keys)
@@ -333,19 +312,18 @@ def _record_keys_cb(self):
int(geom.x() + geom.width() / 2 - 150),
int(geom.y() + geom.height() / 2 - 75),
300,
- 150
+ 150,
)
button_press_dialog.show()
class MapToKeyboardExFunctor(gremlin.base_profile.AbstractFunctor):
-
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_keyboard
- #verbose = True
+ # verbose = True
self.press = gremlin.macro.Macro()
self.needs_auto_release = True
@@ -358,7 +336,7 @@ def __init__(self, action, parent = None):
self._ar_running = False
self._ar_event = threading.Event()
self.has_keys = bool(action.keys)
- self.use_macros = False # true to use macros, false to send direct keys
+ self.use_macros = False # true to use macros, false to send direct keys
self._press_keys = []
self._release_keys = []
@@ -383,15 +361,19 @@ def __init__(self, action, parent = None):
self.delay_press_release = gremlin.macro.Macro()
# execute press/release with a delay before releasing (pulse)
- if verbose: syslog.info(f"DelayPressMacro:")
+ if verbose:
+ syslog.info("DelayPressMacro:")
for key in action.keys:
- if verbose: syslog.info(f"\tPress: {key}")
+ if verbose:
+ syslog.info(f"\tPress: {key}")
self.delay_press_release.press(key)
if self.delay > 0:
- if verbose: syslog.info(f"\tPause: {self.delay}")
+ if verbose:
+ syslog.info(f"\tPause: {self.delay}")
self.delay_press_release.pause(self.delay)
for key in reversed(action.keys):
- if verbose: syslog.info(f"\tRelease: {key}")
+ if verbose:
+ syslog.info(f"\tRelease: {key}")
self.delay_press_release.release(key)
# tell the time delay or release macros to inform us when they are done running
@@ -405,28 +387,26 @@ def __init__(self, action, parent = None):
# list of the macros we generate
self._macro_ids = set()
-
-
- def registerMacro(self, id : int):
+ def registerMacro(self, id: int):
self._macro_ids.add(id)
- def unregisterMacro(self, id : int):
+ def unregisterMacro(self, id: int):
if id in self._macro_ids:
self._macro_ids.remove(id)
- def isMacroOurs(self, id : int):
- ''' true if the macro is one of ours '''
+ def is_macro_ours(self, id: int):
+ """true if the macro is one of ours"""
return gremlin.macro.MacroManager()
@QtCore.Slot(int)
def _macro_handler(self, id):
- ''' called when a macro completes '''
- if self.isMacroOurs(id):
+ """called when a macro completes"""
+ if self.is_macro_ours(id):
self.unregisterMacro(id)
self.functor_complete.emit()
def _macro_completed(self):
- ''' called when a macro is done running '''
+ """called when a macro is done running"""
self.is_pressed = False
def profile_start(self):
@@ -448,25 +428,24 @@ def _autorepeat_clear(self):
# ensure the keys are released
gremlin.macro.MacroManager().queue_macro(self.release)
-
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_keyboard
-
+
if event.is_axis or value.current or event.is_pressed:
# joystick values or virtual button
# verbose = True
-
+
if self.mode == KeyboardOutputMode.Release:
if verbose:
- syslog.info(f"MapToKeyboardEx: release")
+ syslog.info("MapToKeyboardEx: release")
# kill off any auto-repeat as well
if not self.has_keys:
- # clear autorepeat if no keys are provided
+ # clear autorepeat if no keys are provided
if verbose:
- syslog.info(f"MapToKeyboardEx: clear autorepeat")
+ syslog.info("MapToKeyboardEx: clear autorepeat")
eh = gremlin.event_handler.EventListener()
- eh.autorepeat_clear.emit() # clear auto-repeats
+ eh.autorepeat_clear.emit() # clear auto-repeats
else:
self.is_pressed = False
id = gremlin.macro.MacroManager().queue_macro(self.release)
@@ -476,7 +455,7 @@ def process_event(self, event, value, extra_data = None):
if self.has_keys:
self.is_pressed = True
if verbose:
- syslog.info(f"MapToKeyboardEx: press")
+ syslog.info("MapToKeyboardEx: press")
id = gremlin.macro.MacroManager().queue_macro(self.press)
self.registerMacro(id)
@@ -485,8 +464,10 @@ def process_event(self, event, value, extra_data = None):
if self.has_keys:
if not self.is_pressed:
if verbose:
- syslog.info(f"MapToKeyboardEx: both/pulse")
- id = gremlin.macro.MacroManager().queue_macro(self.delay_press_release)
+ syslog.info("MapToKeyboardEx: both/pulse")
+ id = gremlin.macro.MacroManager().queue_macro(
+ self.delay_press_release
+ )
self.is_pressed = True
self.registerMacro(id)
@@ -499,30 +480,38 @@ def process_event(self, event, value, extra_data = None):
if event.is_pressed and not auto_release:
# press event
if verbose:
- syslog.info(f"MapToKeyboardEx: hold (press)")
+ syslog.info("MapToKeyboardEx: hold (press)")
if self.use_macros:
id = gremlin.macro.MacroManager().queue_macro(self.press)
self.registerMacro(id)
else:
# send direct
- key : gremlin.keyboard.Key
+ key: gremlin.keyboard.Key
for key in self._press_keys:
- if verbose: syslog.info(f"send key press: {key}")
+ if verbose:
+ syslog.info(f"send key press: {key}")
gremlin.keyboard.send_key_down(key)
- if event.is_pressed and auto_release:
+ if event.is_pressed and auto_release:
id = gremlin.macro.MacroManager().queue_macro(self.press)
self.registerMacro(id)
- callback = lambda : gremlin.macro.MacroManager().queue_macro(self.release)
+
+ def callback():
+ return gremlin.macro.MacroManager().queue_macro(
+ self.release
+ )
+
ButtonReleaseActions().register_callback(callback, event)
elif self.mode == KeyboardOutputMode.AutoRepeat:
# setup autorepeat thread
if verbose:
- syslog.info(f"MapToKeyboardEx: autorepeat")
+ syslog.info("MapToKeyboardEx: autorepeat")
if self.has_keys:
if self._ar_thread is None:
- self._ar_thread = threading.Thread(target=self._ar_execute, daemon=True)
+ self._ar_thread = threading.Thread(
+ target=self._ar_execute, daemon=True
+ )
self._ar_running = True
self._ar_event.clear()
self._ar_thread.start()
@@ -530,33 +519,32 @@ def process_event(self, event, value, extra_data = None):
else:
# release
if self.has_keys:
- if self.mode == KeyboardOutputMode.Hold:
- # release keys
- if self.use_macros:
- gremlin.macro.MacroManager().queue_macro(self.release)
- else:
- # not using macros
- for key in self._release_keys:
- if verbose: syslog.info(f"send key release: {key}")
- gremlin.keyboard.send_key_up(key)
+ if self.mode == KeyboardOutputMode.Hold:
+ # release keys
+ if self.use_macros:
+ gremlin.macro.MacroManager().queue_macro(self.release)
+ else:
+ # not using macros
+ for key in self._release_keys:
+ if verbose:
+ syslog.info(f"send key release: {key}")
+ gremlin.keyboard.send_key_up(key)
self._ar_running = False
self._ar_event.set()
if self._ar_thread is not None:
self._ar_thread.join()
self._ar_thread = None
-
- return True
-
+ return True
def _ar_execute(self):
- ''' autorepeat run thread '''
+ """autorepeat run thread"""
verbose = gremlin.config.Configuration().verbose_mode_keyboard
macro_mgr = gremlin.macro.MacroManager()
if verbose:
log_info("autorepeat start...")
- trigger_time = time.time()-1
+ trigger_time = time.time() - 1
while self._ar_running and not self._ar_event.is_set():
if time.time() > trigger_time:
macro_mgr.queue_macro(self.delay_press_release)
@@ -565,15 +553,9 @@ def _ar_execute(self):
if verbose:
log_info("autorepeat stop...")
macro_mgr.clear_queue()
-
-
-
-
-
class MapToKeyboardEx(gremlin.base_profile.AbstractAction):
-
"""Action data for the map to keyboard action.
Map to keyboard presses and releases a set of keys in sync with another
@@ -602,15 +584,20 @@ def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.keys = []
- self.mode = KeyboardOutputMode.Both # pulse by default
- #self._mode = KeyboardOutputMode.Both # pulse by default
+ self.mode = KeyboardOutputMode.Both # pulse by default
+ # self._mode = KeyboardOutputMode.Both # pulse by default
config = gremlin.config.Configuration()
- self._delay = config.last_keyboard_mapper_pulse_value # delay between make/break in milliseconds
- self._autorepeat_delay = config.last_keyboard_mapper_interval_value # delay between autorepeats in milliseconds
+ self._delay = (
+ config.last_keyboard_mapper_pulse_value
+ ) # delay between make/break in milliseconds
+ self._autorepeat_delay = (
+ config.last_keyboard_mapper_interval_value
+ ) # delay between autorepeats in milliseconds
@property
def delay(self):
return self._delay
+
@delay.setter
def delay(self, value):
if value < 0:
@@ -630,6 +617,7 @@ def delay(self, value):
@property
def autorepeat_delay(self):
return self._autorepeat_delay
+
@autorepeat_delay.setter
def autorepeat_delay(self, value):
if value < 0:
@@ -639,12 +627,11 @@ def autorepeat_delay(self, value):
def _update_config(self):
config = gremlin.config.Configuration()
-
- config.last_keyboard_mapper_interval_value =self.autorepeat_delay_box.value()
-
- def _get_display_keys(self, as_list = False):
- text = ''
+ config.last_keyboard_mapper_interval_value = self.autorepeat_delay_box.value()
+
+ def _get_display_keys(self, as_list=False):
+ text = ""
names = []
for code in self.keys:
@@ -667,14 +654,13 @@ def _get_display_keys(self, as_list = False):
def display_name(self):
return self._get_display_keys()
-
def icon(self):
"""Returns the icon to use for this action.
:return icon representing this action
"""
return "fa6s.keyboard"
- #return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
+ # return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
def requires_virtual_button(self):
"""Returns whether or not an activation condition is needed.
@@ -682,12 +668,9 @@ def requires_virtual_button(self):
:return True if an activation condition is required for this particular
action instance, False otherwise
"""
- return self.get_input_type() in [
- InputType.JoystickAxis,
- InputType.JoystickHat
- ]
+ return self.get_input_type() in [InputType.JoystickAxis, InputType.JoystickHat]
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Reads the contents of an XML node to populate this instance.
:param node the node whose content should be used to populate this
@@ -709,10 +692,12 @@ def _parse_xml(self, node, data = None):
self.mode = KeyboardOutputMode.AutoRepeat
if "delay" in node.attrib:
- self.delay = safe_read(node, "delay", int, 250) # delay in milliseconds
+ self.delay = safe_read(node, "delay", int, 250) # delay in milliseconds
if "interval" in node.attrib:
- self.autorepeat_delay = safe_read(node, "interval", int, 250) # delay in milliseconds
+ self.autorepeat_delay = safe_read(
+ node, "interval", int, 250
+ ) # delay in milliseconds
for child in node.findall("key"):
virtual_code = safe_read(child, "virtual-code", int, 0)
@@ -745,18 +730,20 @@ def _generate_xml(self):
elif self.mode == KeyboardOutputMode.AutoRepeat:
mode = "autorepeat"
- node.set("mode",safe_format(mode, str) )
+ node.set("mode", safe_format(mode, str))
- node.set("delay",safe_format(self.delay, int))
+ node.set("delay", safe_format(self.delay, int))
node.set("interval", safe_format(self.autorepeat_delay, int))
-
+ virtual_code = ""
+ scan_code = ""
+ is_extended = ""
for code in self.keys:
- if isinstance(code, tuple): # key ID (scan_code, extended)
+ if isinstance(code, tuple): # key ID (scan_code, extended)
scan_code = code[0]
is_extended = code[1]
key = gremlin.keyboard.KeyMap.find(scan_code, is_extended)
virtual_code = key.virtual_code
- elif isinstance(code, int): # single virtual code
+ elif isinstance(code, int): # single virtual code
key = gremlin.keyboard.KeyMap.find_virtual(code)
scan_code = key.scan_code
is_extended = key.is_extended
diff --git a/action_plugins/map_to_mouse/__init__.py b/action_plugins/map_to_mouse/__init__.py
index 0c449645..77eea6a2 100644
--- a/action_plugins/map_to_mouse/__init__.py
+++ b/action_plugins/map_to_mouse/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -18,7 +18,6 @@
import logging
import math
-import os
from lxml import etree as ElementTree
from PySide6 import QtCore, QtWidgets, QtGui
@@ -35,8 +34,8 @@
syslog = logging.getLogger("system")
-class MapToMouseWidget(gremlin.ui.input_item.AbstractActionWidget):
+class MapToMouseWidget(gremlin.ui.input_item.AbstractActionWidget):
"""UI widget for mapping inputs to mouse motion or buttons."""
def __init__(self, action_data, parent=None):
@@ -86,9 +85,15 @@ def _create_ui(self):
self._create_button_hat_ui()
warning_color = gremlin.ui.ui_common.Color.warningColor()
- warning_widget = gremlin.ui.ui_common.QIconLabel("ph.shield-warning-fill",use_qta=True,icon_color=QtGui.QColor(warning_color),text="Legacy mapper - consider using Map to Mouse Ex for additional functionality", use_wrap=False)
+ warning_widget = gremlin.ui.ui_common.QIconLabel(
+ "ph.shield-warning-fill",
+ use_qta=True,
+ icon_color=QtGui.QColor(warning_color),
+ text="Legacy mapper - consider using Map to Mouse Ex for additional functionality",
+ use_wrap=False,
+ )
self.main_layout.addWidget(self.container_widget)
- self.main_layout.addWidget(warning_widget)
+ self.main_layout.addWidget(warning_widget)
def _create_axis_ui(self):
"""Creates the UI for axis setups."""
@@ -97,35 +102,40 @@ def _create_axis_ui(self):
self.y_axis = QtWidgets.QRadioButton("Y Axis")
self.motion_layout.addWidget(
- QtWidgets.QLabel("Control"),
- 0,
- 0,
- QtCore.Qt.AlignLeft
+ QtWidgets.QLabel("Control"), 0, 0, QtCore.Qt.AlignmentFlag.AlignLeft
+ )
+ self.motion_layout.addWidget(
+ self.x_axis, 0, 1, QtCore.Qt.AlignmentFlag.AlignLeft
+ )
+ self.motion_layout.addWidget(
+ self.y_axis, 0, 2, 1, 2, QtCore.Qt.AlignmentFlag.AlignLeft
)
- self.motion_layout.addWidget(self.x_axis, 0, 1, QtCore.Qt.AlignLeft)
- self.motion_layout.addWidget(self.y_axis, 0, 2, 1, 2, QtCore.Qt.AlignLeft)
self.min_speed = QtWidgets.QSpinBox()
self.min_speed.setRange(0, 1e5)
self.max_speed = QtWidgets.QSpinBox()
self.max_speed.setRange(0, 1e5)
self.motion_layout.addWidget(
- QtWidgets.QLabel("Minimum speed"), 1, 0, QtCore.Qt.AlignLeft
+ QtWidgets.QLabel("Minimum speed"), 1, 0, QtCore.Qt.AlignmentFlag.AlignLeft
+ )
+ self.motion_layout.addWidget(
+ self.min_speed, 1, 1, QtCore.Qt.AlignmentFlag.AlignLeft
+ )
+ self.motion_layout.addWidget(
+ QtWidgets.QLabel("Maximum speed"), 1, 2, QtCore.Qt.AlignmentFlag.AlignLeft
)
- self.motion_layout.addWidget(self.min_speed, 1, 1, QtCore.Qt.AlignLeft)
self.motion_layout.addWidget(
- QtWidgets.QLabel("Maximum speed"), 1, 2, QtCore.Qt.AlignLeft
+ self.max_speed, 1, 3, QtCore.Qt.AlignmentFlag.AlignLeft
)
- self.motion_layout.addWidget(self.max_speed, 1, 3, QtCore.Qt.AlignLeft)
self._connect_axis()
def _create_button_hat_ui(self):
"""Creates the UI for button setups."""
self.min_speed = QtWidgets.QSpinBox()
- self.min_speed.setRange(0, 1e5)
+ self.min_speed.setRange(0, int(1e5))
self.max_speed = QtWidgets.QSpinBox()
- self.max_speed.setRange(0, 1e5)
+ self.max_speed.setRange(0, int(1e5))
self.time_to_max_speed = gremlin.ui.ui_common.DynamicDoubleSpinBox()
self.time_to_max_speed.setRange(0.0, 100.0)
self.time_to_max_speed.setValue(0.0)
@@ -135,22 +145,25 @@ def _create_button_hat_ui(self):
self.direction.setRange(0, 359)
self.motion_layout.addWidget(QtWidgets.QLabel("Minimum speed"), 0, 0)
- self.motion_layout.addWidget(self.min_speed, 0, 1, QtCore.Qt.AlignLeft)
+ self.motion_layout.addWidget(
+ self.min_speed, 0, 1, QtCore.Qt.AlignmentFlag.AlignLeft
+ )
self.motion_layout.addWidget(QtWidgets.QLabel("Maximum speed"), 0, 2)
- self.motion_layout.addWidget(self.max_speed, 0, 3, QtCore.Qt.AlignLeft)
-
self.motion_layout.addWidget(
- QtWidgets.QLabel("Time to maximum speed"), 1, 0
+ self.max_speed, 0, 3, QtCore.Qt.AlignmentFlag.AlignLeft
)
+
+ self.motion_layout.addWidget(QtWidgets.QLabel("Time to maximum speed"), 1, 0)
self.motion_layout.addWidget(
- self.time_to_max_speed, 1, 1, QtCore.Qt.AlignLeft
+ self.time_to_max_speed, 1, 1, QtCore.Qt.AlignmentFlag.AlignLeft
)
if self.action_data.get_input_type() in [
- InputType.JoystickButton, InputType.Keyboard
+ InputType.JoystickButton,
+ InputType.Keyboard,
]:
self.motion_layout.addWidget(QtWidgets.QLabel("Direction"), 1, 2)
self.motion_layout.addWidget(
- self.direction, 1, 3, QtCore.Qt.AlignLeft
+ self.direction, 1, 3, QtCore.Qt.AlignmentFlag.AlignLeft
)
self._connect_button_hat()
@@ -299,8 +312,7 @@ def _change_mode(self):
def _request_user_input(self):
"""Prompts the user for the input to bind to this item."""
self.button_press_dialog = gremlin.ui.ui_common.InputListenerWidget(
- [InputType.Mouse],
- return_kb_event=False
+ [InputType.Mouse], return_kb_event=False
)
self.button_press_dialog.item_selected.connect(self._update_mouse_button)
@@ -314,13 +326,12 @@ def _request_user_input(self):
int(geom.x() + geom.width() / 2 - 150),
int(geom.y() + geom.height() / 2 - 75),
300,
- 150
+ 150,
)
self.button_press_dialog.show()
class MapToMouseFunctor(gremlin.base_profile.AbstractFunctor):
-
"""Implements the functionality required to move a mouse cursor.
This moves the mouse cursor by issuing relative motion commands. This is
@@ -328,7 +339,7 @@ class MapToMouseFunctor(gremlin.base_profile.AbstractFunctor):
properly with a single input, at least partially.
"""
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
"""Creates a new functor with the provided data.
:param action contains parameters to use with the functor
@@ -338,7 +349,7 @@ def __init__(self, action, parent = None):
self.config = action
self.mouse_controller = gremlin.sendinput.MouseController()
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
if self.config.motion_input:
if event.event_type == InputType.JoystickAxis:
self._perform_axis_motion(event, value)
@@ -369,18 +380,22 @@ def _perform_mouse_button(self, event, value):
if is_local:
gremlin.sendinput.mouse_h_wheel(direction)
if is_remote:
- input_devices.remote_client.send_mouse_h_wheel(direction)
+ input_devices.remote_client.send_mouse_h_wheel(direction)
else:
if value.current:
if is_local:
gremlin.sendinput.mouse_press(self.config.button_id)
if is_remote:
- input_devices.remote_client.send_mouse_button(self.config.button_id.value, True)
+ input_devices.remote_client.send_mouse_button(
+ self.config.button_id.value, True
+ )
else:
if is_local:
gremlin.sendinput.mouse_release(self.config.button_id)
if is_remote:
- input_devices.remote_client.send_mouse_button(self.config.button_id.value, False)
+ input_devices.remote_client.send_mouse_button(
+ self.config.button_id.value, False
+ )
def _perform_axis_motion(self, event, value):
"""Processes events destined for an axis.
@@ -388,8 +403,9 @@ def _perform_axis_motion(self, event, value):
:param event the event triggering the code execution
:param value the current value of the event chain
"""
- delta_motion = self.config.min_speed + abs(value.current) * \
- (self.config.max_speed - self.config.min_speed)
+ delta_motion = self.config.min_speed + abs(value.current) * (
+ self.config.max_speed - self.config.min_speed
+ )
delta_motion = math.copysign(delta_motion, value.current)
delta_motion = 0.0 if abs(value.current) < 0.05 else delta_motion
@@ -409,11 +425,16 @@ def _perform_button_motion(self, event, value):
self.config.direction,
self.config.min_speed,
self.config.max_speed,
- self.config.time_to_max_speed
+ self.config.time_to_max_speed,
)
if is_remote:
- input_devices.remote_client.send_mouse_acceleration(self.config.direction, self.config.min_speed, self.config.max_speed, self.config.time_to_max_speed)
-
+ input_devices.remote_client.send_mouse_acceleration(
+ self.config.direction,
+ self.config.min_speed,
+ self.config.max_speed,
+ self.config.time_to_max_speed,
+ )
+
else:
if is_local:
self.mouse_controller.set_absolute_motion(0, 0)
@@ -441,14 +462,18 @@ def _perform_hat_motion(self, event, value):
a,
self.config.min_speed,
self.config.max_speed,
- self.config.time_to_max_speed
+ self.config.time_to_max_speed,
)
if is_remote:
- input_devices.remote_client.send_mouse_acceleration(a, self.config.min_speed, self.config.max_speed, self.config.time_to_max_speed)
+ input_devices.remote_client.send_mouse_acceleration(
+ a,
+ self.config.min_speed,
+ self.config.max_speed,
+ self.config.time_to_max_speed,
+ )
class MapToMouse(gremlin.base_profile.AbstractAction):
-
"""Action data for the map to mouse action.
Map to mouse allows controlling of the mouse cursor using either a joystick
@@ -491,16 +516,16 @@ def __init__(self, parent):
self.time_to_max_speed = 1.0
def display_name(self):
- ''' returns a display string for the current configuration '''
+ """returns a display string for the current configuration"""
return f"[{self.button_id.name}]"
-
+
def icon(self):
"""Returns the icon to use for this action.
:return icon representing this action
"""
return "mdi.mouse"
- #return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
+ # return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
def requires_virtual_button(self):
"""Returns whether or not an activation condition is needed.
@@ -513,7 +538,7 @@ def requires_virtual_button(self):
return not self.motion_input
return False
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Reads the contents of an XML node to populate this instance.
:param node the node whose content should be used to populate this
@@ -525,9 +550,7 @@ def _parse_xml(self, node, data = None):
safe_read(node, "button-id", int, 1)
)
except ValueError as e:
- syslog.warning(
- f"Invalid mouse identifier in profile: {e:}"
- )
+ syslog.warning(f"Invalid mouse identifier in profile: {e:}")
self.button_id = gremlin.types.MouseButton.Left
self.direction = safe_read(node, "direction", int, 0)
self.min_speed = safe_read(node, "min-speed", int, 5)
diff --git a/action_plugins/map_to_mouse_ex/__init__.py b/action_plugins/map_to_mouse_ex/__init__.py
index a7d8979f..2558cebd 100644
--- a/action_plugins/map_to_mouse_ex/__init__.py
+++ b/action_plugins/map_to_mouse_ex/__init__.py
@@ -21,7 +21,10 @@
from gremlin import input_devices
-import enum, threading,time, random
+import enum
+import threading
+import time
+import random
import gremlin.util
@@ -30,7 +33,6 @@
class MapToMouseExWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""UI widget for mapping inputs to mouse motion or buttons."""
def __init__(self, action_data, parent=None):
@@ -40,7 +42,6 @@ def __init__(self, action_data, parent=None):
:param parent the parent of this widget
"""
super().__init__(action_data, QtWidgets.QVBoxLayout, parent=parent)
-
def _create_ui(self):
"""Creates the UI components."""
@@ -57,7 +58,6 @@ def _create_ui(self):
self.click_widget = QtWidgets.QWidget()
self.click_options_layout = QtWidgets.QHBoxLayout(self.click_widget)
-
self.mode_widget = gremlin.ui.ui_common.NoWheelComboBox()
input_type = self._get_input_type()
@@ -65,7 +65,7 @@ def _create_ui(self):
actions = (a for a in MouseAction)
else:
actions = (MouseAction.MouseButton, MouseAction.MouseMotion)
-
+
for mode in actions:
self.mode_widget.addItem(MouseAction.to_name(mode), mode)
@@ -77,12 +77,13 @@ def _create_ui(self):
self.chkb_force_remote_output_only = QtWidgets.QCheckBox("Remote Only")
self.chkb_force_remote_output.clicked.connect(self._force_remote_output_changed)
- self.chkb_force_remote_output_only.clicked.connect(self._force_remote_output_only_changed)
+ self.chkb_force_remote_output_only.clicked.connect(
+ self._force_remote_output_only_changed
+ )
self.chkb_exec_on_release = QtWidgets.QCheckBox("Exec on release")
self.chkb_exec_on_release.clicked.connect(self._exec_on_release_changed)
-
-
+
self.mode_group = QtWidgets.QButtonGroup()
self.mode_normal = QtWidgets.QRadioButton("Click")
self.mode_double_click = QtWidgets.QRadioButton("Double-Click")
@@ -99,20 +100,16 @@ def _create_ui(self):
self.mode_group.addButton(self.mode_press)
self.mode_group.addButton(self.mode_release)
-
self.click_options_layout.addWidget(self.mode_normal)
self.click_options_layout.addWidget(self.mode_double_click)
self.click_options_layout.addWidget(self.mode_press)
self.click_options_layout.addWidget(self.mode_release)
self.click_options_layout.addStretch()
-
-
self.options_layout.addWidget(self.chkb_exec_on_release)
self.options_layout.addWidget(self.chkb_force_remote_output)
self.options_layout.addWidget(self.chkb_force_remote_output_only)
-
-
+
self.options_layout.addStretch()
# self.button_group = QtWidgets.QButtonGroup()
@@ -132,19 +129,15 @@ def _create_ui(self):
# self.button_radio.clicked.connect(self._change_mode)
# self.motion_radio.clicked.connect(self._change_mode)
-
self.button_widget.hide()
self.motion_widget.hide()
-
self.main_layout.addLayout(self.mode_layout)
self.main_layout.addWidget(self.release_widget)
self.main_layout.addWidget(self.button_widget)
self.main_layout.addWidget(self.click_widget)
self.main_layout.addWidget(self.motion_widget)
-
-
# Create the different UI elements
self._create_mouse_button_ui()
@@ -153,7 +146,6 @@ def _create_ui(self):
else:
self._create_button_hat_ui()
-
def _click_change_mode(self):
if self.mode_normal.isChecked():
self.action_data.click_mode = MouseClickMode.Normal
@@ -173,14 +165,13 @@ def _create_axis_ui(self):
self.invert_widget.clicked.connect(self._invert_cb)
self.motion_layout.addWidget(
- QtWidgets.QLabel("Control"),
- 0,
- 0,
- QtCore.Qt.AlignLeft
+ QtWidgets.QLabel("Control"), 0, 0, QtCore.Qt.AlignLeft
)
self.motion_layout.addWidget(self.x_axis, 0, 1, QtCore.Qt.AlignLeft)
self.motion_layout.addWidget(self.y_axis, 0, 2, 1, 2, QtCore.Qt.AlignLeft)
- self.motion_layout.addWidget(self.invert_widget, 0, 3, 1, 2, QtCore.Qt.AlignLeft)
+ self.motion_layout.addWidget(
+ self.invert_widget, 0, 3, 1, 2, QtCore.Qt.AlignLeft
+ )
self.min_speed = QtWidgets.QSpinBox()
self.min_speed.setRange(0, 1e5)
@@ -195,7 +186,7 @@ def _create_axis_ui(self):
)
self.motion_layout.addWidget(self.max_speed, 1, 3, QtCore.Qt.AlignLeft)
self.motion_layout.addWidget(QtWidgets.QLabel(" "), 0, 4)
- self.motion_layout.setColumnStretch(4,2)
+ self.motion_layout.setColumnStretch(4, 2)
self._connect_axis()
@@ -218,19 +209,14 @@ def _create_button_hat_ui(self):
self.motion_layout.addWidget(QtWidgets.QLabel("Maximum speed"), 0, 2)
self.motion_layout.addWidget(self.max_speed, 0, 3, QtCore.Qt.AlignLeft)
- self.motion_layout.addWidget(
- QtWidgets.QLabel("Time to maximum speed"), 1, 0
- )
- self.motion_layout.addWidget(
- self.time_to_max_speed, 1, 1, QtCore.Qt.AlignLeft
- )
+ self.motion_layout.addWidget(QtWidgets.QLabel("Time to maximum speed"), 1, 0)
+ self.motion_layout.addWidget(self.time_to_max_speed, 1, 1, QtCore.Qt.AlignLeft)
if self.action_data.get_input_type() in [
- InputType.JoystickButton, InputType.Keyboard
+ InputType.JoystickButton,
+ InputType.Keyboard,
]:
self.motion_layout.addWidget(QtWidgets.QLabel("Direction"), 1, 2)
- self.motion_layout.addWidget(
- self.direction, 1, 3, QtCore.Qt.AlignLeft
- )
+ self.motion_layout.addWidget(self.direction, 1, 3, QtCore.Qt.AlignLeft)
self._connect_button_hat()
@@ -247,29 +233,41 @@ def _create_mouse_button_ui(self):
self.mouse_container_layout.addWidget(QtWidgets.QLabel("Mouse Button"))
self.mouse_container_layout.addWidget(self.mouse_button)
-
self.mouse_button_widget = gremlin.ui.ui_common.QComboBox()
- self.mouse_button_widget.addItem("Left (mouse 1)",gremlin.types.MouseButton.Left)
- self.mouse_button_widget.addItem("Middle (mouse 2)",gremlin.types.MouseButton.Middle)
- self.mouse_button_widget.addItem("Right (mouse 3)",gremlin.types.MouseButton.Right)
- self.mouse_button_widget.addItem("Forward (mouse 4)",gremlin.types.MouseButton.Forward)
- self.mouse_button_widget.addItem("Back (mouse 5)",gremlin.types.MouseButton.Back)
- self.mouse_button_widget.addItem("Wheel up",gremlin.types.MouseButton.WheelUp)
- self.mouse_button_widget.addItem("Wheel down",gremlin.types.MouseButton.WheelDown)
-
+ self.mouse_button_widget.addItem(
+ "Left (mouse 1)", gremlin.types.MouseButton.Left
+ )
+ self.mouse_button_widget.addItem(
+ "Middle (mouse 2)", gremlin.types.MouseButton.Middle
+ )
+ self.mouse_button_widget.addItem(
+ "Right (mouse 3)", gremlin.types.MouseButton.Right
+ )
+ self.mouse_button_widget.addItem(
+ "Forward (mouse 4)", gremlin.types.MouseButton.Forward
+ )
+ self.mouse_button_widget.addItem(
+ "Back (mouse 5)", gremlin.types.MouseButton.Back
+ )
+ self.mouse_button_widget.addItem("Wheel up", gremlin.types.MouseButton.WheelUp)
+ self.mouse_button_widget.addItem(
+ "Wheel down", gremlin.types.MouseButton.WheelDown
+ )
# update based on the current data
index = self.mouse_button_widget.findData(self.action_data.button_id)
self.mouse_button_widget.setCurrentIndex(index)
- self.mouse_button_widget.currentTextChanged.connect(self._change_mouse_button_cb)
+ self.mouse_button_widget.currentTextChanged.connect(
+ self._change_mouse_button_cb
+ )
self.mouse_container_layout.addWidget(QtWidgets.QLabel("Selected action:"))
self.mouse_container_layout.addWidget(self.mouse_button_widget)
self.mouse_container_layout.addStretch(1)
# add to main layout
- self.button_layout.addWidget(self.mouse_container_widget, 0,0)
+ self.button_layout.addWidget(self.mouse_container_widget, 0, 0)
def _populate_ui(self):
"""Populates the UI components."""
@@ -280,7 +278,6 @@ def _populate_ui(self):
self._populate_button_hat_ui()
self._populate_mouse_button_ui()
-
with QtCore.QSignalBlocker(self.chkb_exec_on_release):
self.chkb_exec_on_release.setChecked(self.action_data.exec_on_release)
@@ -290,7 +287,6 @@ def _populate_ui(self):
with QtCore.QSignalBlocker(self.mode_widget):
self.mode_widget.setCurrentIndex(index)
-
self.mode_label.setText(MouseAction.to_description(action_mode))
click_mode = self.action_data.click_mode
@@ -309,7 +305,6 @@ def _populate_ui(self):
self._change_mode()
-
def _populate_axis_ui(self):
"""Populates axis UI elements with data."""
self._disconnect_axis()
@@ -341,11 +336,9 @@ def _populate_mouse_button_ui(self):
def _invert_cb(self, checked):
self.action_data.invert = checked
-
-
@QtCore.Slot()
def _change_mouse_button_cb(self):
- ''' mouse event drop down selected '''
+ """mouse event drop down selected"""
self.action_data.button_id = self.mouse_button_widget.currentData()
self.mouse_button.setText(
gremlin.types.MouseButton.to_string(self.action_data.button_id)
@@ -353,7 +346,7 @@ def _change_mouse_button_cb(self):
@QtCore.Slot(int)
def _action_mode_changed(self, index):
- ''' called when the action mode drop down value changes '''
+ """called when the action mode drop down value changes"""
with QtCore.QSignalBlocker(self.mode_widget):
action = self.mode_widget.itemData(index)
self.action_data.action_mode = action
@@ -366,11 +359,10 @@ def _exec_on_release_changed(self, checked):
@QtCore.Slot(bool)
def _force_remote_output_changed(self, checked):
self.action_data.force_remote_output = checked
-
+
@QtCore.Slot(bool)
def _force_remote_output_only_changed(self, checked):
self.action_data.force_remote_output_only = checked
-
def _update_axis(self):
"""Updates the axis data with UI information."""
@@ -429,8 +421,6 @@ def _update_mouse_button(self, event):
index = self.mouse_button_widget.findData(self.action_data.button_id)
self.mouse_button_widget.setCurrentIndex(index)
-
-
def _connect_axis(self):
"""Connects all axis input elements to their callbacks."""
self.x_axis.toggled.connect(self._update_axis)
@@ -472,15 +462,17 @@ def _change_mode(self):
action_mode = self.action_data.action_mode
if action_mode == MouseAction.MouseButton:
show_button = True
- if not self.action_data.button_id in [MouseButton.WheelDown, MouseButton.WheelUp]:
+ if self.action_data.button_id not in [
+ MouseButton.WheelDown,
+ MouseButton.WheelUp,
+ ]:
show_click_mode = True
elif action_mode == MouseAction.MouseMotion:
show_motion = True
self.action_data.motion_input = show_motion
-
-
- #show_motion = self.action_data.motion_input
+
+ # show_motion = self.action_data.motion_input
self.motion_widget.setVisible(show_motion)
self.button_widget.setVisible(show_button)
self.click_widget.setVisible(show_click_mode)
@@ -493,8 +485,7 @@ def _change_mode(self):
def _request_user_input(self):
"""Prompts the user for the input to bind to this item."""
self.button_press_dialog = gremlin.ui.ui_common.InputListenerWidget(
- [InputType.Mouse],
- return_kb_event=False
+ [InputType.Mouse], return_kb_event=False
)
self.button_press_dialog.item_selected.connect(self._update_mouse_button)
# Display the dialog centered in the middle of the UI
@@ -507,13 +498,12 @@ def _request_user_input(self):
int(geom.x() + geom.width() / 2 - 150),
int(geom.y() + geom.height() / 2 - 75),
300,
- 150
+ 150,
)
self.button_press_dialog.show()
class MapToMouseExFunctor(gremlin.base_profile.AbstractFunctor):
-
"""Implements the functionality required to move a mouse cursor.
This moves the mouse cursor by issuing relative motion commands. This is
@@ -528,39 +518,37 @@ class MapToMouseExFunctor(gremlin.base_profile.AbstractFunctor):
_wiggle_remote_stop_requested = False
_mouse_controller = None
-
- def __init__(self, action : MapToMouseEx, parent = None):
+ def __init__(self, action: MapToMouseEx, parent=None):
"""Creates a new functor with the provided data.
:param action contains parameters to use with the functor
"""
super().__init__(action, parent)
- self.action : MapToMouseEx = action
+ self.action: MapToMouseEx = action
if not MapToMouseExFunctor._mouse_controller:
MapToMouseExFunctor._mouse_controller = gremlin.sendinput.MouseController()
-
+
self.input_type = action.input_type
self.exec_on_release = action.exec_on_release
self.action_mode = action.action_mode
self.click_mode = action.click_mode
-
- def process_event(self, event, value, extra_data = None):
- ''' processes an input event - must return True on success, False to abort the input sequence '''
+ def process_event(self, event, value, extra_data=None):
+ """processes an input event - must return True on success, False to abort the input sequence"""
- #syslog.debug(f"Process mouse functor event: {self.action_mode.name} {self.action.action_id} exec on release: {self.action.exec_on_release}")
+ # syslog.debug(f"Process mouse functor event: {self.action_mode.name} {self.action.action_id} exec on release: {self.action.exec_on_release}")
if self.input_type == InputType.JoystickButton:
if self.action_mode == MouseAction.MouseWiggleOnLocal:
# start the local wiggle thread
if self.exec_on_release and not event.is_pressed:
- self._wiggle_start(is_local=True)
+ self._wiggle_start(is_local=True)
elif not self.exec_on_release and event.is_pressed:
self._wiggle_start(is_local=True)
-
+
elif self.action_mode == MouseAction.MouseWiggleOffLocal:
if self.exec_on_release and not event.is_pressed:
- self._wiggle_stop(is_local = True)
+ self._wiggle_stop(is_local=True)
elif not self.exec_on_release and event.is_pressed:
self._wiggle_stop(is_local=True)
@@ -570,13 +558,13 @@ def process_event(self, event, value, extra_data = None):
self._wiggle_start(is_remote=True)
elif not self.exec_on_release and event.is_pressed:
self._wiggle_start(is_remote=True)
-
+
elif self.action_mode == MouseAction.MouseWiggleOffRemote:
if self.exec_on_release and not event.is_pressed:
- self._wiggle_stop(is_remote = True)
+ self._wiggle_stop(is_remote=True)
elif not self.exec_on_release and event.is_pressed:
self._wiggle_stop(is_remote=True)
-
+
elif self.action_mode == MouseAction.MouseMotion:
if event.event_type == InputType.JoystickAxis:
self._perform_axis_motion(event, value)
@@ -588,15 +576,15 @@ def process_event(self, event, value, extra_data = None):
if self.exec_on_release:
if not event.is_pressed:
self._perform_mouse_button(event, value)
- elif event.is_pressed:
+ elif event.is_pressed:
self._perform_mouse_button(event, value)
else:
self._perform_mouse_button(event, value)
return True
-
+
def get_state(self):
- ''' gets the control state '''
+ """gets the control state"""
(is_local, is_remote) = input_devices.remote_state.state
if self.action.force_remote_output:
is_remote = True
@@ -632,36 +620,47 @@ def _perform_mouse_button(self, event, value):
if is_local:
gremlin.sendinput.mouse_press(self.action.button_id)
if is_remote:
- input_devices.remote_client.send_mouse_button(self.action.button_id.value, True)
+ input_devices.remote_client.send_mouse_button(
+ self.action.button_id.value, True
+ )
else:
if is_local:
gremlin.sendinput.mouse_release(self.action.button_id)
if is_remote:
- input_devices.remote_client.send_mouse_button(self.action.button_id.value, False)
+ input_devices.remote_client.send_mouse_button(
+ self.action.button_id.value, False
+ )
elif self.action.click_mode == MouseClickMode.DoubleClick:
if value.current:
if is_local:
- gremlin.sendinput.mouse_press_double_click(self.action.button_id)
+ gremlin.sendinput.mouse_press_double_click(
+ self.action.button_id
+ )
if is_remote:
- input_devices.remote_client.send_mouse_button_double_click(self.action.button_id.value, True)
+ input_devices.remote_client.send_mouse_button_double_click(
+ self.action.button_id.value, True
+ )
else:
if is_local:
gremlin.sendinput.mouse_release(self.action.button_id)
if is_remote:
- input_devices.remote_client.send_mouse_button(self.action.button_id.value, False)
+ input_devices.remote_client.send_mouse_button(
+ self.action.button_id.value, False
+ )
elif self.action.click_mode == MouseClickMode.Press:
if is_local:
gremlin.sendinput.mouse_press(self.action.button_id)
if is_remote:
- input_devices.remote_client.send_mouse_button(self.action.button_id.value, True)
+ input_devices.remote_client.send_mouse_button(
+ self.action.button_id.value, True
+ )
elif self.action.click_mode == MouseClickMode.Release:
if is_local:
gremlin.sendinput.mouse_release(self.action.button_id)
if is_remote:
- input_devices.remote_client.send_mouse_button(self.action.button_id.value, False)
-
-
-
+ input_devices.remote_client.send_mouse_button(
+ self.action.button_id.value, False
+ )
def _perform_axis_motion(self, event, value):
"""Processes events destined for an axis.
@@ -670,14 +669,14 @@ def _perform_axis_motion(self, event, value):
:param value the current value of the event chain
"""
-
if self.action.invert:
# invert the input
inverted_value = gremlin.util.scale_to_range(value.current, invert=True)
value.current = inverted_value
- delta_motion = self.action.min_speed + abs(value.current) * \
- (self.action.max_speed - self.action.min_speed)
+ delta_motion = self.action.min_speed + abs(value.current) * (
+ self.action.max_speed - self.action.min_speed
+ )
delta_motion = math.copysign(delta_motion, value.current)
delta_motion = 0.0 if abs(value.current) < 0.05 else delta_motion
@@ -697,11 +696,16 @@ def _perform_button_motion(self, event, value):
self.action.direction,
self.action.min_speed,
self.action.max_speed,
- self.action.time_to_max_speed
+ self.action.time_to_max_speed,
)
if is_remote:
- input_devices.remote_client.send_mouse_acceleration(self.action.direction, self.action.min_speed, self.action.max_speed, self.action.time_to_max_speed)
-
+ input_devices.remote_client.send_mouse_acceleration(
+ self.action.direction,
+ self.action.min_speed,
+ self.action.max_speed,
+ self.action.time_to_max_speed,
+ )
+
else:
if is_local:
MapToMouseExFunctor._mouse_controller.set_absolute_motion(0, 0)
@@ -728,29 +732,37 @@ def _perform_hat_motion(self, event, value):
a,
self.action.min_speed,
self.action.max_speed,
- self.action.time_to_max_speed
+ self.action.time_to_max_speed,
)
if is_remote:
- input_devices.remote_client.send_mouse_acceleration(a, self.action.min_speed, self.action.max_speed, self.action.time_to_max_speed)
-
+ input_devices.remote_client.send_mouse_acceleration(
+ a,
+ self.action.min_speed,
+ self.action.max_speed,
+ self.action.time_to_max_speed,
+ )
- def _wiggle_start(self, is_local = False, is_remote = False):
- ''' starts the wiggle thread, local or remote '''
+ def _wiggle_start(self, is_local=False, is_remote=False):
+ """starts the wiggle thread, local or remote"""
if is_local and not MapToMouseExFunctor._wiggle_local_thread:
syslog.debug("Wiggle start local requested...")
MapToMouseExFunctor._wiggle_local_stop_requested = False
- MapToMouseExFunctor._wiggle_local_thread = threading.Thread(target=MapToMouseExFunctor._wiggle_local, daemon=True)
+ MapToMouseExFunctor._wiggle_local_thread = threading.Thread(
+ target=MapToMouseExFunctor._wiggle_local, daemon=True
+ )
MapToMouseExFunctor._wiggle_local_thread.start()
if is_remote and not MapToMouseExFunctor._wiggle_remote_thread:
syslog.debug("Wiggle start remote requested...")
MapToMouseExFunctor._wiggle_remote_stop_requested = False
- MapToMouseExFunctor._wiggle_remote_thread = threading.Thread(target=MapToMouseExFunctor._wiggle_remote, daemon=True)
+ MapToMouseExFunctor._wiggle_remote_thread = threading.Thread(
+ target=MapToMouseExFunctor._wiggle_remote, daemon=True
+ )
MapToMouseExFunctor._wiggle_remote_thread.start()
- def _wiggle_stop(self, is_local = False, is_remote = False):
- ''' stops the wiggle thread, local or remote '''
+ def _wiggle_stop(self, is_local=False, is_remote=False):
+ """stops the wiggle thread, local or remote"""
if is_local and MapToMouseExFunctor._wiggle_local_thread:
syslog.debug("Wiggle stop local requested...")
@@ -768,7 +780,7 @@ def _wiggle_stop(self, is_local = False, is_remote = False):
@staticmethod
def _wiggle_local():
- ''' wiggles the mouse '''
+ """wiggles the mouse"""
syslog.debug("Wiggle local start...")
msg = "local wiggle mode on"
input_devices.remote_state.say(msg)
@@ -782,17 +794,15 @@ def _wiggle_local():
MapToMouseExFunctor._mouse_controller.set_absolute_motion(-1, -1)
time.sleep(0.5)
MapToMouseExFunctor._mouse_controller.set_absolute_motion(0, 0)
- t_wait = time.time() + random.uniform(10,40)
+ t_wait = time.time() + random.uniform(10, 40)
time.sleep(0.5)
-
+
syslog.debug("Wiggle local stop...")
input_devices.remote_state.say("local wiggle mode off")
-
-
@staticmethod
def _wiggle_remote():
- ''' wiggles the mouse - remote clients'''
+ """wiggles the mouse - remote clients"""
syslog.debug("Wiggle remote start...")
msg = "remote wiggle mode on"
@@ -806,16 +816,15 @@ def _wiggle_remote():
time.sleep(1)
input_devices.remote_client.send_mouse_motion(-1, -1)
time.sleep(0.5)
- input_devices.remote_client.send_mouse_motion(0,0)
- t_wait = time.time() + random.uniform(10,40)
+ input_devices.remote_client.send_mouse_motion(0, 0)
+ t_wait = time.time() + random.uniform(10, 40)
time.sleep(0.5)
-
+
syslog.debug("Wiggle remote stop...")
input_devices.remote_state.say("remote wiggle mode off")
-
-class MapToMouseEx(gremlin.base_profile.AbstractAction):
+class MapToMouseEx(gremlin.base_profile.AbstractAction):
"""Action data for the map to mouse action.
Map to mouse allows controlling of the mouse cursor using either a joystick
@@ -867,9 +876,8 @@ def __init__(self, parent):
self.click_mode = MouseClickMode.Normal
-
def display_name(self):
- ''' returns a display string for the current configuration '''
+ """returns a display string for the current configuration"""
return f"[{self.button_id.name}]"
def icon(self):
@@ -890,14 +898,16 @@ def requires_virtual_button(self):
return not self.motion_input
return False
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Reads the contents of an XML node to populate this instance.
:param node the node whose content should be used to populate this
instance
"""
- self.action_mode = MouseAction.from_string(safe_read(node, "mode", str, "mousebutton"))
+ self.action_mode = MouseAction.from_string(
+ safe_read(node, "mode", str, "mousebutton")
+ )
self.motion_input = read_bool(node, "motion-input", False)
try:
@@ -905,9 +915,7 @@ def _parse_xml(self, node, data = None):
safe_read(node, "button-id", int, 1)
)
except ValueError as e:
- syslog.warning(
- f"Invalid mouse identifier in profile: {e:}"
- )
+ syslog.warning(f"Invalid mouse identifier in profile: {e:}")
self.button_id = gremlin.types.MouseButton.Left
self.direction = safe_read(node, "direction", int, 0)
@@ -916,24 +924,27 @@ def _parse_xml(self, node, data = None):
self.time_to_max_speed = safe_read(node, "time-to-max-speed", float, 0.0)
# get the type of mapping this is
-
+
if "exec_on_release" in node.attrib:
- self.exec_on_release = safe_read(node,"exec_on_release",bool, False)
+ self.exec_on_release = safe_read(node, "exec_on_release", bool, False)
if "force_remote" in node.attrib:
- self.force_remote_output = safe_read(node,"force_remote_output",bool, False)
+ self.force_remote_output = safe_read(
+ node, "force_remote_output", bool, False
+ )
if "remote_only" in node.attrib:
- self.force_remote_output_only = safe_read(node,"force_remote_output_only",bool, False)
+ self.force_remote_output_only = safe_read(
+ node, "force_remote_output_only", bool, False
+ )
if "click_mode" in node.attrib:
- self.click_mode = MouseClickMode.from_string(safe_read(node,"click_mode", str, "normal"))
+ self.click_mode = MouseClickMode.from_string(
+ safe_read(node, "click_mode", str, "normal")
+ )
if "invert" in node.attrib:
- self.invert = safe_read(node,"invert",bool, False)
-
-
-
+ self.invert = safe_read(node, "invert", bool, False)
def _generate_xml(self):
"""Returns an XML node containing this instance's information.
@@ -951,7 +962,9 @@ def _generate_xml(self):
node.set("time-to-max-speed", safe_format(self.time_to_max_speed, float))
node.set("exec_on_release", safe_format(self.exec_on_release, bool))
node.set("force_remote_output", safe_format(self.force_remote_output, bool))
- node.set("force_remote_output_only", safe_format(self.force_remote_output_only, bool))
+ node.set(
+ "force_remote_output_only", safe_format(self.force_remote_output_only, bool)
+ )
node.set("click_mode", self.click_mode.name)
node.set("invert", safe_format(self.invert, bool))
diff --git a/action_plugins/map_to_osc/__init__.py b/action_plugins/map_to_osc/__init__.py
index da551ba9..86b52d87 100644
--- a/action_plugins/map_to_osc/__init__.py
+++ b/action_plugins/map_to_osc/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,9 +16,6 @@
# along with this program. If not, see .
from __future__ import annotations
-import logging
-import math
-import os
from lxml import etree as ElementTree
from PySide6 import QtCore, QtWidgets, QtGui
@@ -30,34 +27,30 @@
from gremlin.input_types import InputType
import gremlin.joystick_handling
import gremlin.shared_state
-from gremlin.types import MouseButton
-from gremlin.profile import read_bool, safe_read, safe_format
+from gremlin.profile import safe_read, safe_format
import gremlin.util
import gremlin.ui.ui_common
import gremlin.ui.input_item
import gremlin.sendinput
-from gremlin import input_devices
import gremlin.ui.osc_device
-from gremlin.ui.osc_device import OscInterface, OscClient
+from gremlin.ui.osc_device import OscInterface
class OscValueWidget(QtWidgets.QWidget):
- valueChanged = QtCore.Signal() # fires when the value changes
- typeChanged = QtCore.Signal() # fires when integer flag changes
-
- def __init__(self, label = "Set Value:", value = None, is_integer = False, parent = None):
- super().__init__(parent)
+ valueChanged = QtCore.Signal() # fires when the value changes
+ typeChanged = QtCore.Signal() # fires when integer flag changes
+ def __init__(self, label="Set Value:", value=None, is_integer=False, parent=None):
+ super().__init__(parent)
self.main_layout = QtWidgets.QGridLayout(self)
self._frame_widget = gremlin.ui.ui_common.QBoxFrame()
- self._frame_widget.setContentsMargins(0,0,0,0)
+ self._frame_widget.setContentsMargins(0, 0, 0, 0)
self._frame_layout = QtWidgets.QHBoxLayout(self._frame_widget)
- self._is_axis = False # true if mapping to an axis input
+ self._is_axis = False # true if mapping to an axis input
self._is_integer = is_integer
-
self.label_widget = QtWidgets.QLabel("Value:")
if label:
@@ -65,13 +58,12 @@ def __init__(self, label = "Set Value:", value = None, is_integer = False, paren
if not value:
value = 1.0
-
self.label_widget = QtWidgets.QLabel("Value:")
if label:
self.label_widget.setText(label)
if not value:
value = 1.0
-
+
self._value_float_widget = gremlin.ui.ui_common.QFloatLineEdit()
self._value_int_widget = gremlin.ui.ui_common.QIntLineEdit()
@@ -82,7 +74,6 @@ def __init__(self, label = "Set Value:", value = None, is_integer = False, paren
self._is_float_widget = QtWidgets.QRadioButton("Float")
self._is_float_widget.setChecked(True)
-
self._is_int_widget.clicked.connect(self._int_selected)
self._is_float_widget.clicked.connect(self._float_selected)
@@ -90,12 +81,14 @@ def __init__(self, label = "Set Value:", value = None, is_integer = False, paren
self._value_float_widget.setRange(0, 1)
self._value_int_widget.valueChanged.connect(self._value_changed)
- self._value_int_widget.setMinimum(0)
+ self._value_int_widget.setMinimum(0)
self._value_container_widget = QtWidgets.QWidget()
- self._value_container_widget.setContentsMargins(0,0,0,0)
- self._value_container_layout = QtWidgets.QHBoxLayout(self._value_container_widget)
- self._value_container_layout.setContentsMargins(0,0,0,0)
+ self._value_container_widget.setContentsMargins(0, 0, 0, 0)
+ self._value_container_layout = QtWidgets.QHBoxLayout(
+ self._value_container_widget
+ )
+ self._value_container_layout.setContentsMargins(0, 0, 0, 0)
self._value_container_layout.addWidget(QtWidgets.QLabel("Value:"))
self._value_container_layout.addWidget(self._value_float_widget)
@@ -104,15 +97,13 @@ def __init__(self, label = "Set Value:", value = None, is_integer = False, paren
self._value_container_layout.addWidget(self._is_float_widget)
self._value_container_layout.addStretch()
-
- self.main_layout.addWidget(QtWidgets.QLabel(label),0,0)
- self.main_layout.addWidget(self._value_container_widget,1,0)
+ self.main_layout.addWidget(QtWidgets.QLabel(label), 0, 0)
+ self.main_layout.addWidget(self._value_container_widget, 1, 0)
self.main_layout.addWidget(QtWidgets.QWidget())
- self.main_layout.setColumnStretch(1,2)
-
+ self.main_layout.setColumnStretch(1, 2)
self._update()
-
+
def _update(self):
int_visible = self._is_integer
if int_visible:
@@ -130,10 +121,9 @@ def _update(self):
self._value_int_widget.setVisible(False)
self._value_float_widget.setVisible(True)
-
@QtCore.Slot()
def _value_changed(self):
- ''' value changed'''
+ """value changed"""
self.valueChanged.emit()
def setValue(self, value):
@@ -143,13 +133,14 @@ def setValue(self, value):
self._value_int_widget.setValue(value)
else:
value = gremlin.util.clamp(value, 0, 1)
- self._value_float_widget.setValue(value)
- @property
- def is_integer(self)-> bool:
+ self._value_float_widget.setValue(value)
+
+ @property
+ def is_integer(self) -> bool:
return self._is_integer
-
+
@is_integer.setter
- def is_integer(self, value : bool):
+ def is_integer(self, value: bool):
if self._is_integer != value:
self._is_integer = value
self._update()
@@ -168,56 +159,49 @@ def _float_selected(self, checked):
self.typeChanged.emit()
-
-
class OscInputWidget(QtWidgets.QWidget):
- ''' value container for an OSC message '''
-
- valuePressChanged = QtCore.Signal() # fires when the value changes (press)
- valueReleaseChanged = QtCore.Signal() # fires when the value changes (release)
- rangeChanged = QtCore.Signal() # fires when the axis range changes
- enabledChanged = QtCore.Signal(bool) # fires when enabled status changes
- typePressChanged = QtCore.Signal(int) # fires when the type change
- typeReleaseChanged = QtCore.Signal(int) # fires when the type change
-
-
- def __init__(self, label = None,
- enabled = False,
- value_press = None,
- value_release = None,
- is_press_integer = False,
- is_release_integer = False,
- is_axis = False,
- min_value = 0,
- max_value = 1.0,
- parent = None):
+ """value container for an OSC message"""
+
+ valuePressChanged = QtCore.Signal() # fires when the value changes (press)
+ valueReleaseChanged = QtCore.Signal() # fires when the value changes (release)
+ rangeChanged = QtCore.Signal() # fires when the axis range changes
+ enabledChanged = QtCore.Signal(bool) # fires when enabled status changes
+ typePressChanged = QtCore.Signal(int) # fires when the type change
+ typeReleaseChanged = QtCore.Signal(int) # fires when the type change
+
+ def __init__(
+ self,
+ label=None,
+ enabled=False,
+ value_press=None,
+ value_release=None,
+ is_press_integer=False,
+ is_release_integer=False,
+ is_axis=False,
+ min_value=0,
+ max_value=1.0,
+ parent=None,
+ ):
super().__init__(parent)
-
self.main_layout = QtWidgets.QVBoxLayout(self)
self._frame_widget = gremlin.ui.ui_common.QBoxFrame()
- self._frame_widget.setContentsMargins(0,0,0,0)
+ self._frame_widget.setContentsMargins(0, 0, 0, 0)
self._frame_layout = QtWidgets.QGridLayout(self._frame_widget)
- self._is_axis = False # true if mapping to an axis input
+ self._is_axis = False # true if mapping to an axis input
self._is_axis = is_axis
self._is_enabled = enabled if enabled is not None else False
-
# this value should be updated when the axis value changes via setRepeaterValue() if in axis mode
self._repeater_value = -1.0
-
-
-
-
self._is_enabled_widget = QtWidgets.QCheckBox("Enabled")
self._is_enabled_widget.setToolTip("Enables this parameter")
self._is_enabled_widget.setChecked(self.enabled)
self._is_enabled_widget.clicked.connect(self._enabled_changed)
-
self._axis_min_widget = gremlin.ui.ui_common.QFloatLineEdit()
self._axis_min_widget.setMinimum(0)
self._axis_min_widget.setValue(min_value)
@@ -226,16 +210,17 @@ def __init__(self, label = None,
self._axis_max_widget.setMinimum(0)
self._axis_max_widget.setValue(max_value)
self._axis_max_widget.valueChanged.connect(self._range_changed)
- self._axis_repeater_widget = gremlin.ui.ui_common.AxisStateWidget(show_percentage=False,orientation=QtCore.Qt.Orientation.Horizontal, show_curve=False)
+ self._axis_repeater_widget = gremlin.ui.ui_common.AxisStateWidget(
+ show_percentage=False,
+ orientation=QtCore.Qt.Orientation.Horizontal,
+ show_curve=False,
+ )
self._axis_repeater_widget.setRange(min_value, max_value)
-
-
self._axis_container_widget = QtWidgets.QWidget()
- self._axis_container_widget.setContentsMargins(0,0,0,0)
+ self._axis_container_widget.setContentsMargins(0, 0, 0, 0)
self._axis_container_layout = QtWidgets.QHBoxLayout(self._axis_container_widget)
-
self._axis_container_layout.addWidget(QtWidgets.QLabel("Range Min:"))
self._axis_container_layout.addWidget(self._axis_min_widget)
self._axis_container_layout.addWidget(QtWidgets.QLabel("Max:"))
@@ -244,19 +229,22 @@ def __init__(self, label = None,
self._axis_container_layout.addStretch()
self._container_widget = QtWidgets.QWidget()
- self._container_widget.setContentsMargins(0,0,0,0)
+ self._container_widget.setContentsMargins(0, 0, 0, 0)
self._container_layout = QtWidgets.QHBoxLayout(self._container_widget)
- self._value_press_widget = OscValueWidget(label = "Press Value:", is_integer= is_press_integer)
- self._value_release_widget = OscValueWidget(label = "Release Value:", is_integer = is_release_integer)
-
+ self._value_press_widget = OscValueWidget(
+ label="Press Value:", is_integer=is_press_integer
+ )
+ self._value_release_widget = OscValueWidget(
+ label="Release Value:", is_integer=is_release_integer
+ )
+
self._value_press = value_press if value_press is not None else 1.0
self._value_release = value_release if value_release is not None else 0.0
self._value_press_widget.setValue(value_press)
self._value_release_widget.setValue(value_release)
-
self._value_press_widget.valueChanged.connect(self._value_press_changed)
self._value_press_widget.typeChanged.connect(self._press_type_changed)
self._value_release_widget.valueChanged.connect(self._value_release_changed)
@@ -269,48 +257,43 @@ def __init__(self, label = None,
self._frame_layout.setSpacing(0)
row = 0
if label:
- self._frame_layout.addWidget(QtWidgets.QLabel(label), row, 0)
- row+=1
- self._frame_layout.addWidget(self._is_enabled_widget,row,0)
- self._frame_layout.addWidget(self._container_widget,row,1)
- self._frame_layout.addWidget(QtWidgets.QWidget(),row, 2)
- self._frame_layout.setColumnStretch(2,2)
+ self._frame_layout.addWidget(QtWidgets.QLabel(label), row, 0)
+ row += 1
+ self._frame_layout.addWidget(self._is_enabled_widget, row, 0)
+ self._frame_layout.addWidget(self._container_widget, row, 1)
+ self._frame_layout.addWidget(QtWidgets.QWidget(), row, 2)
+ self._frame_layout.setColumnStretch(2, 2)
-
-
self.main_layout.addWidget(self._frame_widget)
self.main_layout.addStretch()
self._update()
-
-
def _update(self):
# mapped to axis?
axis_visible = self._is_axis
self._axis_container_widget.setVisible(axis_visible)
self._value_press_widget.setVisible(not axis_visible)
self._value_release_widget.setVisible(not axis_visible)
-
-
+
self._container_widget.setEnabled(self._is_enabled)
self._axis_container_widget.setEnabled(self._is_enabled)
if not axis_visible:
pass
-
- def setRepeaterValue(self, value : float):
- ''' sets the axis repeater value - expecting an input -1 to +1 '''
+ def setRepeaterValue(self, value: float):
+ """sets the axis repeater value - expecting an input -1 to +1"""
self._repeater_value = value
self._update_repeater()
def _update_repeater(self):
- ''' updates the repeater '''
- value = gremlin.util.scale_to_range(self._repeater_value, target_min = self.min_range, target_max = self.max_range)
+ """updates the repeater"""
+ value = gremlin.util.scale_to_range(
+ self._repeater_value, target_min=self.min_range, target_max=self.max_range
+ )
self._axis_repeater_widget.setValue(value)
-
@QtCore.Slot()
def _range_changed(self):
# tell UI range changed
@@ -321,55 +304,54 @@ def _range_changed(self):
@property
def min_range(self) -> float:
return self._axis_min_widget.value()
-
+
@min_range.setter
- def min_range(self, value : float):
+ def min_range(self, value: float):
if value >= 0:
self._axis_min_widget.setValue(value)
@property
def max_range(self) -> float:
return self._axis_max_widget.value()
-
+
@max_range.setter
- def max_range(self, value : float):
+ def max_range(self, value: float):
if value >= 0:
self._axis_max_widget.setValue(value)
@property
def label(self):
return self.label_widget.text()
-
+
@label.setter
def label(self, value):
self.label_widget.setText(value)
- @property
- def is_press_integer(self)-> bool:
+ @property
+ def is_press_integer(self) -> bool:
return self._value_press_widget.is_integer
-
+
@is_press_integer.setter
- def is_press_integer(self, value : bool):
+ def is_press_integer(self, value: bool):
self._value_press_widget.is_integer = value
- @property
- def is_release_integer(self)-> bool:
+ @property
+ def is_release_integer(self) -> bool:
return self._value_release_widget.is_integer
-
+
@is_release_integer.setter
- def is_release_integer(self, value : bool):
- self._value_release_widget.is_integer = value
-
- @property
- def is_enabled(self)-> bool:
+ def is_release_integer(self, value: bool):
+ self._value_release_widget.is_integer = value
+
+ @property
+ def is_enabled(self) -> bool:
return self._is_enabled
-
+
@is_enabled.setter
- def is_enabled(self, value : bool):
+ def is_enabled(self, value: bool):
if self._is_enabled != value:
self._is_enabled = value
self._update()
-
@QtCore.Slot(bool)
def _enabled_changed(self, checked):
@@ -377,50 +359,48 @@ def _enabled_changed(self, checked):
self._update()
self.enabledChanged.emit(checked)
-
@QtCore.Slot()
def _value_press_changed(self):
- ''' value changed'''
+ """value changed"""
self.valuePressChanged.emit()
@QtCore.Slot()
- def _press_type_changed(self):
- self.typePressChanged.emit(0)
+ def _press_type_changed(self):
+ self.typePressChanged.emit(0)
@QtCore.Slot()
- def _release_type_changed(self):
- self.typeReleaseChanged.emit(1)
+ def _release_type_changed(self):
+ self.typeReleaseChanged.emit(1)
@QtCore.Slot()
def _value_release_changed(self):
- ''' value changed'''
+ """value changed"""
self.valueReleaseChanged.emit()
-
+
def valuePressed(self):
return self._value_press_widget.value()
-
+
def valueReleased(self):
return self._value_release_widget.value()
-
+
def setValuePressed(self, value):
self._value_press_widget.setValue(value)
+
def setValueRelease(self, value):
self._value_release_widget.setValue(value)
@property
def enabled(self) -> bool:
return self.is_enabled
-
+
@enabled.setter
- def enabled(self, value : bool):
+ def enabled(self, value: bool):
if self._is_enabled != value:
self._is_enabled = value
self._update()
-
class MapToOscWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""UI widget for mapping inputs to mouse motion or buttons."""
def __init__(self, action_data, parent=None):
@@ -441,13 +421,17 @@ def _create_ui(self):
self._osc_container_layout = QtWidgets.QHBoxLayout(self._osc_container_widget)
self._server_container_widget = QtWidgets.QWidget()
- self._server_container_widget.setContentsMargins(0,0,0,0)
- self._server_container_layout = QtWidgets.QHBoxLayout(self._server_container_widget)
-
- self._server_ip_widget = gremlin.ui.ui_common.QDataIPLineEdit(self.action_data.server_ip)
+ self._server_container_widget.setContentsMargins(0, 0, 0, 0)
+ self._server_container_layout = QtWidgets.QHBoxLayout(
+ self._server_container_widget
+ )
+
+ self._server_ip_widget = gremlin.ui.ui_common.QDataIPLineEdit(
+ self.action_data.server_ip
+ )
self._server_ip_widget.textChanged.connect(self._server_ip_changed)
self._server_port_widget = gremlin.ui.ui_common.QIntLineEdit()
-
+
self._server_port_widget.setRange(4096, 65535)
self._server_port_widget.setValue(self.action_data.server_port)
self._server_port_widget.valueChanged.connect(self._server_port_changed)
@@ -475,35 +459,35 @@ def _create_ui(self):
# # trigger is only used when the input is not an axis
# self._trigger_on_release_widget.setVisible(not is_axis)
- self._v1_widget = OscInputWidget(label = "Parameter 1:",
- value_press = self.action_data.v1_press,
- value_release = self.action_data.v1_release,
- enabled = self.action_data.v1_enabled,
- is_press_integer = self.action_data.v1_is_press_integer,
- is_release_integer = self.action_data.v1_is_release_integer,
- is_axis = is_axis,
- min_value = self.action_data.v1_min_range,
- max_value = self.action_data.v1_max_range,
- )
+ self._v1_widget = OscInputWidget(
+ label="Parameter 1:",
+ value_press=self.action_data.v1_press,
+ value_release=self.action_data.v1_release,
+ enabled=self.action_data.v1_enabled,
+ is_press_integer=self.action_data.v1_is_press_integer,
+ is_release_integer=self.action_data.v1_is_release_integer,
+ is_axis=is_axis,
+ min_value=self.action_data.v1_min_range,
+ max_value=self.action_data.v1_max_range,
+ )
self._v1_widget.valuePressChanged.connect(self._v1_value_press_changed)
self._v1_widget.valuePressChanged.connect(self._v1_value_release_changed)
self._v1_widget.enabledChanged.connect(self._v1_enabled_changed)
self._v1_widget.typePressChanged.connect(self._v1_press_type_changed)
self._v1_widget.typeReleaseChanged.connect(self._v1_release_type_changed)
self._v1_widget.rangeChanged.connect(self._v1_range_changed)
-
- self._v2_widget = OscInputWidget(label = "Parameter 2:",
- value_press = self.action_data.v2_press,
- value_release = self.action_data.v2_release,
- enabled = self.action_data.v2_enabled,
- is_press_integer = self.action_data.v2_is_press_integer,
- is_release_integer = self.action_data.v2_is_release_integer,
- is_axis = is_axis,
- min_value = self.action_data.v2_min_range,
- max_value = self.action_data.v2_max_range,
- )
-
+ self._v2_widget = OscInputWidget(
+ label="Parameter 2:",
+ value_press=self.action_data.v2_press,
+ value_release=self.action_data.v2_release,
+ enabled=self.action_data.v2_enabled,
+ is_press_integer=self.action_data.v2_is_press_integer,
+ is_release_integer=self.action_data.v2_is_release_integer,
+ is_axis=is_axis,
+ min_value=self.action_data.v2_min_range,
+ max_value=self.action_data.v2_max_range,
+ )
self._v2_widget.valuePressChanged.connect(self._v2_value_press_changed)
self._v2_widget.valuePressChanged.connect(self._v2_value_release_changed)
@@ -528,44 +512,53 @@ def _create_ui(self):
self._container_layout.addWidget(self._osc_container_widget)
self._value_container_widget = QtWidgets.QWidget()
- self._value_container_layout = QtWidgets.QVBoxLayout(self._value_container_widget)
+ self._value_container_layout = QtWidgets.QVBoxLayout(
+ self._value_container_widget
+ )
self._value_container_layout.addWidget(self._v1_widget)
self._value_container_layout.addWidget(self._v2_widget)
self._value_container_layout.addStretch()
warning_color = gremlin.ui.ui_common.Color.warningColor()
- self._warning_widget = gremlin.ui.ui_common.QIconLabel("ph.shield-warning-fill",use_qta=True,icon_color=QtGui.QColor(warning_color),text="", use_wrap=False)
+ self._warning_widget = gremlin.ui.ui_common.QIconLabel(
+ "ph.shield-warning-fill",
+ use_qta=True,
+ icon_color=QtGui.QColor(warning_color),
+ text="",
+ use_wrap=False,
+ )
self.main_layout.addWidget(QtWidgets.QLabel("Send OSC command:"))
self.main_layout.addWidget(self._container_widget)
self.main_layout.addWidget(self._value_container_widget)
- #self.main_layout.addWidget(self._trigger_on_release_widget)
- self.main_layout.addWidget(self._warning_widget)
-
+ # self.main_layout.addWidget(self._trigger_on_release_widget)
+ self.main_layout.addWidget(self._warning_widget)
+
self._warning_widget.setVisible(False)
self._update()
# get the current joystick value so repeaters are correct for start position
if is_axis:
- value = gremlin.joystick_handling.get_axis(self.action_data.hardware_device_guid, self.action_data.hardware_input_id)
+ value = gremlin.joystick_handling.get_axis(
+ self.action_data.hardware_device_guid,
+ self.action_data.hardware_input_id,
+ )
self._v1_widget.setRepeaterValue(value)
self._v2_widget.setRepeaterValue(value)
-
def _populate_ui(self):
"""Populates the UI components."""
pass
-
def _joystick_event_handler(self, event):
- ''' handles joystick events in the UI (functor handles the output when profile is running) so we see the output at design time '''
+ """handles joystick events in the UI (functor handles the output when profile is running) so we see the output at design time"""
if gremlin.shared_state.is_running:
- return
+ return
if not event.is_axis:
- return
-
+ return
+
value = event.value
-
+
if event.device_guid != self.action_data.hardware_device_guid:
return
if event.identifier != self.action_data.hardware_input_id:
@@ -576,15 +569,15 @@ def _joystick_event_handler(self, event):
@QtCore.Slot()
def _reset_server(self):
- ''' reset IP and port to configured defaults '''
- msgbox = gremlin.ui.ui_common.ConfirmBox(f"Reset server data to defaults?")
+ """reset IP and port to configured defaults"""
+ msgbox = gremlin.ui.ui_common.ConfirmBox("Reset server data to defaults?")
result = msgbox.show()
if result == QtWidgets.QMessageBox.StandardButton.Ok:
config = gremlin.config.Configuration()
- self._server_ip_widget.setText(config.osc_host) # also updates action_data
- self._server_port_widget.setValue(config.osc_output_port) # also updates action_data
-
-
+ self._server_ip_widget.setText(config.osc_host) # also updates action_data
+ self._server_port_widget.setValue(
+ config.osc_output_port
+ ) # also updates action_data
@QtCore.Slot()
def _server_ip_changed(self):
@@ -598,56 +591,55 @@ def _server_port_changed(self):
# def _trigger_on_release_cb(self, checked):
# self.action_data.trigger_on_release = checked
- @QtCore.Slot(bool)
+ @QtCore.Slot(bool)
def _v1_enabled_changed(self, enabled):
self.action_data.v1_enabled = enabled
- @QtCore.Slot()
+ @QtCore.Slot()
def _v1_value_press_changed(self):
- self.action_data.v1_press = self._v1_widget.valuePress()
- @QtCore.Slot()
+ self.action_data.v1_press = self._v1_widget.valuePressed()
+
+ @QtCore.Slot()
def _v1_value_release_changed(self):
- self.action_data.v1_release = self._v1_widget.valueRelease()
+ self.action_data.v1_release = self._v1_widget.valueReleased()
- @QtCore.Slot()
+ @QtCore.Slot()
def _v1_press_type_changed(self, index):
self.action_data.v1_is_press_integer = self._v1_widget.is_press_integer
- @QtCore.Slot()
+ @QtCore.Slot()
def _v2_press_type_changed(self, index):
self.action_data.v2_is_press_integer = self._v2_widget.is_press_integer
- @QtCore.Slot()
+ @QtCore.Slot()
def _v1_release_type_changed(self, index):
self.action_data.v1_is_release_integer = self._v1_widget.is_release_integer
- @QtCore.Slot()
+ @QtCore.Slot()
def _v2_release_type_changed(self, index):
self.action_data.v2_is_release_integer = self._v2_widget.is_release_integer
-
- @QtCore.Slot()
+ @QtCore.Slot()
def _v1_range_changed(self):
self.action_data.v1_min_range = self._v1_widget.min_range
- self.action_data.v1_max_range = self._v1_widget.max_range
+ self.action_data.v1_max_range = self._v1_widget.max_range
- @QtCore.Slot()
+ @QtCore.Slot()
def _v2_value_press_changed(self):
- self.action_data.v2_press = self._v2_widget.valuePress()
- @QtCore.Slot()
+ self.action_data.v2_press = self._v2_widget.valuePress()
+
+ @QtCore.Slot()
def _v2_value_release_changed(self):
- self.action_data.v2_release = self._v2_widget.valueRelease()
+ self.action_data.v2_release = self._v2_widget.valueRelease()
- @QtCore.Slot(bool)
+ @QtCore.Slot(bool)
def _v2_enabled_changed(self, enabled):
self.action_data.v2_enabled = enabled
-
- @QtCore.Slot()
+ @QtCore.Slot()
def _v2_range_changed(self):
self.action_data.v2_min_range = self._v2_widget.min_range
self.action_data.v2_max_range = self._v2_widget.max_range
-
def _update(self):
command = self._osc_widget.text()
@@ -660,15 +652,12 @@ def _update(self):
return
self._warning_widget.setVisible(False)
-
-
@QtCore.Slot()
def _command_changed(self):
command = self._osc_widget.text()
self.action_data.command = command
self._update()
-
def setWarning(self, warning):
if warning:
self._warning_widget.setText(warning)
@@ -678,7 +667,6 @@ def setWarning(self, warning):
class MapToOscFunctor(gremlin.base_profile.AbstractFunctor):
-
"""Implements the functionality required to move a mouse cursor.
This moves the mouse cursor by issuing relative motion commands. This is
@@ -686,7 +674,7 @@ class MapToOscFunctor(gremlin.base_profile.AbstractFunctor):
properly with a single input, at least partially.
"""
- def __init__(self, action : MapToOsc, parent = None):
+ def __init__(self, action: MapToOsc, parent=None):
"""Creates a new functor with the provided data.
:param action contains parameters to use with the functor
@@ -696,14 +684,17 @@ def __init__(self, action : MapToOsc, parent = None):
self.config = action
self.oscInterface = OscInterface()
self.osc_client = None
-
def profile_start(self):
- ''' occurs when process starts '''
- device_name = gremlin.shared_state.get_device_name(self.action_data.hardware_device_guid)
- self.osc_client = self.oscInterface.getClient(self.action_data.server_ip,
- self.action_data.server_port,
- name=f"OSC {device_name}/{self.action_data.hardware_input_id}")
+ """occurs when process starts"""
+ device_name = gremlin.shared_state.get_device_name(
+ self.action_data.hardware_device_guid
+ )
+ self.osc_client = self.oscInterface.getClient(
+ self.action_data.server_ip,
+ self.action_data.server_port,
+ name=f"OSC {device_name}/{self.action_data.hardware_input_id}",
+ )
self.osc_client.start()
def profile_stop(self):
@@ -711,59 +702,80 @@ def profile_stop(self):
self.osc_client.stop()
self.osc_client = None
-
- def process_event(self, event : gremlin.event_handler.Event, value : gremlin.actions.Value, extra_data = None):
-
+ def process_event(
+ self,
+ event: gremlin.event_handler.Event,
+ value: gremlin.actions.Value,
+ extra_data=None,
+ ):
is_axis = self.action_data.input_is_axis()
if is_axis:
# axis mode - compute the output values
raw = value.current
if self.action_data.v1_enabled:
- v1 = gremlin.util.scale_to_range(raw, target_min = self.action_data.v1_min_range,
- target_max = self.action_data.v1_max_range)
+ v1 = gremlin.util.scale_to_range(
+ raw,
+ target_min=self.action_data.v1_min_range,
+ target_max=self.action_data.v1_max_range,
+ )
else:
v1 = None
if self.action_data.v2_enabled:
- v2 = gremlin.util.scale_to_range(raw, target_min = self.action_data.v2_min_range,
- target_max = self.action_data.v2_max_range)
+ v2 = gremlin.util.scale_to_range(
+ raw,
+ target_min=self.action_data.v2_min_range,
+ target_max=self.action_data.v2_max_range,
+ )
else:
v2 = None
self.osc_client.send(self.action_data.command, v1, v2)
-
- else:
- # button mode - see what to trigger
+
+ else:
+ # button mode - see what to trigger
if event.is_pressed:
# send the command
if self.action_data.v1_enabled:
- v1 = int(self.action_data.v1_press) if self.action_data.v1_is_press_integer else self.action_data.v1_press
+ v1 = (
+ int(self.action_data.v1_press)
+ if self.action_data.v1_is_press_integer
+ else self.action_data.v1_press
+ )
else:
v1 = None
if self.action_data.v2_enabled:
- v2 = int(self.action_data.v2_press) if self.action_data.v2_is_press_integer else self.action_data.v1_press
+ v2 = (
+ int(self.action_data.v2_press)
+ if self.action_data.v2_is_press_integer
+ else self.action_data.v1_press
+ )
else:
v2 = None
else:
# send the command
if self.action_data.v1_enabled:
- v1 = int(self.action_data.v1_release) if self.action_data.v1_is_release_integer else self.action_data.v1_release
+ v1 = (
+ int(self.action_data.v1_release)
+ if self.action_data.v1_is_release_integer
+ else self.action_data.v1_release
+ )
else:
v1 = None
if self.action_data.v2_enabled:
- v2 = int(self.action_data.v2_release) if self.action_data.v2_is_release_integer else self.action_data.v2_release
+ v2 = (
+ int(self.action_data.v2_release)
+ if self.action_data.v2_is_release_integer
+ else self.action_data.v2_release
+ )
else:
v2 = None
-
self.osc_client.send(self.action_data.command, v1, v2)
-
-
class MapToOsc(gremlin.base_profile.AbstractAction):
-
- """Action data for the map to OSC (open sound control) - allows the inputs to send an OSC command """
+ """Action data for the map to OSC (open sound control) - allows the inputs to send an OSC command"""
name = "Map to OSC"
tag = "map-to-osc"
@@ -795,30 +807,28 @@ def __init__(self, parent):
self.command = None
self.server_ip = config.osc_host
self.server_port = config.osc_output_port
- self.v1_press = 1.0 # default v1 value
- self.v2_press = 1.0 # default v2 value
- self.v1_release = 0.0 # default v1 value
- self.v2_release = 0.0 # default v2 value
+ self.v1_press = 1.0 # default v1 value
+ self.v2_press = 1.0 # default v2 value
+ self.v1_release = 0.0 # default v1 value
+ self.v2_release = 0.0 # default v2 value
self.v1_enabled = False
self.v2_enabled = False
self.v1_is_press_integer = False
self.v1_is_release_integer = False
self.v2_is_press_integer = False
self.v2_is_release_integer = False
- self.trigger_on_release = False # trigger on release
+ self.trigger_on_release = False # trigger on release
self.v1_map_to_axis = False
self.v2_map_to_axis = False
- self.v1_min_range = 0.0 # min range when mapping to an axis
- self.v1_max_range = 1.0 # max range whem mapping to an axis
- self.v2_min_range = 0.0 # min range when mapping to an axis
- self.v2_max_range = 1.0 # max range whem mapping to an axis
-
-
+ self.v1_min_range = 0.0 # min range when mapping to an axis
+ self.v1_max_range = 1.0 # max range whem mapping to an axis
+ self.v2_min_range = 0.0 # min range when mapping to an axis
+ self.v2_max_range = 1.0 # max range whem mapping to an axis
def display_name(self):
- ''' returns a display string for the current configuration '''
+ """returns a display string for the current configuration"""
return f"OSC [{self.command}]"
-
+
def icon(self):
"""Returns the icon to use for this action.
@@ -835,7 +845,7 @@ def requires_virtual_button(self):
"""
return False
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Reads the contents of an XML node to populate this instance.
:param node the node whose content should be used to populate this
@@ -850,51 +860,53 @@ def _parse_xml(self, node, data = None):
if "v1" in node.attrib:
# old version
- self.v1_press = safe_read(node,"v1", float, 1)
+ self.v1_press = safe_read(node, "v1", float, 1)
if "v1_press" in node.attrib:
- self.v1_press = safe_read(node,"v1_press", float, 1)
+ self.v1_press = safe_read(node, "v1_press", float, 1)
if "v1_release" in node.attrib:
- self.v1_release = safe_read(node,"v1_release", float, 0)
-
+ self.v1_release = safe_read(node, "v1_release", float, 0)
if "v1_enabled" in node.attrib:
- self.v1_enabled = safe_read(node,"v1_enabled", bool, False)
+ self.v1_enabled = safe_read(node, "v1_enabled", bool, False)
if "v1_integer" in node.attrib:
- self.v1_is_press_integer = safe_read(node,"v1_integer", bool, False)
+ self.v1_is_press_integer = safe_read(node, "v1_integer", bool, False)
if "v1_press_integer" in node.attrib:
- self.v1_is_press_integer = safe_read(node,"v1_press_integer", bool, False)
+ self.v1_is_press_integer = safe_read(node, "v1_press_integer", bool, False)
if "v1_release_integer" in node.attrib:
- self.v1_is_release_integer = safe_read(node,"v1_release_integer", bool, False)
+ self.v1_is_release_integer = safe_read(
+ node, "v1_release_integer", bool, False
+ )
if "v2" in node.attrib:
# old version
- self.v2_press = safe_read(node,"v2", float, 1)
+ self.v2_press = safe_read(node, "v2", float, 1)
if "v2_press" in node.attrib:
- self.v2_press = safe_read(node,"v2_press", float, 1)
+ self.v2_press = safe_read(node, "v2_press", float, 1)
if "v2_release" in node.attrib:
- self.v2_release = safe_read(node,"v2_release", float, 0)
+ self.v2_release = safe_read(node, "v2_release", float, 0)
if "v2_enabled" in node.attrib:
- self.v2_enabled = safe_read(node,"v2_enabled", bool, False)
+ self.v2_enabled = safe_read(node, "v2_enabled", bool, False)
if "v2_integer" in node.attrib:
- self.v2_is_press_integer = safe_read(node,"v2_integer", bool, False)
+ self.v2_is_press_integer = safe_read(node, "v2_integer", bool, False)
if "v2_press_integer" in node.attrib:
- self.v1_is_press_integer = safe_read(node,"v2_press_integer", bool, False)
+ self.v1_is_press_integer = safe_read(node, "v2_press_integer", bool, False)
if "v2_release_integer" in node.attrib:
- self.v2_is_release_integer = safe_read(node,"v2_release_integer", bool, False)
-
+ self.v2_is_release_integer = safe_read(
+ node, "v2_release_integer", bool, False
+ )
+
if "v1_min_range" in node.attrib:
- self.v1_min_range = safe_read(node,"v1_min_range", float, 0)
+ self.v1_min_range = safe_read(node, "v1_min_range", float, 0)
if "v1_max_range" in node.attrib:
- self.v1_max_range = safe_read(node,"v1_max_range", float, 1)
+ self.v1_max_range = safe_read(node, "v1_max_range", float, 1)
if "v2_min_range" in node.attrib:
- self.v2_min_range = safe_read(node,"v2_min_range", float, 0)
+ self.v2_min_range = safe_read(node, "v2_min_range", float, 0)
if "v2_max_range" in node.attrib:
- self.v2_max_range = safe_read(node,"v2_max_range", float, 1)
-
+ self.v2_max_range = safe_read(node, "v2_max_range", float, 1)
- self.trigger_on_release = safe_read(node,"trigger_on_release", bool, False)
+ self.trigger_on_release = safe_read(node, "trigger_on_release", bool, False)
def _generate_xml(self):
"""Returns an XML node containing this instance's information.
@@ -917,11 +929,13 @@ def _generate_xml(self):
if self.v1_is_press_integer:
node.set("v1_press_integer", safe_format(self.v1_is_press_integer, bool))
if self.v1_is_release_integer:
- node.set("v1_release_integer", safe_format(self.v1_is_release_integer, bool))
+ node.set(
+ "v1_release_integer", safe_format(self.v1_is_release_integer, bool)
+ )
if self.v2_press:
node.set("v2_press", safe_format(self.v2_press, float))
if self.v2_release:
- node.set("v2_release", safe_format(self.v2_release, float))
+ node.set("v2_release", safe_format(self.v2_release, float))
if self.v2_enabled:
node.set("v1_enabled", safe_format(self.v2_enabled, bool))
if self.v2_is_press_integer:
diff --git a/action_plugins/map_to_simconnect/SimConnect/Attributes.py b/action_plugins/map_to_simconnect/SimConnect/Attributes.py
index 1c556437..eef36f8c 100644
--- a/action_plugins/map_to_simconnect/SimConnect/Attributes.py
+++ b/action_plugins/map_to_simconnect/SimConnect/Attributes.py
@@ -16,995 +16,975 @@
# @property
# def value(self):
# return self._value
-
class SimConnectDll(object):
-
- def __init__(self, simconnect_dll):
- self.EventID = SIMCONNECT_CLIENT_EVENT_ID
- self.DATA_DEFINITION_ID = SIMCONNECT_DATA_DEFINITION_ID
- self.DATA_REQUEST_ID = SIMCONNECT_DATA_REQUEST_ID
- self.GROUP_ID = SIMCONNECT_NOTIFICATION_GROUP_ID
- self.INPUT_GROUP_ID = SIMCONNECT_INPUT_GROUP_ID
- self.CLIENT_DATA_ID = SIMCONNECT_CLIENT_DATA_ID
- self.CLIENT_DATA_DEFINITION_ID = SIMCONNECT_CLIENT_DATA_DEFINITION_ID
-
- self.SimConnect = simconnect_dll
-
- # SIMCONNECTAPI SimConnect_Open(
- # HANDLE * phSimConnect,
- # LPCSTR szName,
- # HWND hWnd,
- # DWORD UserEventWin32,
- # HANDLE hEventHandle,
- # DWORD ConfigIndex)
-
- self.Open = self.SimConnect.SimConnect_Open
- self.Open.restype = HRESULT
- self.Open.argtypes = [POINTER(HANDLE), LPCSTR, HWND, DWORD, HANDLE, DWORD]
-
- # SIMCONNECTAPI SimConnect_Close(
- # HANDLE hSimConnect);
-
- self.Close = self.SimConnect.SimConnect_Close
- self.Close.restype = HRESULT
- self.Close.argtypes = [HANDLE]
-
- # SIMCONNECTAPI SimConnect_AddToDataDefinition(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_DEFINITION_ID DefineID,
- # const char * DatumName,
- # const char * UnitsName,
- # SIMCONNECT_DATATYPE DatumType = SIMCONNECT_DATATYPE_FLOAT64,
- # float fEpsilon = 0,
- # DWORD DatumID = SIMCONNECT_UNUSED);
-
- self.AddToDataDefinition = self.SimConnect.SimConnect_AddToDataDefinition
- self.AddToDataDefinition.restype = HRESULT
- self.AddToDataDefinition.argtypes = [
- HANDLE,
- self.DATA_DEFINITION_ID,
- c_char_p,
- c_char_p,
- SIMCONNECT_DATATYPE,
- c_float,
- DWORD,
- ]
-
- # SIMCONNECTAPI SimConnect_SubscribeToSystemEvent(
- # HANDLE hSimConnect,
- # SIMCONNECT_CLIENT_EVENT_ID EventID,
- # const char * SystemEventName);
-
- self.SubscribeToSystemEvent = (
- self.SimConnect.SimConnect_SubscribeToSystemEvent
- )
- self.SubscribeToSystemEvent.restype = HRESULT
- self.SubscribeToSystemEvent.argtypes = [HANDLE, self.EventID, c_char_p]
-
- # SIMCONNECTAPI SimConnect_CallDispatch(
- # HANDLE hSimConnect,
- # DispatchProc pfcnDispatch,
- # void * pContext);
-
- self.DispatchProc = WINFUNCTYPE(None, POINTER(SIMCONNECT_RECV), DWORD, c_void_p)
-
- self.CallDispatch = self.SimConnect.SimConnect_CallDispatch
- self.CallDispatch.restype = HRESULT
- self.CallDispatch.argtypes = [HANDLE, self.DispatchProc, c_void_p]
-
- # SIMCONNECTAPI SimConnect_RequestDataOnSimObjectType(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_REQUEST_ID RequestID,
- # SIMCONNECT_DATA_DEFINITION_ID DefineID,
- # DWORD dwRadiusMeters,
- # SIMCONNECT_SIMOBJECT_TYPE type);
-
- self.RequestDataOnSimObjectType = (
- self.SimConnect.SimConnect_RequestDataOnSimObjectType
- )
- self.RequestDataOnSimObjectType.restype = HRESULT
- self.RequestDataOnSimObjectType.argtypes = [
- HANDLE,
- self.DATA_REQUEST_ID,
- self.DATA_DEFINITION_ID,
- DWORD,
- SIMCONNECT_SIMOBJECT_TYPE,
- ]
-
- # SIMCONNECTAPI SimConnect_TransmitClientEvent(
- # HANDLE hSimConnect,
- # SIMCONNECT_OBJECT_ID ObjectID,
- # SIMCONNECT_CLIENT_EVENT_ID EventID,
- # DWORD dwData,
- # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID,
- # SIMCONNECT_EVENT_FLAG Flags);
-
- self.TransmitClientEvent = self.SimConnect.SimConnect_TransmitClientEvent
- self.TransmitClientEvent.restype = HRESULT
- self.TransmitClientEvent.argtypes = [
- HANDLE,
- SIMCONNECT_OBJECT_ID,
- self.EventID,
- DWORD,
- DWORD,
- DWORD,
- ]
-
- # SIMCONNECTAPI SimConnect_MapClientEventToSimEvent(
- # HANDLE hSimConnect,
- # SIMCONNECT_CLIENT_EVENT_ID EventID,
- # const char * EventName = "");
-
- self.MapClientEventToSimEvent = (
- self.SimConnect.SimConnect_MapClientEventToSimEvent
- )
- self.MapClientEventToSimEvent.restype = HRESULT
- self.MapClientEventToSimEvent.argtypes = [HANDLE, self.EventID, c_char_p]
-
- # SIMCONNECTAPI SimConnect_AddClientEventToNotificationGroup(
- # HANDLE hSimConnect,
- # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID,
- # SIMCONNECT_CLIENT_EVENT_ID EventID,
- # BOOL bMaskable = FALSE);
-
- self.AddClientEventToNotificationGroup = (
- self.SimConnect.SimConnect_AddClientEventToNotificationGroup
- )
- self.AddClientEventToNotificationGroup.restype = HRESULT
- self.AddClientEventToNotificationGroup.argtypes = [
- HANDLE,
- self.GROUP_ID,
- self.EventID,
- c_bool,
- ]
-
- # SIMCONNECTAPI SimConnect_SetSystemEventState(
- # HANDLE hSimConnect,
- # SIMCONNECT_CLIENT_EVENT_ID EventID
- # SIMCONNECT_STATE dwState);
- self.SetSystemEventState = self.SimConnect.SimConnect_SetSystemEventState
- self.SetSystemEventState.restype = HRESULT
- self.SetSystemEventState.argtypes = [HANDLE, self.EventID, SIMCONNECT_STATE]
-
- # SIMCONNECTAPI SimConnect_AddClientEventToNotificationGroup(
- # HANDLE hSimConnect,
- # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID
- # SIMCONNECT_CLIENT_EVENT_ID EventID
- # BOOL bMaskable = FALSE);
- self.AddClientEventToNotificationGroup = (
- self.SimConnect.SimConnect_AddClientEventToNotificationGroup
- )
- self.AddClientEventToNotificationGroup.restype = HRESULT
- self.AddClientEventToNotificationGroup.argtypes = [
- HANDLE,
- self.GROUP_ID,
- self.EventID,
- c_bool,
- ]
-
- # SIMCONNECTAPI SimConnect_RemoveClientEvent(
- # HANDLE hSimConnect,
- # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID
- # SIMCONNECT_CLIENT_EVENT_ID EventID);
- self.RemoveClientEvent = self.SimConnect.SimConnect_RemoveClientEvent
- self.RemoveClientEvent.restype = HRESULT
- self.RemoveClientEvent.argtypes = [HANDLE, self.GROUP_ID, self.EventID]
-
- # SIMCONNECTAPI SimConnect_SetNotificationGroupPriority(
- # HANDLE hSimConnect,
- # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID
- # DWORD uPriority);
- self.SetNotificationGroupPriority = (
- self.SimConnect.SimConnect_SetNotificationGroupPriority
- )
- self.SetNotificationGroupPriority.restype = HRESULT
- self.SetNotificationGroupPriority.argtypes = [HANDLE, self.GROUP_ID, DWORD]
-
- # SIMCONNECTAPI SimConnect_ClearNotificationGroup(
- # HANDLE hSimConnect,
- # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID);
- self.ClearNotificationGroup = (
- self.SimConnect.SimConnect_ClearNotificationGroup
- )
- self.ClearNotificationGroup.restype = HRESULT
- self.ClearNotificationGroup.argtypes = [HANDLE, self.GROUP_ID]
-
- # SIMCONNECTAPI SimConnect_RequestNotificationGroup(
- # HANDLE hSimConnect,
- # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID
- # DWORD dwReserved = 0
- # DWORD Flags = 0);
- self.RequestNotificationGroup = (
- self.SimConnect.SimConnect_RequestNotificationGroup
- )
- self.RequestNotificationGroup.restype = HRESULT
- self.RequestNotificationGroup.argtypes = [HANDLE, self.GROUP_ID, DWORD, DWORD]
-
- # SIMCONNECTAPI SimConnect_ClearDataDefinition(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_DEFINITION_ID DefineID);
- self.ClearDataDefinition = self.SimConnect.SimConnect_ClearDataDefinition
- self.ClearDataDefinition.restype = HRESULT
- self.ClearDataDefinition.argtypes = [HANDLE, self.DATA_DEFINITION_ID]
-
- # SIMCONNECTAPI SimConnect_RequestDataOnSimObject(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_REQUEST_ID RequestID
- # SIMCONNECT_DATA_DEFINITION_ID DefineID
- # SIMCONNECT_OBJECT_ID ObjectID
- # SIMCONNECT_PERIOD Period
- # SIMCONNECT_DATA_REQUEST_FLAG Flags = 0
- # DWORD origin = 0
- # DWORD interval = 0
- # DWORD limit = 0);
- self.RequestDataOnSimObject = (
- self.SimConnect.SimConnect_RequestDataOnSimObject
- )
- self.RequestDataOnSimObject.restype = HRESULT
- self.RequestDataOnSimObject.argtypes = [
- HANDLE,
- self.DATA_REQUEST_ID,
- self.DATA_DEFINITION_ID,
- SIMCONNECT_OBJECT_ID,
- SIMCONNECT_PERIOD,
- SIMCONNECT_DATA_REQUEST_FLAG,
- DWORD,
- DWORD,
- DWORD,
- ]
-
- # SIMCONNECTAPI SimConnect_SetDataOnSimObject(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_DEFINITION_ID DefineID
- # SIMCONNECT_OBJECT_ID ObjectID
- # SIMCONNECT_DATA_SET_FLAG Flags
- # DWORD ArrayCount
- # DWORD cbUnitSize
- # void * pDataSet);
- self.SetDataOnSimObject = self.SimConnect.SimConnect_SetDataOnSimObject
- self.SetDataOnSimObject.restype = HRESULT
- self.SetDataOnSimObject.argtypes = [
- HANDLE,
- self.DATA_DEFINITION_ID,
- SIMCONNECT_OBJECT_ID,
- SIMCONNECT_DATA_SET_FLAG,
- DWORD,
- DWORD,
- c_void_p,
- ]
-
- # SIMCONNECTAPI SimConnect_MapInputEventToClientEvent(
- # HANDLE hSimConnect,
- # SIMCONNECT_INPUT_GROUP_ID GroupID
- # const char * szInputDefinition
- # SIMCONNECT_CLIENT_EVENT_ID DownEventID
- # DWORD DownValue = 0
- # SIMCONNECT_CLIENT_EVENT_ID UpEventID = (SIMCONNECT_CLIENT_EVENT_ID)SIMCONNECT_UNUSED
- # DWORD UpValue = 0
- # BOOL bMaskable = FALSE);
- self.MapInputEventToClientEvent = (
- self.SimConnect.SimConnect_MapInputEventToClientEvent
- )
- self.MapInputEventToClientEvent.restype = HRESULT
- self.MapInputEventToClientEvent.argtypes = [
- HANDLE,
- self.INPUT_GROUP_ID,
- c_char_p,
- self.EventID,
- DWORD,
- self.EventID,
- DWORD,
- c_bool,
- ]
-
- # SIMCONNECTAPI SimConnect_SetInputGroupPriority(
- # HANDLE hSimConnect,
- # SIMCONNECT_INPUT_GROUP_ID GroupID
- # DWORD uPriority);
- self.SetInputGroupPriority = self.SimConnect.SimConnect_SetInputGroupPriority
- self.SetInputGroupPriority.restype = HRESULT
- self.SetInputGroupPriority.argtypes = [HANDLE, self.INPUT_GROUP_ID, DWORD]
-
- # SIMCONNECTAPI SimConnect_RemoveInputEvent(
- # HANDLE hSimConnect,
- # SIMCONNECT_INPUT_GROUP_ID GroupID
- # const char * szInputDefinition);
- self.RemoveInputEvent = self.SimConnect.SimConnect_RemoveInputEvent
- self.RemoveInputEvent.restype = HRESULT
- self.RemoveInputEvent.argtypes = [HANDLE, self.INPUT_GROUP_ID, c_char_p]
-
- # SIMCONNECTAPI SimConnect_ClearInputGroup(
- # HANDLE hSimConnect,
- # SIMCONNECT_INPUT_GROUP_ID GroupID);
- self.ClearInputGroup = self.SimConnect.SimConnect_ClearInputGroup
- self.ClearInputGroup.restype = HRESULT
- self.ClearInputGroup.argtypes = [HANDLE, self.INPUT_GROUP_ID]
-
- # SIMCONNECTAPI SimConnect_SetInputGroupState(
- # HANDLE hSimConnect,
- # SIMCONNECT_INPUT_GROUP_ID GroupID
- # DWORD dwState);
- self.SetInputGroupState = self.SimConnect.SimConnect_SetInputGroupState
- self.SetInputGroupState.restype = HRESULT
- self.SetInputGroupState.argtypes = [HANDLE, self.INPUT_GROUP_ID, DWORD]
-
- # SIMCONNECTAPI SimConnect_RequestReservedKey(
- # HANDLE hSimConnect,
- # SIMCONNECT_CLIENT_EVENT_ID EventID
- # const char * szKeyChoice1 = ""
- # const char * szKeyChoice2 = ""
- # const char * szKeyChoice3 = "");
- self.RequestReservedKey = self.SimConnect.SimConnect_RequestReservedKey
- self.RequestReservedKey.restype = HRESULT
- self.RequestReservedKey.argtypes = [
- HANDLE,
- self.EventID,
- c_char_p,
- c_char_p,
- c_char_p,
- ]
-
- # SIMCONNECTAPI SimConnect_UnsubscribeFromSystemEvent(
- # HANDLE hSimConnect,
- # SIMCONNECT_CLIENT_EVENT_ID EventID);
- self.UnsubscribeFromSystemEvent = (
- self.SimConnect.SimConnect_UnsubscribeFromSystemEvent
- )
- self.UnsubscribeFromSystemEvent.restype = HRESULT
- self.UnsubscribeFromSystemEvent.argtypes = [HANDLE, self.EventID]
-
- # SIMCONNECTAPI SimConnect_WeatherRequestInterpolatedObservation(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_REQUEST_ID RequestID
- # float lat
- # float lon
- # float alt);
- self.WeatherRequestInterpolatedObservation = (
- self.SimConnect.SimConnect_WeatherRequestInterpolatedObservation
- )
- self.WeatherRequestInterpolatedObservation.restype = HRESULT
- self.WeatherRequestInterpolatedObservation.argtypes = [
- HANDLE,
- self.DATA_REQUEST_ID,
- c_float,
- c_float,
- c_float,
- ]
-
- # SIMCONNECTAPI SimConnect_WeatherRequestObservationAtStation(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_REQUEST_ID RequestID
- # const char * szICAO);
- self.WeatherRequestObservationAtStation = (
- self.SimConnect.SimConnect_WeatherRequestObservationAtStation
- )
- self.WeatherRequestObservationAtStation.restype = HRESULT
- self.WeatherRequestObservationAtStation.argtypes = [
- HANDLE,
- self.DATA_REQUEST_ID,
- c_char_p,
- ]
-
- # SIMCONNECTAPI SimConnect_WeatherRequestObservationAtNearestStation(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_REQUEST_ID RequestID
- # float lat
- # float lon);
- self.WeatherRequestObservationAtNearestStation = (
- self.SimConnect.SimConnect_WeatherRequestObservationAtNearestStation
- )
- self.WeatherRequestObservationAtNearestStation.restype = HRESULT
- self.WeatherRequestObservationAtNearestStation.argtypes = [
- HANDLE,
- self.DATA_REQUEST_ID,
- c_float,
- c_float,
- ]
-
- # SIMCONNECTAPI SimConnect_WeatherCreateStation(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_REQUEST_ID RequestID
- # const char * szICAO
- # const char * szName
- # float lat
- # float lon
- # float alt);
- self.WeatherCreateStation = self.SimConnect.SimConnect_WeatherCreateStation
- self.WeatherCreateStation.restype = HRESULT
- self.WeatherCreateStation.argtypes = [
- HANDLE,
- self.DATA_REQUEST_ID,
- c_char_p,
- c_char_p,
- c_float,
- c_float,
- c_float,
- ]
-
- # SIMCONNECTAPI SimConnect_WeatherRemoveStation(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_REQUEST_ID RequestID
- # const char * szICAO);
- self.WeatherRemoveStation = self.SimConnect.SimConnect_WeatherRemoveStation
- self.WeatherRemoveStation.restype = HRESULT
- self.WeatherRemoveStation.argtypes = [HANDLE, self.DATA_REQUEST_ID, c_char_p]
-
- # SIMCONNECTAPI SimConnect_WeatherSetObservation(
- # HANDLE hSimConnect,
- # DWORD Seconds
- # const char * szMETAR);
- self.WeatherSetObservation = self.SimConnect.SimConnect_WeatherSetObservation
- self.WeatherSetObservation.restype = HRESULT
- self.WeatherSetObservation.argtypes = [HANDLE, DWORD, c_char_p]
-
- # SIMCONNECTAPI SimConnect_WeatherSetModeServer(
- # HANDLE hSimConnect,
- # DWORD dwPort
- # DWORD dwSeconds);
- self.WeatherSetModeServer = self.SimConnect.SimConnect_WeatherSetModeServer
- self.WeatherSetModeServer.restype = HRESULT
- self.WeatherSetModeServer.argtypes = [HANDLE, DWORD, DWORD]
-
- # SIMCONNECTAPI SimConnect_WeatherSetModeTheme(
- # HANDLE hSimConnect,
- # const char * szThemeName);
- self.WeatherSetModeTheme = self.SimConnect.SimConnect_WeatherSetModeTheme
- self.WeatherSetModeTheme.restype = HRESULT
- self.WeatherSetModeTheme.argtypes = [HANDLE, c_char_p]
-
- # SIMCONNECTAPI SimConnect_WeatherSetModeGlobal(
- # HANDLE hSimConnect);
- self.WeatherSetModeGlobal = self.SimConnect.SimConnect_WeatherSetModeGlobal
- self.WeatherSetModeGlobal.restype = HRESULT
- self.WeatherSetModeGlobal.argtypes = [HANDLE]
-
- # SIMCONNECTAPI SimConnect_WeatherSetModeCustom(
- # HANDLE hSimConnect);
- self.WeatherSetModeCustom = self.SimConnect.SimConnect_WeatherSetModeCustom
- self.WeatherSetModeCustom.restype = HRESULT
- self.WeatherSetModeCustom.argtypes = [HANDLE]
-
- # SIMCONNECTAPI SimConnect_WeatherSetDynamicUpdateRate(
- # HANDLE hSimConnect,
- # DWORD dwRate);
- self.WeatherSetDynamicUpdateRate = (
- self.SimConnect.SimConnect_WeatherSetDynamicUpdateRate
- )
- self.WeatherSetDynamicUpdateRate.restype = HRESULT
- self.WeatherSetDynamicUpdateRate.argtypes = [HANDLE, DWORD]
-
- # SIMCONNECTAPI SimConnect_WeatherRequestCloudState(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_REQUEST_ID RequestID
- # float minLat
- # float minLon
- # float minAlt
- # float maxLat
- # float maxLon
- # float maxAlt
- # DWORD dwFlags = 0);
- self.WeatherRequestCloudState = (
- self.SimConnect.SimConnect_WeatherRequestCloudState
- )
- self.WeatherRequestCloudState.restype = HRESULT
- self.WeatherRequestCloudState.argtypes = [
- HANDLE,
- self.DATA_REQUEST_ID,
- c_float,
- c_float,
- c_float,
- c_float,
- c_float,
- c_float,
- DWORD,
- ]
-
- # SIMCONNECTAPI SimConnect_WeatherCreateThermal(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_REQUEST_ID RequestID
- # float lat
- # float lon
- # float alt
- # float radius
- # float height
- # float coreRate = 3.0f
- # float coreTurbulence = 0.05f
- # float sinkRate = 3.0f
- # float sinkTurbulence = 0.2f
- # float coreSize = 0.4f
- # float coreTransitionSize = 0.1f
- # float sinkLayerSize = 0.4f
- # float sinkTransitionSize = 0.1f);
- self.WeatherCreateThermal = self.SimConnect.SimConnect_WeatherCreateThermal
- self.WeatherCreateThermal.restype = HRESULT
- self.WeatherCreateThermal.argtypes = [
- HANDLE,
- self.DATA_REQUEST_ID,
- c_float,
- c_float,
- c_float,
- c_float,
- c_float,
- c_float,
- c_float,
- c_float,
- c_float,
- c_float,
- c_float,
- c_float,
- c_float,
- ]
-
- # SIMCONNECTAPI SimConnect_WeatherRemoveThermal(
- # HANDLE hSimConnect,
- # SIMCONNECT_OBJECT_ID ObjectID);
- self.WeatherRemoveThermal = self.SimConnect.SimConnect_WeatherRemoveThermal
- self.WeatherRemoveThermal.restype = HRESULT
- self.WeatherRemoveThermal.argtypes = [HANDLE, SIMCONNECT_OBJECT_ID]
-
- # SIMCONNECTAPI SimConnect_AICreateParkedATCAircraft(
- # HANDLE hSimConnect,
- # const char * szContainerTitle
- # const char * szTailNumber
- # const char * szAirportID
- # SIMCONNECT_DATA_REQUEST_ID RequestID);
- self.AICreateParkedATCAircraft = (
- self.SimConnect.SimConnect_AICreateParkedATCAircraft
- )
- self.AICreateParkedATCAircraft.restype = HRESULT
- self.AICreateParkedATCAircraft.argtypes = [
- HANDLE,
- c_char_p,
- c_char_p,
- c_char_p,
- self.DATA_REQUEST_ID,
- ]
-
- # SIMCONNECTAPI SimConnect_AICreateEnrouteATCAircraft(
- # HANDLE hSimConnect,
- # const char * szContainerTitle
- # const char * szTailNumber
- # int iFlightNumber
- # const char * szFlightPlanPath
- # double dFlightPlanPosition
- # BOOL bTouchAndGo
- # SIMCONNECT_DATA_REQUEST_ID RequestID);
- self.AICreateEnrouteATCAircraft = (
- self.SimConnect.SimConnect_AICreateEnrouteATCAircraft
- )
- self.AICreateEnrouteATCAircraft.restype = HRESULT
- self.AICreateEnrouteATCAircraft.argtypes = [
- HANDLE,
- c_char_p,
- c_char_p,
- c_int,
- c_char_p,
- c_double,
- c_bool,
- self.DATA_REQUEST_ID,
- ]
-
- # SIMCONNECTAPI SimConnect_AICreateNonATCAircraft(
- # HANDLE hSimConnect,
- # const char * szContainerTitle
- # const char * szTailNumber
- # SIMCONNECT_DATA_INITPOSITION InitPos
- # SIMCONNECT_DATA_REQUEST_ID RequestID);
- self.AICreateNonATCAircraft = (
- self.SimConnect.SimConnect_AICreateNonATCAircraft
- )
- self.AICreateNonATCAircraft.restype = HRESULT
- self.AICreateNonATCAircraft.argtypes = [
- HANDLE,
- c_char_p,
- c_char_p,
- SIMCONNECT_DATA_INITPOSITION,
- self.DATA_REQUEST_ID,
- ]
-
- # SIMCONNECTAPI SimConnect_AICreateSimulatedObject(
- # HANDLE hSimConnect,
- # const char * szContainerTitle
- # SIMCONNECT_DATA_INITPOSITION InitPos
- # SIMCONNECT_DATA_REQUEST_ID RequestID);
- self.AICreateSimulatedObject = (
- self.SimConnect.SimConnect_AICreateSimulatedObject
- )
- self.AICreateSimulatedObject.restype = HRESULT
- self.AICreateSimulatedObject.argtypes = [
- HANDLE,
- c_char_p,
- SIMCONNECT_DATA_INITPOSITION,
- self.DATA_REQUEST_ID,
- ]
-
- # SIMCONNECTAPI SimConnect_AIReleaseControl(
- # HANDLE hSimConnect,
- # SIMCONNECT_OBJECT_ID ObjectID
- # SIMCONNECT_DATA_REQUEST_ID RequestID);
- self.AIReleaseControl = self.SimConnect.SimConnect_AIReleaseControl
- self.AIReleaseControl.restype = HRESULT
- self.AIReleaseControl.argtypes = [
- HANDLE,
- SIMCONNECT_OBJECT_ID,
- self.DATA_REQUEST_ID,
- ]
-
- # SIMCONNECTAPI SimConnect_AIRemoveObject(
- # HANDLE hSimConnect,
- # SIMCONNECT_OBJECT_ID ObjectID
- # SIMCONNECT_DATA_REQUEST_ID RequestID);
- self.AIRemoveObject = self.SimConnect.SimConnect_AIRemoveObject
- self.AIRemoveObject.restype = HRESULT
- self.AIRemoveObject.argtypes = [
- HANDLE,
- SIMCONNECT_OBJECT_ID,
- self.DATA_REQUEST_ID,
- ]
-
- # SIMCONNECTAPI SimConnect_AISetAircraftFlightPlan(
- # HANDLE hSimConnect,
- # SIMCONNECT_OBJECT_ID ObjectID
- # const char * szFlightPlanPath
- # SIMCONNECT_DATA_REQUEST_ID RequestID);
- self.AISetAircraftFlightPlan = (
- self.SimConnect.SimConnect_AISetAircraftFlightPlan
- )
- self.AISetAircraftFlightPlan.restype = HRESULT
- self.AISetAircraftFlightPlan.argtypes = [
- HANDLE,
- SIMCONNECT_OBJECT_ID,
- c_char_p,
- self.DATA_REQUEST_ID,
- ]
-
- # SIMCONNECTAPI SimConnect_ExecuteMissionAction(
- # HANDLE hSimConnect,
- # const GUID guidInstanceId);
- self.ExecuteMissionAction = self.SimConnect.SimConnect_ExecuteMissionAction
- self.ExecuteMissionAction.restype = HRESULT
- self.ExecuteMissionAction.argtypes = []
-
- # SIMCONNECTAPI SimConnect_CompleteCustomMissionAction(
- # HANDLE hSimConnect,
- # const GUID guidInstanceId);
- self.CompleteCustomMissionAction = (
- self.SimConnect.SimConnect_CompleteCustomMissionAction
- )
- self.CompleteCustomMissionAction.restype = HRESULT
- self.CompleteCustomMissionAction.argtypes = []
-
- # SIMCONNECTAPI SimConnect_RetrieveString(
- # SIMCONNECT_RECV * pData,
- # DWORD cbData
- # void * pStringV
- # char ** pszString
- # DWORD * pcbString);
- self.RetrieveString = self.SimConnect.SimConnect_RetrieveString
- self.RetrieveString.restype = HRESULT
- self.RetrieveString.argtypes = []
-
- # SIMCONNECTAPI SimConnect_GetLastSentPacketID(
- # HANDLE hSimConnect,
- # DWORD * pdwError);
- self.GetLastSentPacketID = self.SimConnect.SimConnect_GetLastSentPacketID
- self.GetLastSentPacketID.restype = HRESULT
- self.GetLastSentPacketID.argtypes = [HANDLE, POINTER(DWORD)]
-
- # SIMCONNECTAPI SimConnect_GetNextDispatch(
- # HANDLE hSimConnect,
- # SIMCONNECT_RECV ** ppData
- # DWORD * pcbData);
- self.GetNextDispatch = self.SimConnect.SimConnect_GetNextDispatch
- self.GetNextDispatch.restype = HRESULT
- self.GetNextDispatch.argtypes = []
-
- # SIMCONNECTAPI SimConnect_RequestResponseTimes(
- # HANDLE hSimConnect,
- # DWORD nCount
- # float * fElapsedSeconds);
- self.RequestResponseTimes = self.SimConnect.SimConnect_RequestResponseTimes
- self.RequestResponseTimes.restype = HRESULT
- self.RequestResponseTimes.argtypes = [
- HANDLE,
- DWORD,
- c_float
- ]
-
- # SIMCONNECTAPI SimConnect_InsertString(
- # char * pDest,
- # DWORD cbDest
- # void ** ppEnd
- # DWORD * pcbStringV
- # const char * pSource);
- self.InsertString = self.SimConnect.SimConnect_InsertString
- self.InsertString.restype = HRESULT
- self.InsertString.argtypes = []
-
- # SIMCONNECTAPI SimConnect_CameraSetRelative6DOF(
- # HANDLE hSimConnect,
- # float fDeltaX
- # float fDeltaY
- # float fDeltaZ
- # float fPitchDeg
- # float fBankDeg
- # float fHeadingDeg);
- self.CameraSetRelative6DOF = self.SimConnect.SimConnect_CameraSetRelative6DOF
- self.CameraSetRelative6DOF.restype = HRESULT
- self.CameraSetRelative6DOF.argtypes = [
- c_float,
- c_float,
- c_float,
- c_float,
- c_float,
- c_float
- ]
-
- # SIMCONNECTAPI SimConnect_MenuAddItem(
- # HANDLE hSimConnect,
- # const char * szMenuItem
- # SIMCONNECT_CLIENT_EVENT_ID MenuEventID
- # DWORD dwData);
- self.MenuAddItem = self.SimConnect.SimConnect_MenuAddItem
- self.MenuAddItem.restype = HRESULT
- self.MenuAddItem.argtypes = [
- HANDLE,
- SIMCONNECT_CLIENT_EVENT_ID,
- DWORD
- ]
-
- # SIMCONNECTAPI SimConnect_MenuDeleteItem(
- # HANDLE hSimConnect,
- # SIMCONNECT_CLIENT_EVENT_ID MenuEventID);
- self.MenuDeleteItem = self.SimConnect.SimConnect_MenuDeleteItem
- self.MenuDeleteItem.restype = HRESULT
- self.MenuDeleteItem.argtypes = [
- HANDLE,
- SIMCONNECT_CLIENT_EVENT_ID
- ]
-
- # SIMCONNECTAPI SimConnect_MenuAddSubItem(
- # HANDLE hSimConnect,
- # SIMCONNECT_CLIENT_EVENT_ID MenuEventID
- # const char * szMenuItem
- # SIMCONNECT_CLIENT_EVENT_ID SubMenuEventID
- # DWORD dwData);
- self.MenuAddSubItem = self.SimConnect.SimConnect_MenuAddSubItem
- self.MenuAddSubItem.restype = HRESULT
- self.MenuAddSubItem.argtypes = [
- HANDLE,
- SIMCONNECT_CLIENT_EVENT_ID,
- c_char_p,
- SIMCONNECT_CLIENT_EVENT_ID,
- DWORD
- ]
-
- # SIMCONNECTAPI SimConnect_MenuDeleteSubItem(
- # HANDLE hSimConnect,
- # SIMCONNECT_CLIENT_EVENT_ID MenuEventID
- # const SIMCONNECT_CLIENT_EVENT_ID SubMenuEventID);
- self.MenuDeleteSubItem = self.SimConnect.SimConnect_MenuDeleteSubItem
- self.MenuDeleteSubItem.restype = HRESULT
- self.MenuDeleteSubItem.argtypes = [
- HANDLE,
- SIMCONNECT_CLIENT_EVENT_ID,
- SIMCONNECT_CLIENT_EVENT_ID
- ]
-
- # SIMCONNECTAPI SimConnect_RequestSystemState(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_REQUEST_ID RequestID
- # const char * szState);
- self.RequestSystemState = self.SimConnect.SimConnect_RequestSystemState
- self.RequestSystemState.restype = HRESULT
- self.RequestSystemState.argtypes = [
- HANDLE,
- SIMCONNECT_DATA_REQUEST_ID,
- c_char_p
- ]
-
- # SIMCONNECTAPI SimConnect_SetSystemState(
- # HANDLE hSimConnect,
- # const char * szState
- # DWORD dwInteger
- # float fFloat
- # const char * szString);
- self.SetSystemState = self.SimConnect.SimConnect_SetSystemState
- self.SetSystemState.restype = HRESULT
- self.SetSystemState.argtypes = [
- HANDLE,
- c_char_p,
- DWORD,
- c_float,
- c_char_p
- ]
-
- # SIMCONNECTAPI SimConnect_MapClientDataNameToID(
- # HANDLE hSimConnect,
- # const char * szClientDataName
- # SIMCONNECT_CLIENT_DATA_ID ClientDataID);
- self.MapClientDataNameToID = self.SimConnect.SimConnect_MapClientDataNameToID
- self.MapClientDataNameToID.restype = HRESULT
- self.MapClientDataNameToID.argtypes = [HANDLE, c_char_p, SIMCONNECT_CLIENT_DATA_ID]
-
- # SIMCONNECTAPI SimConnect_CreateClientData(
- # HANDLE hSimConnect,
- # SIMCONNECT_CLIENT_DATA_ID ClientDataID
- # DWORD dwSize
- # SIMCONNECT_CREATE_CLIENT_DATA_FLAG Flags);
- self.CreateClientData = self.SimConnect.SimConnect_CreateClientData
- self.CreateClientData.restype = HRESULT
- self.CreateClientData.argtypes = [
- HANDLE,
- self.CLIENT_DATA_ID,
- DWORD,
- SIMCONNECT_CREATE_CLIENT_DATA_FLAG,
- ]
-
- # SIMCONNECTAPI SimConnect_AddToClientDataDefinition(
- # HANDLE hSimConnect,
- # SIMCONNECT_CLIENT_DATA_DEFINITION_ID DefineID
- # DWORD dwOffset
- # DWORD dwSizeOrType
- # float fEpsilon = 0
- # DWORD DatumID = SIMCONNECT_UNUSED);
- self.AddToClientDataDefinition = self.SimConnect.SimConnect_AddToClientDataDefinition
- self.AddToClientDataDefinition.restype = HRESULT
- self.AddToClientDataDefinition.argtypes = [
- HANDLE,
- self.CLIENT_DATA_DEFINITION_ID,
- DWORD,
- DWORD,
- c_float,
- DWORD,
- ]
-
- # SIMCONNECTAPI SimConnect_ClearClientDataDefinition(
- # HANDLE hSimConnect,
- # SIMCONNECT_CLIENT_DATA_DEFINITION_ID DefineID);
- self.ClearClientDataDefinition = self.SimConnect.SimConnect_ClearClientDataDefinition
- self.ClearClientDataDefinition.restype = HRESULT
- self.ClearClientDataDefinition.argtypes = [
- HANDLE,
- self.CLIENT_DATA_DEFINITION_ID,
- ]
-
- # SIMCONNECTAPI SimConnect_RequestClientData(
- # HANDLE hSimConnect,
- # SIMCONNECT_CLIENT_DATA_ID ClientDataID
- # SIMCONNECT_DATA_REQUEST_ID RequestID
- # SIMCONNECT_CLIENT_DATA_DEFINITION_ID DefineID
- # SIMCONNECT_CLIENT_DATA_PERIOD Period = SIMCONNECT_CLIENT_DATA_PERIOD_ONCE
- # SIMCONNECT_CLIENT_DATA_REQUEST_FLAG Flags = 0
- # DWORD origin = 0
- # DWORD interval = 0
- # DWORD limit = 0);
- self.RequestClientData = self.SimConnect.SimConnect_RequestClientData
- self.RequestClientData.restype = HRESULT
- self.RequestClientData.argtypes = [
- HANDLE,
- self.CLIENT_DATA_ID,
- self.DATA_REQUEST_ID,
- self.CLIENT_DATA_DEFINITION_ID,
- SIMCONNECT_CLIENT_DATA_PERIOD,
- SIMCONNECT_CLIENT_DATA_REQUEST_FLAG,
- DWORD,
- DWORD,
- DWORD,
- ]
-
- # https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_SetClientData.htm
- # SIMCONNECTAPI SimConnect_SetClientData(
- # HANDLE hSimConnect,
- # SIMCONNECT_CLIENT_DATA_ID ClientDataID
- # SIMCONNECT_CLIENT_DATA_DEFINITION_ID DefineID
- # SIMCONNECT_CLIENT_DATA_SET_FLAG Flags
- # DWORD dwReserved set to 0
- # DWORD cbUnitSize
- # void * pDataSet);
- self.SetClientData = self.SimConnect.SimConnect_SetClientData
- self.SetClientData.restype = HRESULT
- self.SetClientData.argtypes = [
- HANDLE,
- self.CLIENT_DATA_ID,
- self.CLIENT_DATA_DEFINITION_ID,
- SIMCONNECT_CLIENT_DATA_SET_FLAG,
- DWORD,
- DWORD,
- c_void_p,
- ]
-
- # SIMCONNECTAPI SimConnect_FlightLoad(
- # HANDLE hSimConnect,
- # const char * szFileName);
- self.FlightLoad = self.SimConnect.SimConnect_FlightLoad
- self.FlightLoad.restype = HRESULT
- self.FlightLoad.argtypes = [HANDLE, c_char_p]
-
- # SIMCONNECTAPI SimConnect_FlightSave(
- # HANDLE hSimConnect,
- # const char * szFileName
- # const char * szTitle
- # const char * szDescription
- # DWORD Flags);
- self.FlightSave = self.SimConnect.SimConnect_FlightSave
- self.FlightSave.restype = HRESULT
- self.FlightSave.argtypes = [HANDLE, c_char_p, c_char_p, c_char_p, DWORD]
-
- # SIMCONNECTAPI SimConnect_FlightPlanLoad(
- # HANDLE hSimConnect,
- # const char * szFileName);
- self.FlightPlanLoad = self.SimConnect.SimConnect_FlightPlanLoad
- self.FlightPlanLoad.restype = HRESULT
- self.FlightPlanLoad.argtypes = [HANDLE, c_char_p]
-
- # SIMCONNECTAPI SimConnect_Text(
- # HANDLE hSimConnect,
- # SIMCONNECT_TEXT_TYPE type
- # float fTimeSeconds
- # SIMCONNECT_CLIENT_EVENT_ID EventID
- # DWORD cbUnitSize
- # void * pDataSet);
- self.Text = self.SimConnect.SimConnect_Text
- self.Text.restype = HRESULT
- self.Text.argtypes = [
- HANDLE,
- SIMCONNECT_TEXT_TYPE,
- c_float,
- self.EventID,
- DWORD,
- c_void_p,
- ]
-
- # SIMCONNECTAPI SimConnect_SubscribeToFacilities(
- # HANDLE hSimConnect,
- # SIMCONNECT_FACILITY_LIST_TYPE type
- # SIMCONNECT_DATA_REQUEST_ID RequestID);
- self.SubscribeToFacilities = self.SimConnect.SimConnect_SubscribeToFacilities
- self.SubscribeToFacilities.restype = HRESULT
- self.SubscribeToFacilities.argtypes = [
- HANDLE,
- SIMCONNECT_FACILITY_LIST_TYPE,
- self.DATA_REQUEST_ID,
- ]
-
- # SIMCONNECTAPI SimConnect_UnsubscribeToFacilities(
- # HANDLE hSimConnect,
- # SIMCONNECT_FACILITY_LIST_TYPE type);
- self.UnsubscribeToFacilities = (
- self.SimConnect.SimConnect_UnsubscribeToFacilities
- )
- self.UnsubscribeToFacilities.restype = HRESULT
- self.UnsubscribeToFacilities.argtypes = [
- HANDLE,
- SIMCONNECT_FACILITY_LIST_TYPE,
- ]
-
- # SIMCONNECTAPI SimConnect_RequestFacilitiesList(
- # HANDLE hSimConnect,
- # SIMCONNECT_FACILITY_LIST_TYPE type
- # SIMCONNECT_DATA_REQUEST_ID RequestID);
- self.RequestFacilitiesList = (
- self.SimConnect.SimConnect_RequestFacilitiesList
- )
- self.RequestFacilitiesList.restype = HRESULT
- self.RequestFacilitiesList.argtypes = [
- HANDLE,
- SIMCONNECT_FACILITY_LIST_TYPE,
- self.DATA_REQUEST_ID,
- ]
-
- # SIMCONNECTAPI SimConnect_EnumerateSimObjectsAndLiveries(
- # HANDLE hSimConnect,
- # SIMCONNECT_DATA_REQUEST_ID RequestID,
- # SIMCONNECT_SIMOBJECT_TYPE Type);
- self.EnumerateSimObjectsAndLiveries = (self.SimConnect.SimConnect_EnumerateSimObjectsAndLiveries)
- self.EnumerateSimObjectsAndLiveries.restype = HRESULT
- self.EnumerateSimObjectsAndLiveries.argtypes = [
- HANDLE,
- self.DATA_REQUEST_ID,
- SIMCONNECT_SIMOBJECT_TYPE,
- ]
-
+ def __init__(self, simconnect_dll):
+ self.EventID = SIMCONNECT_CLIENT_EVENT_ID
+ self.DATA_DEFINITION_ID = SIMCONNECT_DATA_DEFINITION_ID
+ self.DATA_REQUEST_ID = SIMCONNECT_DATA_REQUEST_ID
+ self.GROUP_ID = SIMCONNECT_NOTIFICATION_GROUP_ID
+ self.INPUT_GROUP_ID = SIMCONNECT_INPUT_GROUP_ID
+ self.CLIENT_DATA_ID = SIMCONNECT_CLIENT_DATA_ID
+ self.CLIENT_DATA_DEFINITION_ID = SIMCONNECT_CLIENT_DATA_DEFINITION_ID
+
+ self.SimConnect = simconnect_dll
+
+ # SIMCONNECTAPI SimConnect_Open(
+ # HANDLE * phSimConnect,
+ # LPCSTR szName,
+ # HWND hWnd,
+ # DWORD UserEventWin32,
+ # HANDLE hEventHandle,
+ # DWORD ConfigIndex)
+
+ self.Open = self.SimConnect.SimConnect_Open
+ self.Open.restype = HRESULT
+ self.Open.argtypes = [POINTER(HANDLE), LPCSTR, HWND, DWORD, HANDLE, DWORD]
+
+ # SIMCONNECTAPI SimConnect_Close(
+ # HANDLE hSimConnect);
+
+ self.Close = self.SimConnect.SimConnect_Close
+ self.Close.restype = HRESULT
+ self.Close.argtypes = [HANDLE]
+
+ # SIMCONNECTAPI SimConnect_AddToDataDefinition(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_DEFINITION_ID DefineID,
+ # const char * DatumName,
+ # const char * UnitsName,
+ # SIMCONNECT_DATATYPE DatumType = SIMCONNECT_DATATYPE_FLOAT64,
+ # float fEpsilon = 0,
+ # DWORD DatumID = SIMCONNECT_UNUSED);
+
+ self.AddToDataDefinition = self.SimConnect.SimConnect_AddToDataDefinition
+ self.AddToDataDefinition.restype = HRESULT
+ self.AddToDataDefinition.argtypes = [
+ HANDLE,
+ self.DATA_DEFINITION_ID,
+ c_char_p,
+ c_char_p,
+ SIMCONNECT_DATATYPE,
+ c_float,
+ DWORD,
+ ]
+
+ # SIMCONNECTAPI SimConnect_SubscribeToSystemEvent(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_CLIENT_EVENT_ID EventID,
+ # const char * SystemEventName);
+
+ self.SubscribeToSystemEvent = self.SimConnect.SimConnect_SubscribeToSystemEvent
+ self.SubscribeToSystemEvent.restype = HRESULT
+ self.SubscribeToSystemEvent.argtypes = [HANDLE, self.EventID, c_char_p]
+
+ # SIMCONNECTAPI SimConnect_CallDispatch(
+ # HANDLE hSimConnect,
+ # DispatchProc pfcnDispatch,
+ # void * pContext);
+
+ self.DispatchProc = WINFUNCTYPE(None, POINTER(SIMCONNECT_RECV), DWORD, c_void_p)
+
+ self.CallDispatch = self.SimConnect.SimConnect_CallDispatch
+ self.CallDispatch.restype = HRESULT
+ self.CallDispatch.argtypes = [HANDLE, self.DispatchProc, c_void_p]
+
+ # SIMCONNECTAPI SimConnect_RequestDataOnSimObjectType(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_REQUEST_ID RequestID,
+ # SIMCONNECT_DATA_DEFINITION_ID DefineID,
+ # DWORD dwRadiusMeters,
+ # SIMCONNECT_SIMOBJECT_TYPE type);
+
+ self.RequestDataOnSimObjectType = (
+ self.SimConnect.SimConnect_RequestDataOnSimObjectType
+ )
+ self.RequestDataOnSimObjectType.restype = HRESULT
+ self.RequestDataOnSimObjectType.argtypes = [
+ HANDLE,
+ self.DATA_REQUEST_ID,
+ self.DATA_DEFINITION_ID,
+ DWORD,
+ SIMCONNECT_SIMOBJECT_TYPE,
+ ]
+
+ # SIMCONNECTAPI SimConnect_TransmitClientEvent(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_OBJECT_ID ObjectID,
+ # SIMCONNECT_CLIENT_EVENT_ID EventID,
+ # DWORD dwData,
+ # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID,
+ # SIMCONNECT_EVENT_FLAG Flags);
+
+ self.TransmitClientEvent = self.SimConnect.SimConnect_TransmitClientEvent
+ self.TransmitClientEvent.restype = HRESULT
+ self.TransmitClientEvent.argtypes = [
+ HANDLE,
+ SIMCONNECT_OBJECT_ID,
+ self.EventID,
+ DWORD,
+ DWORD,
+ DWORD,
+ ]
+
+ # SIMCONNECTAPI SimConnect_MapClientEventToSimEvent(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_CLIENT_EVENT_ID EventID,
+ # const char * EventName = "");
+
+ self.MapClientEventToSimEvent = (
+ self.SimConnect.SimConnect_MapClientEventToSimEvent
+ )
+ self.MapClientEventToSimEvent.restype = HRESULT
+ self.MapClientEventToSimEvent.argtypes = [HANDLE, self.EventID, c_char_p]
+
+ # SIMCONNECTAPI SimConnect_AddClientEventToNotificationGroup(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID,
+ # SIMCONNECT_CLIENT_EVENT_ID EventID,
+ # BOOL bMaskable = FALSE);
+
+ self.AddClientEventToNotificationGroup = (
+ self.SimConnect.SimConnect_AddClientEventToNotificationGroup
+ )
+ self.AddClientEventToNotificationGroup.restype = HRESULT
+ self.AddClientEventToNotificationGroup.argtypes = [
+ HANDLE,
+ self.GROUP_ID,
+ self.EventID,
+ c_bool,
+ ]
+
+ # SIMCONNECTAPI SimConnect_SetSystemEventState(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_CLIENT_EVENT_ID EventID
+ # SIMCONNECT_STATE dwState);
+ self.SetSystemEventState = self.SimConnect.SimConnect_SetSystemEventState
+ self.SetSystemEventState.restype = HRESULT
+ self.SetSystemEventState.argtypes = [HANDLE, self.EventID, SIMCONNECT_STATE]
+
+ # SIMCONNECTAPI SimConnect_AddClientEventToNotificationGroup(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID
+ # SIMCONNECT_CLIENT_EVENT_ID EventID
+ # BOOL bMaskable = FALSE);
+ self.AddClientEventToNotificationGroup = (
+ self.SimConnect.SimConnect_AddClientEventToNotificationGroup
+ )
+ self.AddClientEventToNotificationGroup.restype = HRESULT
+ self.AddClientEventToNotificationGroup.argtypes = [
+ HANDLE,
+ self.GROUP_ID,
+ self.EventID,
+ c_bool,
+ ]
+
+ # SIMCONNECTAPI SimConnect_RemoveClientEvent(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID
+ # SIMCONNECT_CLIENT_EVENT_ID EventID);
+ self.RemoveClientEvent = self.SimConnect.SimConnect_RemoveClientEvent
+ self.RemoveClientEvent.restype = HRESULT
+ self.RemoveClientEvent.argtypes = [HANDLE, self.GROUP_ID, self.EventID]
+
+ # SIMCONNECTAPI SimConnect_SetNotificationGroupPriority(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID
+ # DWORD uPriority);
+ self.SetNotificationGroupPriority = (
+ self.SimConnect.SimConnect_SetNotificationGroupPriority
+ )
+ self.SetNotificationGroupPriority.restype = HRESULT
+ self.SetNotificationGroupPriority.argtypes = [HANDLE, self.GROUP_ID, DWORD]
+
+ # SIMCONNECTAPI SimConnect_ClearNotificationGroup(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID);
+ self.ClearNotificationGroup = self.SimConnect.SimConnect_ClearNotificationGroup
+ self.ClearNotificationGroup.restype = HRESULT
+ self.ClearNotificationGroup.argtypes = [HANDLE, self.GROUP_ID]
+
+ # SIMCONNECTAPI SimConnect_RequestNotificationGroup(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_NOTIFICATION_GROUP_ID GroupID
+ # DWORD dwReserved = 0
+ # DWORD Flags = 0);
+ self.RequestNotificationGroup = (
+ self.SimConnect.SimConnect_RequestNotificationGroup
+ )
+ self.RequestNotificationGroup.restype = HRESULT
+ self.RequestNotificationGroup.argtypes = [HANDLE, self.GROUP_ID, DWORD, DWORD]
+
+ # SIMCONNECTAPI SimConnect_ClearDataDefinition(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_DEFINITION_ID DefineID);
+ self.ClearDataDefinition = self.SimConnect.SimConnect_ClearDataDefinition
+ self.ClearDataDefinition.restype = HRESULT
+ self.ClearDataDefinition.argtypes = [HANDLE, self.DATA_DEFINITION_ID]
+
+ # SIMCONNECTAPI SimConnect_RequestDataOnSimObject(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_REQUEST_ID RequestID
+ # SIMCONNECT_DATA_DEFINITION_ID DefineID
+ # SIMCONNECT_OBJECT_ID ObjectID
+ # SIMCONNECT_PERIOD Period
+ # SIMCONNECT_DATA_REQUEST_FLAG Flags = 0
+ # DWORD origin = 0
+ # DWORD interval = 0
+ # DWORD limit = 0);
+ self.RequestDataOnSimObject = self.SimConnect.SimConnect_RequestDataOnSimObject
+ self.RequestDataOnSimObject.restype = HRESULT
+ self.RequestDataOnSimObject.argtypes = [
+ HANDLE,
+ self.DATA_REQUEST_ID,
+ self.DATA_DEFINITION_ID,
+ SIMCONNECT_OBJECT_ID,
+ SIMCONNECT_PERIOD,
+ SIMCONNECT_DATA_REQUEST_FLAG,
+ DWORD,
+ DWORD,
+ DWORD,
+ ]
+
+ # SIMCONNECTAPI SimConnect_SetDataOnSimObject(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_DEFINITION_ID DefineID
+ # SIMCONNECT_OBJECT_ID ObjectID
+ # SIMCONNECT_DATA_SET_FLAG Flags
+ # DWORD ArrayCount
+ # DWORD cbUnitSize
+ # void * pDataSet);
+ self.SetDataOnSimObject = self.SimConnect.SimConnect_SetDataOnSimObject
+ self.SetDataOnSimObject.restype = HRESULT
+ self.SetDataOnSimObject.argtypes = [
+ HANDLE,
+ self.DATA_DEFINITION_ID,
+ SIMCONNECT_OBJECT_ID,
+ SIMCONNECT_DATA_SET_FLAG,
+ DWORD,
+ DWORD,
+ c_void_p,
+ ]
+
+ # SIMCONNECTAPI SimConnect_MapInputEventToClientEvent(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_INPUT_GROUP_ID GroupID
+ # const char * szInputDefinition
+ # SIMCONNECT_CLIENT_EVENT_ID DownEventID
+ # DWORD DownValue = 0
+ # SIMCONNECT_CLIENT_EVENT_ID UpEventID = (SIMCONNECT_CLIENT_EVENT_ID)SIMCONNECT_UNUSED
+ # DWORD UpValue = 0
+ # BOOL bMaskable = FALSE);
+ self.MapInputEventToClientEvent = (
+ self.SimConnect.SimConnect_MapInputEventToClientEvent
+ )
+ self.MapInputEventToClientEvent.restype = HRESULT
+ self.MapInputEventToClientEvent.argtypes = [
+ HANDLE,
+ self.INPUT_GROUP_ID,
+ c_char_p,
+ self.EventID,
+ DWORD,
+ self.EventID,
+ DWORD,
+ c_bool,
+ ]
+
+ # SIMCONNECTAPI SimConnect_SetInputGroupPriority(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_INPUT_GROUP_ID GroupID
+ # DWORD uPriority);
+ self.SetInputGroupPriority = self.SimConnect.SimConnect_SetInputGroupPriority
+ self.SetInputGroupPriority.restype = HRESULT
+ self.SetInputGroupPriority.argtypes = [HANDLE, self.INPUT_GROUP_ID, DWORD]
+
+ # SIMCONNECTAPI SimConnect_RemoveInputEvent(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_INPUT_GROUP_ID GroupID
+ # const char * szInputDefinition);
+ self.RemoveInputEvent = self.SimConnect.SimConnect_RemoveInputEvent
+ self.RemoveInputEvent.restype = HRESULT
+ self.RemoveInputEvent.argtypes = [HANDLE, self.INPUT_GROUP_ID, c_char_p]
+
+ # SIMCONNECTAPI SimConnect_ClearInputGroup(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_INPUT_GROUP_ID GroupID);
+ self.ClearInputGroup = self.SimConnect.SimConnect_ClearInputGroup
+ self.ClearInputGroup.restype = HRESULT
+ self.ClearInputGroup.argtypes = [HANDLE, self.INPUT_GROUP_ID]
+
+ # SIMCONNECTAPI SimConnect_SetInputGroupState(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_INPUT_GROUP_ID GroupID
+ # DWORD dwState);
+ self.SetInputGroupState = self.SimConnect.SimConnect_SetInputGroupState
+ self.SetInputGroupState.restype = HRESULT
+ self.SetInputGroupState.argtypes = [HANDLE, self.INPUT_GROUP_ID, DWORD]
+
+ # SIMCONNECTAPI SimConnect_RequestReservedKey(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_CLIENT_EVENT_ID EventID
+ # const char * szKeyChoice1 = ""
+ # const char * szKeyChoice2 = ""
+ # const char * szKeyChoice3 = "");
+ self.RequestReservedKey = self.SimConnect.SimConnect_RequestReservedKey
+ self.RequestReservedKey.restype = HRESULT
+ self.RequestReservedKey.argtypes = [
+ HANDLE,
+ self.EventID,
+ c_char_p,
+ c_char_p,
+ c_char_p,
+ ]
+
+ # SIMCONNECTAPI SimConnect_UnsubscribeFromSystemEvent(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_CLIENT_EVENT_ID EventID);
+ self.UnsubscribeFromSystemEvent = (
+ self.SimConnect.SimConnect_UnsubscribeFromSystemEvent
+ )
+ self.UnsubscribeFromSystemEvent.restype = HRESULT
+ self.UnsubscribeFromSystemEvent.argtypes = [HANDLE, self.EventID]
+
+ # SIMCONNECTAPI SimConnect_WeatherRequestInterpolatedObservation(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_REQUEST_ID RequestID
+ # float lat
+ # float lon
+ # float alt);
+ self.WeatherRequestInterpolatedObservation = (
+ self.SimConnect.SimConnect_WeatherRequestInterpolatedObservation
+ )
+ self.WeatherRequestInterpolatedObservation.restype = HRESULT
+ self.WeatherRequestInterpolatedObservation.argtypes = [
+ HANDLE,
+ self.DATA_REQUEST_ID,
+ c_float,
+ c_float,
+ c_float,
+ ]
+
+ # SIMCONNECTAPI SimConnect_WeatherRequestObservationAtStation(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_REQUEST_ID RequestID
+ # const char * szICAO);
+ self.WeatherRequestObservationAtStation = (
+ self.SimConnect.SimConnect_WeatherRequestObservationAtStation
+ )
+ self.WeatherRequestObservationAtStation.restype = HRESULT
+ self.WeatherRequestObservationAtStation.argtypes = [
+ HANDLE,
+ self.DATA_REQUEST_ID,
+ c_char_p,
+ ]
+
+ # SIMCONNECTAPI SimConnect_WeatherRequestObservationAtNearestStation(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_REQUEST_ID RequestID
+ # float lat
+ # float lon);
+ self.WeatherRequestObservationAtNearestStation = (
+ self.SimConnect.SimConnect_WeatherRequestObservationAtNearestStation
+ )
+ self.WeatherRequestObservationAtNearestStation.restype = HRESULT
+ self.WeatherRequestObservationAtNearestStation.argtypes = [
+ HANDLE,
+ self.DATA_REQUEST_ID,
+ c_float,
+ c_float,
+ ]
+
+ # SIMCONNECTAPI SimConnect_WeatherCreateStation(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_REQUEST_ID RequestID
+ # const char * szICAO
+ # const char * szName
+ # float lat
+ # float lon
+ # float alt);
+ self.WeatherCreateStation = self.SimConnect.SimConnect_WeatherCreateStation
+ self.WeatherCreateStation.restype = HRESULT
+ self.WeatherCreateStation.argtypes = [
+ HANDLE,
+ self.DATA_REQUEST_ID,
+ c_char_p,
+ c_char_p,
+ c_float,
+ c_float,
+ c_float,
+ ]
+
+ # SIMCONNECTAPI SimConnect_WeatherRemoveStation(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_REQUEST_ID RequestID
+ # const char * szICAO);
+ self.WeatherRemoveStation = self.SimConnect.SimConnect_WeatherRemoveStation
+ self.WeatherRemoveStation.restype = HRESULT
+ self.WeatherRemoveStation.argtypes = [HANDLE, self.DATA_REQUEST_ID, c_char_p]
+
+ # SIMCONNECTAPI SimConnect_WeatherSetObservation(
+ # HANDLE hSimConnect,
+ # DWORD Seconds
+ # const char * szMETAR);
+ self.WeatherSetObservation = self.SimConnect.SimConnect_WeatherSetObservation
+ self.WeatherSetObservation.restype = HRESULT
+ self.WeatherSetObservation.argtypes = [HANDLE, DWORD, c_char_p]
+
+ # SIMCONNECTAPI SimConnect_WeatherSetModeServer(
+ # HANDLE hSimConnect,
+ # DWORD dwPort
+ # DWORD dwSeconds);
+ self.WeatherSetModeServer = self.SimConnect.SimConnect_WeatherSetModeServer
+ self.WeatherSetModeServer.restype = HRESULT
+ self.WeatherSetModeServer.argtypes = [HANDLE, DWORD, DWORD]
+
+ # SIMCONNECTAPI SimConnect_WeatherSetModeTheme(
+ # HANDLE hSimConnect,
+ # const char * szThemeName);
+ self.WeatherSetModeTheme = self.SimConnect.SimConnect_WeatherSetModeTheme
+ self.WeatherSetModeTheme.restype = HRESULT
+ self.WeatherSetModeTheme.argtypes = [HANDLE, c_char_p]
+
+ # SIMCONNECTAPI SimConnect_WeatherSetModeGlobal(
+ # HANDLE hSimConnect);
+ self.WeatherSetModeGlobal = self.SimConnect.SimConnect_WeatherSetModeGlobal
+ self.WeatherSetModeGlobal.restype = HRESULT
+ self.WeatherSetModeGlobal.argtypes = [HANDLE]
+
+ # SIMCONNECTAPI SimConnect_WeatherSetModeCustom(
+ # HANDLE hSimConnect);
+ self.WeatherSetModeCustom = self.SimConnect.SimConnect_WeatherSetModeCustom
+ self.WeatherSetModeCustom.restype = HRESULT
+ self.WeatherSetModeCustom.argtypes = [HANDLE]
+
+ # SIMCONNECTAPI SimConnect_WeatherSetDynamicUpdateRate(
+ # HANDLE hSimConnect,
+ # DWORD dwRate);
+ self.WeatherSetDynamicUpdateRate = (
+ self.SimConnect.SimConnect_WeatherSetDynamicUpdateRate
+ )
+ self.WeatherSetDynamicUpdateRate.restype = HRESULT
+ self.WeatherSetDynamicUpdateRate.argtypes = [HANDLE, DWORD]
+
+ # SIMCONNECTAPI SimConnect_WeatherRequestCloudState(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_REQUEST_ID RequestID
+ # float minLat
+ # float minLon
+ # float minAlt
+ # float maxLat
+ # float maxLon
+ # float maxAlt
+ # DWORD dwFlags = 0);
+ self.WeatherRequestCloudState = (
+ self.SimConnect.SimConnect_WeatherRequestCloudState
+ )
+ self.WeatherRequestCloudState.restype = HRESULT
+ self.WeatherRequestCloudState.argtypes = [
+ HANDLE,
+ self.DATA_REQUEST_ID,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ DWORD,
+ ]
+
+ # SIMCONNECTAPI SimConnect_WeatherCreateThermal(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_REQUEST_ID RequestID
+ # float lat
+ # float lon
+ # float alt
+ # float radius
+ # float height
+ # float coreRate = 3.0f
+ # float coreTurbulence = 0.05f
+ # float sinkRate = 3.0f
+ # float sinkTurbulence = 0.2f
+ # float coreSize = 0.4f
+ # float coreTransitionSize = 0.1f
+ # float sinkLayerSize = 0.4f
+ # float sinkTransitionSize = 0.1f);
+ self.WeatherCreateThermal = self.SimConnect.SimConnect_WeatherCreateThermal
+ self.WeatherCreateThermal.restype = HRESULT
+ self.WeatherCreateThermal.argtypes = [
+ HANDLE,
+ self.DATA_REQUEST_ID,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ ]
+
+ # SIMCONNECTAPI SimConnect_WeatherRemoveThermal(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_OBJECT_ID ObjectID);
+ self.WeatherRemoveThermal = self.SimConnect.SimConnect_WeatherRemoveThermal
+ self.WeatherRemoveThermal.restype = HRESULT
+ self.WeatherRemoveThermal.argtypes = [HANDLE, SIMCONNECT_OBJECT_ID]
+
+ # SIMCONNECTAPI SimConnect_AICreateParkedATCAircraft(
+ # HANDLE hSimConnect,
+ # const char * szContainerTitle
+ # const char * szTailNumber
+ # const char * szAirportID
+ # SIMCONNECT_DATA_REQUEST_ID RequestID);
+ self.AICreateParkedATCAircraft = (
+ self.SimConnect.SimConnect_AICreateParkedATCAircraft
+ )
+ self.AICreateParkedATCAircraft.restype = HRESULT
+ self.AICreateParkedATCAircraft.argtypes = [
+ HANDLE,
+ c_char_p,
+ c_char_p,
+ c_char_p,
+ self.DATA_REQUEST_ID,
+ ]
+
+ # SIMCONNECTAPI SimConnect_AICreateEnrouteATCAircraft(
+ # HANDLE hSimConnect,
+ # const char * szContainerTitle
+ # const char * szTailNumber
+ # int iFlightNumber
+ # const char * szFlightPlanPath
+ # double dFlightPlanPosition
+ # BOOL bTouchAndGo
+ # SIMCONNECT_DATA_REQUEST_ID RequestID);
+ self.AICreateEnrouteATCAircraft = (
+ self.SimConnect.SimConnect_AICreateEnrouteATCAircraft
+ )
+ self.AICreateEnrouteATCAircraft.restype = HRESULT
+ self.AICreateEnrouteATCAircraft.argtypes = [
+ HANDLE,
+ c_char_p,
+ c_char_p,
+ c_int,
+ c_char_p,
+ c_double,
+ c_bool,
+ self.DATA_REQUEST_ID,
+ ]
+
+ # SIMCONNECTAPI SimConnect_AICreateNonATCAircraft(
+ # HANDLE hSimConnect,
+ # const char * szContainerTitle
+ # const char * szTailNumber
+ # SIMCONNECT_DATA_INITPOSITION InitPos
+ # SIMCONNECT_DATA_REQUEST_ID RequestID);
+ self.AICreateNonATCAircraft = self.SimConnect.SimConnect_AICreateNonATCAircraft
+ self.AICreateNonATCAircraft.restype = HRESULT
+ self.AICreateNonATCAircraft.argtypes = [
+ HANDLE,
+ c_char_p,
+ c_char_p,
+ SIMCONNECT_DATA_INITPOSITION,
+ self.DATA_REQUEST_ID,
+ ]
+
+ # SIMCONNECTAPI SimConnect_AICreateSimulatedObject(
+ # HANDLE hSimConnect,
+ # const char * szContainerTitle
+ # SIMCONNECT_DATA_INITPOSITION InitPos
+ # SIMCONNECT_DATA_REQUEST_ID RequestID);
+ self.AICreateSimulatedObject = (
+ self.SimConnect.SimConnect_AICreateSimulatedObject
+ )
+ self.AICreateSimulatedObject.restype = HRESULT
+ self.AICreateSimulatedObject.argtypes = [
+ HANDLE,
+ c_char_p,
+ SIMCONNECT_DATA_INITPOSITION,
+ self.DATA_REQUEST_ID,
+ ]
+
+ # SIMCONNECTAPI SimConnect_AIReleaseControl(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_OBJECT_ID ObjectID
+ # SIMCONNECT_DATA_REQUEST_ID RequestID);
+ self.AIReleaseControl = self.SimConnect.SimConnect_AIReleaseControl
+ self.AIReleaseControl.restype = HRESULT
+ self.AIReleaseControl.argtypes = [
+ HANDLE,
+ SIMCONNECT_OBJECT_ID,
+ self.DATA_REQUEST_ID,
+ ]
+
+ # SIMCONNECTAPI SimConnect_AIRemoveObject(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_OBJECT_ID ObjectID
+ # SIMCONNECT_DATA_REQUEST_ID RequestID);
+ self.AIRemoveObject = self.SimConnect.SimConnect_AIRemoveObject
+ self.AIRemoveObject.restype = HRESULT
+ self.AIRemoveObject.argtypes = [
+ HANDLE,
+ SIMCONNECT_OBJECT_ID,
+ self.DATA_REQUEST_ID,
+ ]
+
+ # SIMCONNECTAPI SimConnect_AISetAircraftFlightPlan(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_OBJECT_ID ObjectID
+ # const char * szFlightPlanPath
+ # SIMCONNECT_DATA_REQUEST_ID RequestID);
+ self.AISetAircraftFlightPlan = (
+ self.SimConnect.SimConnect_AISetAircraftFlightPlan
+ )
+ self.AISetAircraftFlightPlan.restype = HRESULT
+ self.AISetAircraftFlightPlan.argtypes = [
+ HANDLE,
+ SIMCONNECT_OBJECT_ID,
+ c_char_p,
+ self.DATA_REQUEST_ID,
+ ]
+
+ # SIMCONNECTAPI SimConnect_ExecuteMissionAction(
+ # HANDLE hSimConnect,
+ # const GUID guidInstanceId);
+ self.ExecuteMissionAction = self.SimConnect.SimConnect_ExecuteMissionAction
+ self.ExecuteMissionAction.restype = HRESULT
+ self.ExecuteMissionAction.argtypes = []
+
+ # SIMCONNECTAPI SimConnect_CompleteCustomMissionAction(
+ # HANDLE hSimConnect,
+ # const GUID guidInstanceId);
+ self.CompleteCustomMissionAction = (
+ self.SimConnect.SimConnect_CompleteCustomMissionAction
+ )
+ self.CompleteCustomMissionAction.restype = HRESULT
+ self.CompleteCustomMissionAction.argtypes = []
+
+ # SIMCONNECTAPI SimConnect_RetrieveString(
+ # SIMCONNECT_RECV * pData,
+ # DWORD cbData
+ # void * pStringV
+ # char ** pszString
+ # DWORD * pcbString);
+ self.RetrieveString = self.SimConnect.SimConnect_RetrieveString
+ self.RetrieveString.restype = HRESULT
+ self.RetrieveString.argtypes = []
+
+ # SIMCONNECTAPI SimConnect_GetLastSentPacketID(
+ # HANDLE hSimConnect,
+ # DWORD * pdwError);
+ self.GetLastSentPacketID = self.SimConnect.SimConnect_GetLastSentPacketID
+ self.GetLastSentPacketID.restype = HRESULT
+ self.GetLastSentPacketID.argtypes = [HANDLE, POINTER(DWORD)]
+
+ # SIMCONNECTAPI SimConnect_GetNextDispatch(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_RECV ** ppData
+ # DWORD * pcbData);
+ self.GetNextDispatch = self.SimConnect.SimConnect_GetNextDispatch
+ self.GetNextDispatch.restype = HRESULT
+ self.GetNextDispatch.argtypes = []
+
+ # SIMCONNECTAPI SimConnect_RequestResponseTimes(
+ # HANDLE hSimConnect,
+ # DWORD nCount
+ # float * fElapsedSeconds);
+ self.RequestResponseTimes = self.SimConnect.SimConnect_RequestResponseTimes
+ self.RequestResponseTimes.restype = HRESULT
+ self.RequestResponseTimes.argtypes = [HANDLE, DWORD, c_float]
+
+ # SIMCONNECTAPI SimConnect_InsertString(
+ # char * pDest,
+ # DWORD cbDest
+ # void ** ppEnd
+ # DWORD * pcbStringV
+ # const char * pSource);
+ self.InsertString = self.SimConnect.SimConnect_InsertString
+ self.InsertString.restype = HRESULT
+ self.InsertString.argtypes = []
+
+ # SIMCONNECTAPI SimConnect_CameraSetRelative6DOF(
+ # HANDLE hSimConnect,
+ # float fDeltaX
+ # float fDeltaY
+ # float fDeltaZ
+ # float fPitchDeg
+ # float fBankDeg
+ # float fHeadingDeg);
+ self.CameraSetRelative6DOF = self.SimConnect.SimConnect_CameraSetRelative6DOF
+ self.CameraSetRelative6DOF.restype = HRESULT
+ self.CameraSetRelative6DOF.argtypes = [
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ c_float,
+ ]
+
+ # SIMCONNECTAPI SimConnect_MenuAddItem(
+ # HANDLE hSimConnect,
+ # const char * szMenuItem
+ # SIMCONNECT_CLIENT_EVENT_ID MenuEventID
+ # DWORD dwData);
+ self.MenuAddItem = self.SimConnect.SimConnect_MenuAddItem
+ self.MenuAddItem.restype = HRESULT
+ self.MenuAddItem.argtypes = [HANDLE, SIMCONNECT_CLIENT_EVENT_ID, DWORD]
+
+ # SIMCONNECTAPI SimConnect_MenuDeleteItem(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_CLIENT_EVENT_ID MenuEventID);
+ self.MenuDeleteItem = self.SimConnect.SimConnect_MenuDeleteItem
+ self.MenuDeleteItem.restype = HRESULT
+ self.MenuDeleteItem.argtypes = [HANDLE, SIMCONNECT_CLIENT_EVENT_ID]
+
+ # SIMCONNECTAPI SimConnect_MenuAddSubItem(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_CLIENT_EVENT_ID MenuEventID
+ # const char * szMenuItem
+ # SIMCONNECT_CLIENT_EVENT_ID SubMenuEventID
+ # DWORD dwData);
+ self.MenuAddSubItem = self.SimConnect.SimConnect_MenuAddSubItem
+ self.MenuAddSubItem.restype = HRESULT
+ self.MenuAddSubItem.argtypes = [
+ HANDLE,
+ SIMCONNECT_CLIENT_EVENT_ID,
+ c_char_p,
+ SIMCONNECT_CLIENT_EVENT_ID,
+ DWORD,
+ ]
+
+ # SIMCONNECTAPI SimConnect_MenuDeleteSubItem(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_CLIENT_EVENT_ID MenuEventID
+ # const SIMCONNECT_CLIENT_EVENT_ID SubMenuEventID);
+ self.MenuDeleteSubItem = self.SimConnect.SimConnect_MenuDeleteSubItem
+ self.MenuDeleteSubItem.restype = HRESULT
+ self.MenuDeleteSubItem.argtypes = [
+ HANDLE,
+ SIMCONNECT_CLIENT_EVENT_ID,
+ SIMCONNECT_CLIENT_EVENT_ID,
+ ]
+
+ # SIMCONNECTAPI SimConnect_RequestSystemState(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_REQUEST_ID RequestID
+ # const char * szState);
+ self.RequestSystemState = self.SimConnect.SimConnect_RequestSystemState
+ self.RequestSystemState.restype = HRESULT
+ self.RequestSystemState.argtypes = [
+ HANDLE,
+ SIMCONNECT_DATA_REQUEST_ID,
+ c_char_p,
+ ]
+
+ # SIMCONNECTAPI SimConnect_SetSystemState(
+ # HANDLE hSimConnect,
+ # const char * szState
+ # DWORD dwInteger
+ # float fFloat
+ # const char * szString);
+ self.SetSystemState = self.SimConnect.SimConnect_SetSystemState
+ self.SetSystemState.restype = HRESULT
+ self.SetSystemState.argtypes = [HANDLE, c_char_p, DWORD, c_float, c_char_p]
+
+ # SIMCONNECTAPI SimConnect_MapClientDataNameToID(
+ # HANDLE hSimConnect,
+ # const char * szClientDataName
+ # SIMCONNECT_CLIENT_DATA_ID ClientDataID);
+ self.MapClientDataNameToID = self.SimConnect.SimConnect_MapClientDataNameToID
+ self.MapClientDataNameToID.restype = HRESULT
+ self.MapClientDataNameToID.argtypes = [
+ HANDLE,
+ c_char_p,
+ SIMCONNECT_CLIENT_DATA_ID,
+ ]
+
+ # SIMCONNECTAPI SimConnect_CreateClientData(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_CLIENT_DATA_ID ClientDataID
+ # DWORD dwSize
+ # SIMCONNECT_CREATE_CLIENT_DATA_FLAG Flags);
+ self.CreateClientData = self.SimConnect.SimConnect_CreateClientData
+ self.CreateClientData.restype = HRESULT
+ self.CreateClientData.argtypes = [
+ HANDLE,
+ self.CLIENT_DATA_ID,
+ DWORD,
+ SIMCONNECT_CREATE_CLIENT_DATA_FLAG,
+ ]
+
+ # SIMCONNECTAPI SimConnect_AddToClientDataDefinition(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_CLIENT_DATA_DEFINITION_ID DefineID
+ # DWORD dwOffset
+ # DWORD dwSizeOrType
+ # float fEpsilon = 0
+ # DWORD DatumID = SIMCONNECT_UNUSED);
+ self.AddToClientDataDefinition = (
+ self.SimConnect.SimConnect_AddToClientDataDefinition
+ )
+ self.AddToClientDataDefinition.restype = HRESULT
+ self.AddToClientDataDefinition.argtypes = [
+ HANDLE,
+ self.CLIENT_DATA_DEFINITION_ID,
+ DWORD,
+ DWORD,
+ c_float,
+ DWORD,
+ ]
+
+ # SIMCONNECTAPI SimConnect_ClearClientDataDefinition(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_CLIENT_DATA_DEFINITION_ID DefineID);
+ self.ClearClientDataDefinition = (
+ self.SimConnect.SimConnect_ClearClientDataDefinition
+ )
+ self.ClearClientDataDefinition.restype = HRESULT
+ self.ClearClientDataDefinition.argtypes = [
+ HANDLE,
+ self.CLIENT_DATA_DEFINITION_ID,
+ ]
+
+ # SIMCONNECTAPI SimConnect_RequestClientData(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_CLIENT_DATA_ID ClientDataID
+ # SIMCONNECT_DATA_REQUEST_ID RequestID
+ # SIMCONNECT_CLIENT_DATA_DEFINITION_ID DefineID
+ # SIMCONNECT_CLIENT_DATA_PERIOD Period = SIMCONNECT_CLIENT_DATA_PERIOD_ONCE
+ # SIMCONNECT_CLIENT_DATA_REQUEST_FLAG Flags = 0
+ # DWORD origin = 0
+ # DWORD interval = 0
+ # DWORD limit = 0);
+ self.RequestClientData = self.SimConnect.SimConnect_RequestClientData
+ self.RequestClientData.restype = HRESULT
+ self.RequestClientData.argtypes = [
+ HANDLE,
+ self.CLIENT_DATA_ID,
+ self.DATA_REQUEST_ID,
+ self.CLIENT_DATA_DEFINITION_ID,
+ SIMCONNECT_CLIENT_DATA_PERIOD,
+ SIMCONNECT_CLIENT_DATA_REQUEST_FLAG,
+ DWORD,
+ DWORD,
+ DWORD,
+ ]
+
+ # https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_SetClientData.htm
+ # SIMCONNECTAPI SimConnect_SetClientData(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_CLIENT_DATA_ID ClientDataID
+ # SIMCONNECT_CLIENT_DATA_DEFINITION_ID DefineID
+ # SIMCONNECT_CLIENT_DATA_SET_FLAG Flags
+ # DWORD dwReserved set to 0
+ # DWORD cbUnitSize
+ # void * pDataSet);
+ self.SetClientData = self.SimConnect.SimConnect_SetClientData
+ self.SetClientData.restype = HRESULT
+ self.SetClientData.argtypes = [
+ HANDLE,
+ self.CLIENT_DATA_ID,
+ self.CLIENT_DATA_DEFINITION_ID,
+ SIMCONNECT_CLIENT_DATA_SET_FLAG,
+ DWORD,
+ DWORD,
+ c_void_p,
+ ]
+
+ # SIMCONNECTAPI SimConnect_FlightLoad(
+ # HANDLE hSimConnect,
+ # const char * szFileName);
+ self.FlightLoad = self.SimConnect.SimConnect_FlightLoad
+ self.FlightLoad.restype = HRESULT
+ self.FlightLoad.argtypes = [HANDLE, c_char_p]
+
+ # SIMCONNECTAPI SimConnect_FlightSave(
+ # HANDLE hSimConnect,
+ # const char * szFileName
+ # const char * szTitle
+ # const char * szDescription
+ # DWORD Flags);
+ self.FlightSave = self.SimConnect.SimConnect_FlightSave
+ self.FlightSave.restype = HRESULT
+ self.FlightSave.argtypes = [HANDLE, c_char_p, c_char_p, c_char_p, DWORD]
+
+ # SIMCONNECTAPI SimConnect_FlightPlanLoad(
+ # HANDLE hSimConnect,
+ # const char * szFileName);
+ self.FlightPlanLoad = self.SimConnect.SimConnect_FlightPlanLoad
+ self.FlightPlanLoad.restype = HRESULT
+ self.FlightPlanLoad.argtypes = [HANDLE, c_char_p]
+
+ # SIMCONNECTAPI SimConnect_Text(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_TEXT_TYPE type
+ # float fTimeSeconds
+ # SIMCONNECT_CLIENT_EVENT_ID EventID
+ # DWORD cbUnitSize
+ # void * pDataSet);
+ self.Text = self.SimConnect.SimConnect_Text
+ self.Text.restype = HRESULT
+ self.Text.argtypes = [
+ HANDLE,
+ SIMCONNECT_TEXT_TYPE,
+ c_float,
+ self.EventID,
+ DWORD,
+ c_void_p,
+ ]
+
+ # SIMCONNECTAPI SimConnect_SubscribeToFacilities(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_FACILITY_LIST_TYPE type
+ # SIMCONNECT_DATA_REQUEST_ID RequestID);
+ self.SubscribeToFacilities = self.SimConnect.SimConnect_SubscribeToFacilities
+ self.SubscribeToFacilities.restype = HRESULT
+ self.SubscribeToFacilities.argtypes = [
+ HANDLE,
+ SIMCONNECT_FACILITY_LIST_TYPE,
+ self.DATA_REQUEST_ID,
+ ]
+
+ # SIMCONNECTAPI SimConnect_UnsubscribeToFacilities(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_FACILITY_LIST_TYPE type);
+ self.UnsubscribeToFacilities = (
+ self.SimConnect.SimConnect_UnsubscribeToFacilities
+ )
+ self.UnsubscribeToFacilities.restype = HRESULT
+ self.UnsubscribeToFacilities.argtypes = [
+ HANDLE,
+ SIMCONNECT_FACILITY_LIST_TYPE,
+ ]
+
+ # SIMCONNECTAPI SimConnect_RequestFacilitiesList(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_FACILITY_LIST_TYPE type
+ # SIMCONNECT_DATA_REQUEST_ID RequestID);
+ self.RequestFacilitiesList = self.SimConnect.SimConnect_RequestFacilitiesList
+ self.RequestFacilitiesList.restype = HRESULT
+ self.RequestFacilitiesList.argtypes = [
+ HANDLE,
+ SIMCONNECT_FACILITY_LIST_TYPE,
+ self.DATA_REQUEST_ID,
+ ]
+
+ # SIMCONNECTAPI SimConnect_EnumerateSimObjectsAndLiveries(
+ # HANDLE hSimConnect,
+ # SIMCONNECT_DATA_REQUEST_ID RequestID,
+ # SIMCONNECT_SIMOBJECT_TYPE Type);
+ self.EnumerateSimObjectsAndLiveries = (
+ self.SimConnect.SimConnect_EnumerateSimObjectsAndLiveries
+ )
+ self.EnumerateSimObjectsAndLiveries.restype = HRESULT
+ self.EnumerateSimObjectsAndLiveries.argtypes = [
+ HANDLE,
+ self.DATA_REQUEST_ID,
+ SIMCONNECT_SIMOBJECT_TYPE,
+ ]
diff --git a/action_plugins/map_to_simconnect/SimConnect/Constants.py b/action_plugins/map_to_simconnect/SimConnect/Constants.py
index 190b696a..91ef17c2 100644
--- a/action_plugins/map_to_simconnect/SimConnect/Constants.py
+++ b/action_plugins/map_to_simconnect/SimConnect/Constants.py
@@ -15,25 +15,35 @@
SIMCONNECT_UNUSED = DWORD_MAX # special value to indicate unused event, ID
SIMCONNECT_CAMERA_IGNORE_FIELD = c_float(
- -1
+ -1
) # Used to tell the Camera API to NOT modify the value in this part of the argument.
SIMCONNECT_CLIENTDATA_MAX_SIZE = DWORD(
- 8192
+ 8192
) # maximum value for SimConnect_CreateClientData dwSize parameter
# Notification Group priority values
SIMCONNECT_GROUP_PRIORITY_HIGHEST = DWORD(1) # highest priority
-SIMCONNECT_GROUP_PRIORITY_HIGHEST_MASKABLE = DWORD(10000000) # highest priority that allows events to be masked
+SIMCONNECT_GROUP_PRIORITY_HIGHEST_MASKABLE = DWORD(
+ 10000000
+) # highest priority that allows events to be masked
SIMCONNECT_GROUP_PRIORITY_STANDARD = DWORD(1900000000) # standard priority
SIMCONNECT_GROUP_PRIORITY_DEFAULT = DWORD(2000000000) # default priority
-SIMCONNECT_GROUP_PRIORITY_LOWEST = DWORD(4000000000) # priorities lower than this will be ignored
+SIMCONNECT_GROUP_PRIORITY_LOWEST = DWORD(
+ 4000000000
+) # priorities lower than this will be ignored
-SIMCONNECT_EVENT_FLAG_DEFAULT = DWORD(0x00000000)
-SIMCONNECT_EVENT_FLAG_FAST_REPEAT_TIMER = DWORD(0x00000001) # set event repeat timer to simulate fast repeat
-SIMCONNECT_EVENT_FLAG_SLOW_REPEAT_TIMER = DWORD(0x00000002) # set event repeat timer to simulate slow repeat
-SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY = DWORD(0x00000010) # interpret GroupID parameter as priority value
+SIMCONNECT_EVENT_FLAG_DEFAULT = DWORD(0x00000000)
+SIMCONNECT_EVENT_FLAG_FAST_REPEAT_TIMER = DWORD(
+ 0x00000001
+) # set event repeat timer to simulate fast repeat
+SIMCONNECT_EVENT_FLAG_SLOW_REPEAT_TIMER = DWORD(
+ 0x00000002
+) # set event repeat timer to simulate slow repeat
+SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY = DWORD(
+ 0x00000010
+) # interpret GroupID parameter as priority value
# Weather observations Metar strings
MAX_METAR_LENGTH = DWORD(2000)
@@ -56,11 +66,11 @@
# AddToClientDataDefinition dwOffset parameter special values
SIMCONNECT_CLIENTDATAOFFSET_AUTO = DWORD(
- -1
+ -1
) # automatically compute offset of the ClientData variable
# Open ConfigIndex parameter special value
SIMCONNECT_OPEN_CONFIGINDEX_LOCAL = DWORD(
- -1
+ -1
) # ignore SimConnect.cfg settings, and force local connection
SIMCONNECT_OBJECT_ID = DWORD
diff --git a/action_plugins/map_to_simconnect/SimConnect/Enum.py b/action_plugins/map_to_simconnect/SimConnect/Enum.py
index 767022cd..7753a020 100644
--- a/action_plugins/map_to_simconnect/SimConnect/Enum.py
+++ b/action_plugins/map_to_simconnect/SimConnect/Enum.py
@@ -1,4 +1,4 @@
-from enum import IntEnum, IntFlag, Enum, auto
+from enum import IntEnum, IntFlag, auto
from ctypes.wintypes import *
from ctypes import *
from .Constants import *
@@ -14,395 +14,426 @@
# Define the types we need.
class CtypesEnum(IntEnum):
- """A ctypes-compatible IntEnum superclass."""
+ """A ctypes-compatible IntEnum superclass."""
- @classmethod
- def from_param(cls, obj):
- return int(obj)
+ @classmethod
+ def from_param(cls, obj):
+ return int(obj)
# Define the types we need.
class CtypesFlagEnum(IntFlag):
- """A ctypes-compatible Enum superclass."""
+ """A ctypes-compatible Enum superclass."""
- @classmethod
- def from_param(cls, obj):
- return int(obj)
+ @classmethod
+ def from_param(cls, obj):
+ return int(obj)
class AutoName(CtypesEnum):
- def _generate_next_value_(name, start, count, last_values):
- return count
-
-
+ def _generate_next_value_(name, start, count, last_values):
+ return count
# Receive data types
class SIMCONNECT_RECV_ID(CtypesEnum):
- SIMCONNECT_RECV_ID_NULL = 0
- SIMCONNECT_RECV_ID_EXCEPTION = 1
- SIMCONNECT_RECV_ID_OPEN = 2
- SIMCONNECT_RECV_ID_QUIT = 3
- SIMCONNECT_RECV_ID_EVENT = 4
- SIMCONNECT_RECV_ID_EVENT_OBJECT_ADDREMOVE = 5
- SIMCONNECT_RECV_ID_EVENT_FILENAME = 6
- SIMCONNECT_RECV_ID_EVENT_FRAME = 7
- SIMCONNECT_RECV_ID_SIMOBJECT_DATA = 8
- SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE = 9
- SIMCONNECT_RECV_ID_WEATHER_OBSERVATION = 10
- SIMCONNECT_RECV_ID_CLOUD_STATE = 11
- SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID = 12
- SIMCONNECT_RECV_ID_RESERVED_KEY = 13
- SIMCONNECT_RECV_ID_CUSTOM_ACTION = 14
- SIMCONNECT_RECV_ID_SYSTEM_STATE = 15
- SIMCONNECT_RECV_ID_CLIENT_DATA = 16
- SIMCONNECT_RECV_ID_EVENT_WEATHER_MODE = 17
- SIMCONNECT_RECV_ID_AIRPORT_LIST = 18
- SIMCONNECT_RECV_ID_VOR_LIST = 19
- SIMCONNECT_RECV_ID_NDB_LIST = 20
- SIMCONNECT_RECV_ID_WAYPOINT_LIST = 21
- SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SERVER_STARTED = 22
- SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_CLIENT_STARTED = 23
- SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SESSION_ENDED = 24
- SIMCONNECT_RECV_ID_EVENT_RACE_END = 25
- SIMCONNECT_RECV_ID_EVENT_RACE_LAP = 26
- SIMCONNECT_RECV_ID_EVENT_EX1 = 27
- SIMCONNECT_RECV_ID_FACILITY_DATA = 28
- SIMCONNECT_RECV_ID_FACILITY_DATA_END = 29
- SIMCONNECT_RECV_ID_FACILITY_MINIMAL_LIST = 30
- SIMCONNECT_RECV_ID_JETWAY_DATA = 31
- SIMCONNECT_RECV_ID_CONTROLLERS_LIST = 32
- SIMCONNECT_RECV_ID_ACTION_CALLBACK = 33
- SIMCONNECT_RECV_ID_ENUMERATE_INPUT_EVENTS = 34
- SIMCONNECT_RECV_ID_GET_INPUT_EVENT = 35
- SIMCONNECT_RECV_ID_SUBSCRIBE_INPUT_EVENT = 36
- SIMCONNECT_RECV_ID_ENUMERATE_INPUT_EVENT_PARAMS = 37
- SIMCONNECT_RECV_ID_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST = 38
+ SIMCONNECT_RECV_ID_NULL = 0
+ SIMCONNECT_RECV_ID_EXCEPTION = 1
+ SIMCONNECT_RECV_ID_OPEN = 2
+ SIMCONNECT_RECV_ID_QUIT = 3
+ SIMCONNECT_RECV_ID_EVENT = 4
+ SIMCONNECT_RECV_ID_EVENT_OBJECT_ADDREMOVE = 5
+ SIMCONNECT_RECV_ID_EVENT_FILENAME = 6
+ SIMCONNECT_RECV_ID_EVENT_FRAME = 7
+ SIMCONNECT_RECV_ID_SIMOBJECT_DATA = 8
+ SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE = 9
+ SIMCONNECT_RECV_ID_WEATHER_OBSERVATION = 10
+ SIMCONNECT_RECV_ID_CLOUD_STATE = 11
+ SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID = 12
+ SIMCONNECT_RECV_ID_RESERVED_KEY = 13
+ SIMCONNECT_RECV_ID_CUSTOM_ACTION = 14
+ SIMCONNECT_RECV_ID_SYSTEM_STATE = 15
+ SIMCONNECT_RECV_ID_CLIENT_DATA = 16
+ SIMCONNECT_RECV_ID_EVENT_WEATHER_MODE = 17
+ SIMCONNECT_RECV_ID_AIRPORT_LIST = 18
+ SIMCONNECT_RECV_ID_VOR_LIST = 19
+ SIMCONNECT_RECV_ID_NDB_LIST = 20
+ SIMCONNECT_RECV_ID_WAYPOINT_LIST = 21
+ SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SERVER_STARTED = 22
+ SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_CLIENT_STARTED = 23
+ SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SESSION_ENDED = 24
+ SIMCONNECT_RECV_ID_EVENT_RACE_END = 25
+ SIMCONNECT_RECV_ID_EVENT_RACE_LAP = 26
+ SIMCONNECT_RECV_ID_EVENT_EX1 = 27
+ SIMCONNECT_RECV_ID_FACILITY_DATA = 28
+ SIMCONNECT_RECV_ID_FACILITY_DATA_END = 29
+ SIMCONNECT_RECV_ID_FACILITY_MINIMAL_LIST = 30
+ SIMCONNECT_RECV_ID_JETWAY_DATA = 31
+ SIMCONNECT_RECV_ID_CONTROLLERS_LIST = 32
+ SIMCONNECT_RECV_ID_ACTION_CALLBACK = 33
+ SIMCONNECT_RECV_ID_ENUMERATE_INPUT_EVENTS = 34
+ SIMCONNECT_RECV_ID_GET_INPUT_EVENT = 35
+ SIMCONNECT_RECV_ID_SUBSCRIBE_INPUT_EVENT = 36
+ SIMCONNECT_RECV_ID_ENUMERATE_INPUT_EVENT_PARAMS = 37
+ SIMCONNECT_RECV_ID_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST = 38
+
# Data data types
class SIMCONNECT_DATATYPE(CtypesEnum):
- SIMCONNECT_DATATYPE_INVALID = 0 # invalid data type
- SIMCONNECT_DATATYPE_INT32 = 1 # 32-bit integer number
- SIMCONNECT_DATATYPE_INT64 = 2 # 64-bit integer number
- SIMCONNECT_DATATYPE_FLOAT32 = 3 # 32-bit floating-point number (float)
- SIMCONNECT_DATATYPE_FLOAT64 = 4 # 64-bit floating-point number (double)
- SIMCONNECT_DATATYPE_STRING8 = 5 # 8-byte string
- SIMCONNECT_DATATYPE_STRING32 = 6 # 32-byte string
- SIMCONNECT_DATATYPE_STRING64 = 7 # 64-byte string
- SIMCONNECT_DATATYPE_STRING128 = 8 # 128-byte string
- SIMCONNECT_DATATYPE_STRING256 = 9 # 256-byte string
- SIMCONNECT_DATATYPE_STRING260 = 10 # 260-byte string
- SIMCONNECT_DATATYPE_STRINGV = 11 # variable-length string
-
- SIMCONNECT_DATATYPE_INITPOSITION = 12 # see SIMCONNECT_DATA_INITPOSITION
- SIMCONNECT_DATATYPE_MARKERSTATE = 13 # see SIMCONNECT_DATA_MARKERSTATE
- SIMCONNECT_DATATYPE_WAYPOINT = 14 # see SIMCONNECT_DATA_WAYPOINT
- SIMCONNECT_DATATYPE_LATLONALT = 15 # see SIMCONNECT_DATA_LATLONALT
- SIMCONNECT_DATATYPE_XYZ = 16 # see SIMCONNECT_DATA_XYZ
- SIMCONNECT_DATATYPE_INT8 = 17 # 8 bit integer number, char
-
- SIMCONNECT_DATATYPE_MAX = 18 # enum limit
+ SIMCONNECT_DATATYPE_INVALID = 0 # invalid data type
+ SIMCONNECT_DATATYPE_INT32 = 1 # 32-bit integer number
+ SIMCONNECT_DATATYPE_INT64 = 2 # 64-bit integer number
+ SIMCONNECT_DATATYPE_FLOAT32 = 3 # 32-bit floating-point number (float)
+ SIMCONNECT_DATATYPE_FLOAT64 = 4 # 64-bit floating-point number (double)
+ SIMCONNECT_DATATYPE_STRING8 = 5 # 8-byte string
+ SIMCONNECT_DATATYPE_STRING32 = 6 # 32-byte string
+ SIMCONNECT_DATATYPE_STRING64 = 7 # 64-byte string
+ SIMCONNECT_DATATYPE_STRING128 = 8 # 128-byte string
+ SIMCONNECT_DATATYPE_STRING256 = 9 # 256-byte string
+ SIMCONNECT_DATATYPE_STRING260 = 10 # 260-byte string
+ SIMCONNECT_DATATYPE_STRINGV = 11 # variable-length string
+
+ SIMCONNECT_DATATYPE_INITPOSITION = 12 # see SIMCONNECT_DATA_INITPOSITION
+ SIMCONNECT_DATATYPE_MARKERSTATE = 13 # see SIMCONNECT_DATA_MARKERSTATE
+ SIMCONNECT_DATATYPE_WAYPOINT = 14 # see SIMCONNECT_DATA_WAYPOINT
+ SIMCONNECT_DATATYPE_LATLONALT = 15 # see SIMCONNECT_DATA_LATLONALT
+ SIMCONNECT_DATATYPE_XYZ = 16 # see SIMCONNECT_DATA_XYZ
+ SIMCONNECT_DATATYPE_INT8 = 17 # 8 bit integer number, char
+
+ SIMCONNECT_DATATYPE_MAX = 18 # enum limit
# Exception error types
class SIMCONNECT_EXCEPTION(CtypesEnum):
- SIMCONNECT_EXCEPTION_NONE = 0
-
- SIMCONNECT_EXCEPTION_ERROR = 1
- SIMCONNECT_EXCEPTION_SIZE_MISMATCH = 2
- SIMCONNECT_EXCEPTION_UNRECOGNIZED_ID = 3
- SIMCONNECT_EXCEPTION_UNOPENED = 4
- SIMCONNECT_EXCEPTION_VERSION_MISMATCH = 5
- SIMCONNECT_EXCEPTION_TOO_MANY_GROUPS = 6
- SIMCONNECT_EXCEPTION_NAME_UNRECOGNIZED = 7
- SIMCONNECT_EXCEPTION_TOO_MANY_EVENT_NAMES = 8
- SIMCONNECT_EXCEPTION_EVENT_ID_DUPLICATE = 9
- SIMCONNECT_EXCEPTION_TOO_MANY_MAPS = 10
- SIMCONNECT_EXCEPTION_TOO_MANY_OBJECTS = 11
- SIMCONNECT_EXCEPTION_TOO_MANY_REQUESTS = 12
- SIMCONNECT_EXCEPTION_WEATHER_INVALID_PORT = 13
- SIMCONNECT_EXCEPTION_WEATHER_INVALID_METAR = 14
- SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_GET_OBSERVATION = 15
- SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_CREATE_STATION = 16
- SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_REMOVE_STATION = 17
- SIMCONNECT_EXCEPTION_INVALID_DATA_TYPE = 18
- SIMCONNECT_EXCEPTION_INVALID_DATA_SIZE = 19
- SIMCONNECT_EXCEPTION_DATA_ERROR = 20
- SIMCONNECT_EXCEPTION_INVALID_ARRAY = 21
- SIMCONNECT_EXCEPTION_CREATE_OBJECT_FAILED = 22
- SIMCONNECT_EXCEPTION_LOAD_FLIGHTPLAN_FAILED = 23
- SIMCONNECT_EXCEPTION_OPERATION_INVALID_FOR_OBJECT_TYPE = 24
- SIMCONNECT_EXCEPTION_ILLEGAL_OPERATION = 24
- SIMCONNECT_EXCEPTION_ALREADY_SUBSCRIBED = 26
- SIMCONNECT_EXCEPTION_INVALID_ENUM = 27
- SIMCONNECT_EXCEPTION_DEFINITION_ERROR = 28
- SIMCONNECT_EXCEPTION_DUPLICATE_ID = 29
- SIMCONNECT_EXCEPTION_DATUM_ID = 30
- SIMCONNECT_EXCEPTION_OUT_OF_BOUNDS = 31
- SIMCONNECT_EXCEPTION_ALREADY_CREATED = 32
- SIMCONNECT_EXCEPTION_OBJECT_OUTSIDE_REALITY_BUBBLE = 33
- SIMCONNECT_EXCEPTION_OBJECT_CONTAINER = 34
- SIMCONNECT_EXCEPTION_OBJECT_AI = 35
- SIMCONNECT_EXCEPTION_OBJECT_ATC = 36
- SIMCONNECT_EXCEPTION_OBJECT_SCHEDULE = 37
- SIMCONNECT_EXCEPTION_JETWAY_DATA = 38
- SIMCONNECT_EXCEPTION_ACTION_NOT_FOUND = 39
- SIMCONNECT_EXCEPTION_NOT_AN_ACTION = 40
- SIMCONNECT_EXCEPTION_INCORRECT_ACTION_PARAMS = 41
- SIMCONNECT_EXCEPTION_GET_INPUT_EVENT_FAILED = 42
- SIMCONNECT_EXCEPTION_SET_INPUT_EVENT_FAILED = 43
- SIMCONNECT_EXCEPTION_INTERNAL = 44
+ SIMCONNECT_EXCEPTION_NONE = 0
+
+ SIMCONNECT_EXCEPTION_ERROR = 1
+ SIMCONNECT_EXCEPTION_SIZE_MISMATCH = 2
+ SIMCONNECT_EXCEPTION_UNRECOGNIZED_ID = 3
+ SIMCONNECT_EXCEPTION_UNOPENED = 4
+ SIMCONNECT_EXCEPTION_VERSION_MISMATCH = 5
+ SIMCONNECT_EXCEPTION_TOO_MANY_GROUPS = 6
+ SIMCONNECT_EXCEPTION_NAME_UNRECOGNIZED = 7
+ SIMCONNECT_EXCEPTION_TOO_MANY_EVENT_NAMES = 8
+ SIMCONNECT_EXCEPTION_EVENT_ID_DUPLICATE = 9
+ SIMCONNECT_EXCEPTION_TOO_MANY_MAPS = 10
+ SIMCONNECT_EXCEPTION_TOO_MANY_OBJECTS = 11
+ SIMCONNECT_EXCEPTION_TOO_MANY_REQUESTS = 12
+ SIMCONNECT_EXCEPTION_WEATHER_INVALID_PORT = 13
+ SIMCONNECT_EXCEPTION_WEATHER_INVALID_METAR = 14
+ SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_GET_OBSERVATION = 15
+ SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_CREATE_STATION = 16
+ SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_REMOVE_STATION = 17
+ SIMCONNECT_EXCEPTION_INVALID_DATA_TYPE = 18
+ SIMCONNECT_EXCEPTION_INVALID_DATA_SIZE = 19
+ SIMCONNECT_EXCEPTION_DATA_ERROR = 20
+ SIMCONNECT_EXCEPTION_INVALID_ARRAY = 21
+ SIMCONNECT_EXCEPTION_CREATE_OBJECT_FAILED = 22
+ SIMCONNECT_EXCEPTION_LOAD_FLIGHTPLAN_FAILED = 23
+ SIMCONNECT_EXCEPTION_OPERATION_INVALID_FOR_OBJECT_TYPE = 24
+ SIMCONNECT_EXCEPTION_ILLEGAL_OPERATION = 24
+ SIMCONNECT_EXCEPTION_ALREADY_SUBSCRIBED = 26
+ SIMCONNECT_EXCEPTION_INVALID_ENUM = 27
+ SIMCONNECT_EXCEPTION_DEFINITION_ERROR = 28
+ SIMCONNECT_EXCEPTION_DUPLICATE_ID = 29
+ SIMCONNECT_EXCEPTION_DATUM_ID = 30
+ SIMCONNECT_EXCEPTION_OUT_OF_BOUNDS = 31
+ SIMCONNECT_EXCEPTION_ALREADY_CREATED = 32
+ SIMCONNECT_EXCEPTION_OBJECT_OUTSIDE_REALITY_BUBBLE = 33
+ SIMCONNECT_EXCEPTION_OBJECT_CONTAINER = 34
+ SIMCONNECT_EXCEPTION_OBJECT_AI = 35
+ SIMCONNECT_EXCEPTION_OBJECT_ATC = 36
+ SIMCONNECT_EXCEPTION_OBJECT_SCHEDULE = 37
+ SIMCONNECT_EXCEPTION_JETWAY_DATA = 38
+ SIMCONNECT_EXCEPTION_ACTION_NOT_FOUND = 39
+ SIMCONNECT_EXCEPTION_NOT_AN_ACTION = 40
+ SIMCONNECT_EXCEPTION_INCORRECT_ACTION_PARAMS = 41
+ SIMCONNECT_EXCEPTION_GET_INPUT_EVENT_FAILED = 42
+ SIMCONNECT_EXCEPTION_SET_INPUT_EVENT_FAILED = 43
+ SIMCONNECT_EXCEPTION_INTERNAL = 44
# Object types
class SIMCONNECT_SIMOBJECT_TYPE(CtypesEnum):
- SIMCONNECT_SIMOBJECT_TYPE_USER = 0
- SIMCONNECT_SIMOBJECT_TYPE_ALL = 1
- SIMCONNECT_SIMOBJECT_TYPE_AIRCRAFT = 2
- SIMCONNECT_SIMOBJECT_TYPE_HELICOPTER = 3
- SIMCONNECT_SIMOBJECT_TYPE_BOAT = 4
- SIMCONNECT_SIMOBJECT_TYPE_GROUND = 5
- SIMCONNECT_SIMOBJECT_TYPE_HOT_AIR_BALLOON = 6
- SIMCONNECT_SIMOBJECT_TYPE_ANIMAL = 7
- SIMCONNECT_SIMOBJECT_TYPE_USER_AVATAR = 8
- SIMCONNECT_SIMOBJECT_TYPE_USER_CURRENT = 9
+ SIMCONNECT_SIMOBJECT_TYPE_USER = 0
+ SIMCONNECT_SIMOBJECT_TYPE_ALL = 1
+ SIMCONNECT_SIMOBJECT_TYPE_AIRCRAFT = 2
+ SIMCONNECT_SIMOBJECT_TYPE_HELICOPTER = 3
+ SIMCONNECT_SIMOBJECT_TYPE_BOAT = 4
+ SIMCONNECT_SIMOBJECT_TYPE_GROUND = 5
+ SIMCONNECT_SIMOBJECT_TYPE_HOT_AIR_BALLOON = 6
+ SIMCONNECT_SIMOBJECT_TYPE_ANIMAL = 7
+ SIMCONNECT_SIMOBJECT_TYPE_USER_AVATAR = 8
+ SIMCONNECT_SIMOBJECT_TYPE_USER_CURRENT = 9
+
# EventState values
class SIMCONNECT_STATE(CtypesEnum):
- SIMCONNECT_STATE_OFF = 0
- SIMCONNECT_STATE_ON = 1
+ SIMCONNECT_STATE_OFF = 0
+ SIMCONNECT_STATE_ON = 1
# Object Data Request Period values
class SIMCONNECT_PERIOD(CtypesEnum): #
- SIMCONNECT_PERIOD_NEVER = 0
- SIMCONNECT_PERIOD_ONCE = 1
- SIMCONNECT_PERIOD_VISUAL_FRAME = 2
- SIMCONNECT_PERIOD_SIM_FRAME = 3
- SIMCONNECT_PERIOD_SECOND = 4
+ SIMCONNECT_PERIOD_NEVER = 0
+ SIMCONNECT_PERIOD_ONCE = 1
+ SIMCONNECT_PERIOD_VISUAL_FRAME = 2
+ SIMCONNECT_PERIOD_SIM_FRAME = 3
+ SIMCONNECT_PERIOD_SECOND = 4
class SIMCONNECT_MISSION_END(CtypesEnum): #
- SIMCONNECT_MISSION_FAILED = 0
- SIMCONNECT_MISSION_CRASHED = 1
- SIMCONNECT_MISSION_SUCCEEDED = 2
+ SIMCONNECT_MISSION_FAILED = 0
+ SIMCONNECT_MISSION_CRASHED = 1
+ SIMCONNECT_MISSION_SUCCEEDED = 2
# ClientData Request Period values
class SIMCONNECT_CLIENT_DATA_PERIOD(CtypesEnum): #
- SIMCONNECT_CLIENT_DATA_PERIOD_NEVER = 0
- SIMCONNECT_CLIENT_DATA_PERIOD_ONCE = 1
- SIMCONNECT_CLIENT_DATA_PERIOD_VISUAL_FRAME = 2
- SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET = 3
- SIMCONNECT_CLIENT_DATA_PERIOD_SECOND = 4
+ SIMCONNECT_CLIENT_DATA_PERIOD_NEVER = 0
+ SIMCONNECT_CLIENT_DATA_PERIOD_ONCE = 1
+ SIMCONNECT_CLIENT_DATA_PERIOD_VISUAL_FRAME = 2
+ SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET = 3
+ SIMCONNECT_CLIENT_DATA_PERIOD_SECOND = 4
class SIMCONNECT_TEXT_TYPE(CtypesEnum): #
- SIMCONNECT_TEXT_TYPE_SCROLL_BLACK = 0
- SIMCONNECT_TEXT_TYPE_SCROLL_WHITE = 1
- SIMCONNECT_TEXT_TYPE_SCROLL_RED = 2
- SIMCONNECT_TEXT_TYPE_SCROLL_GREEN = 3
- SIMCONNECT_TEXT_TYPE_SCROLL_BLUE = 4
- SIMCONNECT_TEXT_TYPE_SCROLL_YELLOW = 5
- SIMCONNECT_TEXT_TYPE_SCROLL_MAGENTA = 6
- SIMCONNECT_TEXT_TYPE_SCROLL_CYAN = 7
- SIMCONNECT_TEXT_TYPE_PRINT_BLACK = 0x100
- SIMCONNECT_TEXT_TYPE_PRINT_WHITE = 0x101
- SIMCONNECT_TEXT_TYPE_PRINT_RED = 0x102
- SIMCONNECT_TEXT_TYPE_PRINT_GREEN = 0x103
- SIMCONNECT_TEXT_TYPE_PRINT_BLUE = 0x104
- SIMCONNECT_TEXT_TYPE_PRINT_YELLOW = 0x105
- SIMCONNECT_TEXT_TYPE_PRINT_MAGENTA = 0x106
- SIMCONNECT_TEXT_TYPE_PRINT_CYAN = 0x107
- SIMCONNECT_TEXT_TYPE_MENU = 0x0200
+ SIMCONNECT_TEXT_TYPE_SCROLL_BLACK = 0
+ SIMCONNECT_TEXT_TYPE_SCROLL_WHITE = 1
+ SIMCONNECT_TEXT_TYPE_SCROLL_RED = 2
+ SIMCONNECT_TEXT_TYPE_SCROLL_GREEN = 3
+ SIMCONNECT_TEXT_TYPE_SCROLL_BLUE = 4
+ SIMCONNECT_TEXT_TYPE_SCROLL_YELLOW = 5
+ SIMCONNECT_TEXT_TYPE_SCROLL_MAGENTA = 6
+ SIMCONNECT_TEXT_TYPE_SCROLL_CYAN = 7
+ SIMCONNECT_TEXT_TYPE_PRINT_BLACK = 0x100
+ SIMCONNECT_TEXT_TYPE_PRINT_WHITE = 0x101
+ SIMCONNECT_TEXT_TYPE_PRINT_RED = 0x102
+ SIMCONNECT_TEXT_TYPE_PRINT_GREEN = 0x103
+ SIMCONNECT_TEXT_TYPE_PRINT_BLUE = 0x104
+ SIMCONNECT_TEXT_TYPE_PRINT_YELLOW = 0x105
+ SIMCONNECT_TEXT_TYPE_PRINT_MAGENTA = 0x106
+ SIMCONNECT_TEXT_TYPE_PRINT_CYAN = 0x107
+ SIMCONNECT_TEXT_TYPE_MENU = 0x0200
class SIMCONNECT_TEXT_RESULT(CtypesEnum): #
- SIMCONNECT_TEXT_RESULT_MENU_SELECT_1 = 0
- SIMCONNECT_TEXT_RESULT_MENU_SELECT_2 = 1
- SIMCONNECT_TEXT_RESULT_MENU_SELECT_3 = 2
- SIMCONNECT_TEXT_RESULT_MENU_SELECT_4 = 3
- SIMCONNECT_TEXT_RESULT_MENU_SELECT_5 = 4
- SIMCONNECT_TEXT_RESULT_MENU_SELECT_6 = 5
- SIMCONNECT_TEXT_RESULT_MENU_SELECT_7 = 6
- SIMCONNECT_TEXT_RESULT_MENU_SELECT_8 = 7
- SIMCONNECT_TEXT_RESULT_MENU_SELECT_9 = 8
- SIMCONNECT_TEXT_RESULT_MENU_SELECT_10 = 9
- SIMCONNECT_TEXT_RESULT_DISPLAYED = 0x10000
- SIMCONNECT_TEXT_RESULT_QUEUED = 0x10001
- SIMCONNECT_TEXT_RESULT_REMOVED = 0x1002
- SIMCONNECT_TEXT_RESULT_REPLACED = 0x10003
- SIMCONNECT_TEXT_RESULT_TIMEOUT = 0x10004
+ SIMCONNECT_TEXT_RESULT_MENU_SELECT_1 = 0
+ SIMCONNECT_TEXT_RESULT_MENU_SELECT_2 = 1
+ SIMCONNECT_TEXT_RESULT_MENU_SELECT_3 = 2
+ SIMCONNECT_TEXT_RESULT_MENU_SELECT_4 = 3
+ SIMCONNECT_TEXT_RESULT_MENU_SELECT_5 = 4
+ SIMCONNECT_TEXT_RESULT_MENU_SELECT_6 = 5
+ SIMCONNECT_TEXT_RESULT_MENU_SELECT_7 = 6
+ SIMCONNECT_TEXT_RESULT_MENU_SELECT_8 = 7
+ SIMCONNECT_TEXT_RESULT_MENU_SELECT_9 = 8
+ SIMCONNECT_TEXT_RESULT_MENU_SELECT_10 = 9
+ SIMCONNECT_TEXT_RESULT_DISPLAYED = 0x10000
+ SIMCONNECT_TEXT_RESULT_QUEUED = 0x10001
+ SIMCONNECT_TEXT_RESULT_REMOVED = 0x1002
+ SIMCONNECT_TEXT_RESULT_REPLACED = 0x10003
+ SIMCONNECT_TEXT_RESULT_TIMEOUT = 0x10004
class SIMCONNECT_WEATHER_MODE(CtypesEnum): #
- SIMCONNECT_WEATHER_MODE_THEME = 0
- SIMCONNECT_WEATHER_MODE_RWW = 1
- SIMCONNECT_WEATHER_MODE_CUSTOM = 2
- SIMCONNECT_WEATHER_MODE_GLOBAL = 3
-
+ SIMCONNECT_WEATHER_MODE_THEME = 0
+ SIMCONNECT_WEATHER_MODE_RWW = 1
+ SIMCONNECT_WEATHER_MODE_CUSTOM = 2
+ SIMCONNECT_WEATHER_MODE_GLOBAL = 3
class SIMCONNECT_FACILITY_DATA_TYPE(CtypesEnum):
- SIMCONNECT_FACILITY_DATA_AIRPORT = 0
- SIMCONNECT_FACILITY_DATA_RUNWAY = 1
- SIMCONNECT_FACILITY_DATA_START = 2
- SIMCONNECT_FACILITY_DATA_FREQUENCY = 3
- SIMCONNECT_FACILITY_DATA_HELIPAD = 4
- SIMCONNECT_FACILITY_DATA_APPROACH = 5
- SIMCONNECT_FACILITY_DATA_APPROACH_TRANSITION = 6
- SIMCONNECT_FACILITY_DATA_APPROACH_LEG = 7
- SIMCONNECT_FACILITY_DATA_FINAL_APPROACH_LEG = 8
- SIMCONNECT_FACILITY_DATA_MISSED_APPROACH_LEG = 9
- SIMCONNECT_FACILITY_DATA_DEPARTURE = 10
- SIMCONNECT_FACILITY_DATA_ARRIVAL = 11
- SIMCONNECT_FACILITY_DATA_RUNWAY_TRANSITION = 12
- SIMCONNECT_FACILITY_DATA_ENROUTE_TRANSITION = 13
- SIMCONNECT_FACILITY_DATA_TAXI_POINT = 14
- SIMCONNECT_FACILITY_DATA_TAXI_PARKING = 15
- SIMCONNECT_FACILITY_DATA_TAXI_PATH = 16
- SIMCONNECT_FACILITY_DATA_TAXI_NAME = 17
- SIMCONNECT_FACILITY_DATA_JETWAY = 18
- SIMCONNECT_FACILITY_DATA_VOR = 19
- SIMCONNECT_FACILITY_DATA_NDB = 20
- SIMCONNECT_FACILITY_DATA_WAYPOINT = 21
- SIMCONNECT_FACILITY_DATA_ROUTE = 22
- SIMCONNECT_FACILITY_DATA_PAVEMENT = 23
- SIMCONNECT_FACILITY_DATA_APPROACH_LIGHTS = 24
- SIMCONNECT_FACILITY_DATA_VASI = 25
- SIMCONNECT_FACILITY_DATA_VDGS = 26
- SIMCONNECT_FACILITY_DATA_HOLDING_PATTERN = 27
- SIMCONNECT_FACILITY_DATA_TAXI_PARKING_AIRLINE = 28
+ SIMCONNECT_FACILITY_DATA_AIRPORT = 0
+ SIMCONNECT_FACILITY_DATA_RUNWAY = 1
+ SIMCONNECT_FACILITY_DATA_START = 2
+ SIMCONNECT_FACILITY_DATA_FREQUENCY = 3
+ SIMCONNECT_FACILITY_DATA_HELIPAD = 4
+ SIMCONNECT_FACILITY_DATA_APPROACH = 5
+ SIMCONNECT_FACILITY_DATA_APPROACH_TRANSITION = 6
+ SIMCONNECT_FACILITY_DATA_APPROACH_LEG = 7
+ SIMCONNECT_FACILITY_DATA_FINAL_APPROACH_LEG = 8
+ SIMCONNECT_FACILITY_DATA_MISSED_APPROACH_LEG = 9
+ SIMCONNECT_FACILITY_DATA_DEPARTURE = 10
+ SIMCONNECT_FACILITY_DATA_ARRIVAL = 11
+ SIMCONNECT_FACILITY_DATA_RUNWAY_TRANSITION = 12
+ SIMCONNECT_FACILITY_DATA_ENROUTE_TRANSITION = 13
+ SIMCONNECT_FACILITY_DATA_TAXI_POINT = 14
+ SIMCONNECT_FACILITY_DATA_TAXI_PARKING = 15
+ SIMCONNECT_FACILITY_DATA_TAXI_PATH = 16
+ SIMCONNECT_FACILITY_DATA_TAXI_NAME = 17
+ SIMCONNECT_FACILITY_DATA_JETWAY = 18
+ SIMCONNECT_FACILITY_DATA_VOR = 19
+ SIMCONNECT_FACILITY_DATA_NDB = 20
+ SIMCONNECT_FACILITY_DATA_WAYPOINT = 21
+ SIMCONNECT_FACILITY_DATA_ROUTE = 22
+ SIMCONNECT_FACILITY_DATA_PAVEMENT = 23
+ SIMCONNECT_FACILITY_DATA_APPROACH_LIGHTS = 24
+ SIMCONNECT_FACILITY_DATA_VASI = 25
+ SIMCONNECT_FACILITY_DATA_VDGS = 26
+ SIMCONNECT_FACILITY_DATA_HOLDING_PATTERN = 27
+ SIMCONNECT_FACILITY_DATA_TAXI_PARKING_AIRLINE = 28
+
class SIMCONNECT_FACILITY_LIST_TYPE(CtypesEnum): #
- SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT = 0
- SIMCONNECT_FACILITY_LIST_TYPE_WAYPOINT = 1
- SIMCONNECT_FACILITY_LIST_TYPE_NDB = 2
- SIMCONNECT_FACILITY_LIST_TYPE_VOR = 3
- SIMCONNECT_FACILITY_LIST_TYPE_COUNT = 4 # invalid
+ SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT = 0
+ SIMCONNECT_FACILITY_LIST_TYPE_WAYPOINT = 1
+ SIMCONNECT_FACILITY_LIST_TYPE_NDB = 2
+ SIMCONNECT_FACILITY_LIST_TYPE_VOR = 3
+ SIMCONNECT_FACILITY_LIST_TYPE_COUNT = 4 # invalid
class SIMCONNECT_VOR_FLAGS(CtypesFlagEnum): # flags for SIMCONNECT_RECV_ID_VOR_LIST
- SIMCONNECT_RECV_ID_VOR_LIST_HAS_NAV_SIGNAL = 0x00000001 # Has Nav signal
- SIMCONNECT_RECV_ID_VOR_LIST_HAS_LOCALIZER = 0x00000002 # Has localizer
- SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE = 0x00000004 # Has Nav signal
- SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME = 0x00000008 # Station has DME
-
-
+ SIMCONNECT_RECV_ID_VOR_LIST_HAS_NAV_SIGNAL = 0x00000001 # Has Nav signal
+ SIMCONNECT_RECV_ID_VOR_LIST_HAS_LOCALIZER = 0x00000002 # Has localizer
+ SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE = 0x00000004 # Has Nav signal
+ SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME = 0x00000008 # Station has DME
# bits for the Waypoint Flags field: may be combined
class SIMCONNECT_WAYPOINT_FLAGS(CtypesFlagEnum): #
- SIMCONNECT_WAYPOINT_NONE = 0x00 #
- SIMCONNECT_WAYPOINT_SPEED_REQUESTED = 0x04 # requested speed at waypoint is valid
- SIMCONNECT_WAYPOINT_THROTTLE_REQUESTED = 0x08 # request a specific throttle percentage
- SIMCONNECT_WAYPOINT_COMPUTE_VERTICAL_SPEED = 0x10 # compute vertical to speed to reach waypoint altitude when crossing the waypoint
- SIMCONNECT_WAYPOINT_ALTITUDE_IS_AGL = 0x20 # AltitudeIsAGL
- SIMCONNECT_WAYPOINT_ON_GROUND = 0x00100000 # place this waypoint on the ground
- SIMCONNECT_WAYPOINT_REVERSE = 0x00200000 # Back up to this waypoint. Only valid on first waypoint
- SIMCONNECT_WAYPOINT_WRAP_TO_FIRST = 0x00400000 # Wrap around back to first waypoint. Only valid on last waypoint.
- SIMCONNECT_WAYPOINT_KEEP_LAST_HEADING = 0x01000000 # Object doesn't only go from waypoint to waypoint using position but it will also keep the same heading computed on the last 2 waypoints
- SIMCONNECT_WAYPOINT_YIELD_TO_USER = 0x02000000 # Object will never be too close of the player. If waypoints pass too close of the player, the object will stop and wait
- SIMCONNECT_WAYPOINT_CAN_REVERSE = 0x04000000 # This flags handle the behaviour of the object if it can't reach a waypoint. By default, it will take a other way and try to reach this point again. With this flag, object will try some stuff to reach this waypoint in a better condition (moving backwards...)
+ SIMCONNECT_WAYPOINT_NONE = 0x00 #
+ SIMCONNECT_WAYPOINT_SPEED_REQUESTED = 0x04 # requested speed at waypoint is valid
+ SIMCONNECT_WAYPOINT_THROTTLE_REQUESTED = (
+ 0x08 # request a specific throttle percentage
+ )
+ SIMCONNECT_WAYPOINT_COMPUTE_VERTICAL_SPEED = 0x10 # compute vertical to speed to reach waypoint altitude when crossing the waypoint
+ SIMCONNECT_WAYPOINT_ALTITUDE_IS_AGL = 0x20 # AltitudeIsAGL
+ SIMCONNECT_WAYPOINT_ON_GROUND = 0x00100000 # place this waypoint on the ground
+ SIMCONNECT_WAYPOINT_REVERSE = (
+ 0x00200000 # Back up to this waypoint. Only valid on first waypoint
+ )
+ SIMCONNECT_WAYPOINT_WRAP_TO_FIRST = (
+ 0x00400000 # Wrap around back to first waypoint. Only valid on last waypoint.
+ )
+ SIMCONNECT_WAYPOINT_KEEP_LAST_HEADING = 0x01000000 # Object doesn't only go from waypoint to waypoint using position but it will also keep the same heading computed on the last 2 waypoints
+ SIMCONNECT_WAYPOINT_YIELD_TO_USER = 0x02000000 # Object will never be too close of the player. If waypoints pass too close of the player, the object will stop and wait
+ SIMCONNECT_WAYPOINT_CAN_REVERSE = 0x04000000 # This flags handle the behaviour of the object if it can't reach a waypoint. By default, it will take a other way and try to reach this point again. With this flag, object will try some stuff to reach this waypoint in a better condition (moving backwards...)
+
class SIMCONNECT_EVENT_FLAG(CtypesFlagEnum): #
- SIMCONNECT_EVENT_FLAG_DEFAULT = 0x00000000 #
- SIMCONNECT_EVENT_FLAG_FAST_REPEAT_TIMER = 0x00000001 # set event repeat timer to simulate fast repeat
- SIMCONNECT_EVENT_FLAG_SLOW_REPEAT_TIMER = 0x00000002 # set event repeat timer to simulate slow repeat
- SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY = 0x00000010 # interpret GroupID parameter as priority value
+ SIMCONNECT_EVENT_FLAG_DEFAULT = 0x00000000 #
+ SIMCONNECT_EVENT_FLAG_FAST_REPEAT_TIMER = (
+ 0x00000001 # set event repeat timer to simulate fast repeat
+ )
+ SIMCONNECT_EVENT_FLAG_SLOW_REPEAT_TIMER = (
+ 0x00000002 # set event repeat timer to simulate slow repeat
+ )
+ SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY = (
+ 0x00000010 # interpret GroupID parameter as priority value
+ )
class SIMCONNECT_DATA_REQUEST_FLAG(CtypesFlagEnum): #
- SIMCONNECT_DATA_REQUEST_FLAG_DEFAULT = 0x00000000
- SIMCONNECT_DATA_REQUEST_FLAG_CHANGED = 0x00000001 # send requested data when value(s) change
- SIMCONNECT_DATA_REQUEST_FLAG_TAGGED = 0x00000002 # send requested data in tagged format
+ SIMCONNECT_DATA_REQUEST_FLAG_DEFAULT = 0x00000000
+ SIMCONNECT_DATA_REQUEST_FLAG_CHANGED = (
+ 0x00000001 # send requested data when value(s) change
+ )
+ SIMCONNECT_DATA_REQUEST_FLAG_TAGGED = (
+ 0x00000002 # send requested data in tagged format
+ )
class SIMCONNECT_DATA_SET_FLAG(CtypesFlagEnum): #
- SIMCONNECT_DATA_SET_FLAG_DEFAULT = 0x00000000
- SIMCONNECT_DATA_SET_FLAG_TAGGED = 0x00000001 # data is in tagged format
+ SIMCONNECT_DATA_SET_FLAG_DEFAULT = 0x00000000
+ SIMCONNECT_DATA_SET_FLAG_TAGGED = 0x00000001 # data is in tagged format
class SIMCONNECT_CREATE_CLIENT_DATA_FLAG(CtypesFlagEnum): #
- SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT = 0x00000000 #
- SIMCONNECT_CREATE_CLIENT_DATA_FLAG_READ_ONLY = 0x00000001 # permit only ClientData creator to write into ClientData
+ SIMCONNECT_CREATE_CLIENT_DATA_FLAG_DEFAULT = 0x00000000 #
+ SIMCONNECT_CREATE_CLIENT_DATA_FLAG_READ_ONLY = (
+ 0x00000001 # permit only ClientData creator to write into ClientData
+ )
class SIMCONNECT_CLIENT_DATA_REQUEST_FLAG(CtypesFlagEnum): #
- SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT = 0x00000000 #
- SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_CHANGED = 0x00000001 # send requested ClientData when value(s) change
- SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_TAGGED = 0x00000002 # send requested ClientData in tagged format
+ SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT = 0x00000000 #
+ SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_CHANGED = (
+ 0x00000001 # send requested ClientData when value(s) change
+ )
+ SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_TAGGED = (
+ 0x00000002 # send requested ClientData in tagged format
+ )
class SIMCONNECT_CLIENT_DATA_SET_FLAG(CtypesFlagEnum): #
- SIMCONNECT_CLIENT_DATA_SET_FLAG_DEFAULT = 0x00000000 #
- SIMCONNECT_CLIENT_DATA_SET_FLAG_TAGGED = 0x00000001 # data is in tagged format
+ SIMCONNECT_CLIENT_DATA_SET_FLAG_DEFAULT = 0x00000000 #
+ SIMCONNECT_CLIENT_DATA_SET_FLAG_TAGGED = 0x00000001 # data is in tagged format
-class SIMCONNECT_VIEW_SYSTEM_EVENT_DATA(CtypesFlagEnum): # dwData contains these flags for the "View" System Event
- SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_COCKPIT_2D = 0x00000001 # 2D Panels in cockpit view
- SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_COCKPIT_VIRTUAL = 0x00000002 # Virtual (3D) panels in cockpit view
- SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_ORTHOGONAL = 0x00000004 # Orthogonal (Map) view
+class SIMCONNECT_VIEW_SYSTEM_EVENT_DATA(
+ CtypesFlagEnum
+): # dwData contains these flags for the "View" System Event
+ SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_COCKPIT_2D = (
+ 0x00000001 # 2D Panels in cockpit view
+ )
+ SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_COCKPIT_VIRTUAL = (
+ 0x00000002 # Virtual (3D) panels in cockpit view
+ )
+ SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_ORTHOGONAL = 0x00000004 # Orthogonal (Map) view
-class SIMCONNECT_SOUND_SYSTEM_EVENT_DATA(CtypesFlagEnum): # dwData contains these flags for the "Sound" System Event
- SIMCONNECT_SOUND_SYSTEM_EVENT_DATA_MASTER = 0x00000001# Sound Master
+class SIMCONNECT_SOUND_SYSTEM_EVENT_DATA(
+ CtypesFlagEnum
+): # dwData contains these flags for the "Sound" System Event
+ SIMCONNECT_SOUND_SYSTEM_EVENT_DATA_MASTER = 0x00000001 # Sound Master
class SIMCONNECT_PICK_FLAGS(CtypesFlagEnum):
- SIMCONNECT_PICK_GROUND = 0x01 # pick ground/ pick result item is ground location
- SIMCONNECT_PICK_AI = 0x02 # pick AI / pick result item is AI, (dwSimObjectID is valid)
- SIMCONNECT_PICK_SCENERY = 0x04 # pick scenery/ pick result item is scenery object (hSceneryObject is valid)
- SIMCONNECT_PICK_ALL = 0x04 | 0x02 | 0x01 # pick all / (not valid on pick result item)
- SIMCONNECT_PICK_COORDSASPIXELS = 0x08 #
-
-
+ SIMCONNECT_PICK_GROUND = 0x01 # pick ground/ pick result item is ground location
+ SIMCONNECT_PICK_AI = (
+ 0x02 # pick AI / pick result item is AI, (dwSimObjectID is valid)
+ )
+ SIMCONNECT_PICK_SCENERY = 0x04 # pick scenery/ pick result item is scenery object (hSceneryObject is valid)
+ SIMCONNECT_PICK_ALL = (
+ 0x04 | 0x02 | 0x01
+ ) # pick all / (not valid on pick result item)
+ SIMCONNECT_PICK_COORDSASPIXELS = 0x08 #
# ----------------------------------------------------------------------------
# User-defined enums
# ----------------------------------------------------------------------------
class SIMCONNECT_NOTIFICATION_GROUP_ID(
- AutoName
+ AutoName
): # client-defined notification group ID
- pass
+ pass
class SIMCONNECT_INPUT_GROUP_ID(AutoName): # client-defined input group ID
- pass
+ pass
class SIMCONNECT_DATA_DEFINITION_ID(AutoName): # client-defined data definition ID
- pass
+ pass
class SIMCONNECT_DATA_REQUEST_ID(AutoName): # client-defined request data ID
- pass
+ pass
class SIMCONNECT_CLIENT_EVENT_ID(AutoName): # client-defined client event ID
- # https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_SubscribeToSystemEvent.htm
- EVENT_SIM_START = auto()
- EVENT_SIM_STOP = auto()
- EVENT_SIM_PAUSED = auto()
- EVENT_SIM_UNPAUSED = auto()
- EVENT_SIM_RUNNING = auto()
- EVENT_SIM_AIRCRAFT_LOADED = auto()
- EVENT_SIM_REQUEST_AIRCRAFT = auto()
- EVENT_SIM_PAUSE_STATE = auto()
- EVENT_SIM_REQUEST_ENUMERATE_SIM_OBJECTS_AND_LIVERIES = auto()
-
- pass
+ # https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_SubscribeToSystemEvent.htm
+ EVENT_SIM_START = auto()
+ EVENT_SIM_STOP = auto()
+ EVENT_SIM_PAUSED = auto()
+ EVENT_SIM_UNPAUSED = auto()
+ EVENT_SIM_RUNNING = auto()
+ EVENT_SIM_AIRCRAFT_LOADED = auto()
+ EVENT_SIM_REQUEST_AIRCRAFT = auto()
+ EVENT_SIM_PAUSE_STATE = auto()
+ EVENT_SIM_REQUEST_ENUMERATE_SIM_OBJECTS_AND_LIVERIES = auto()
+
+ pass
class SIMCONNECT_CLIENT_DATA_ID(AutoName): # client-defined client data ID
- pass
+ pass
class SIMCONNECT_CLIENT_DATA_DEFINITION_ID(
- AutoName
+ AutoName
): # client-defined client data definition ID
- pass
+ pass
# ----------------------------------------------------------------------------
@@ -411,277 +442,297 @@ class SIMCONNECT_CLIENT_DATA_DEFINITION_ID(
class SIMCONNECT_RECV(Structure):
- _fields_ = [("dwSize", DWORD), ("dwVersion", DWORD), ("dwID", DWORD)]
+ _fields_ = [("dwSize", DWORD), ("dwVersion", DWORD), ("dwID", DWORD)]
class SIMCONNECT_RECV_EXCEPTION(
- SIMCONNECT_RECV
+ SIMCONNECT_RECV
): # when dwID == SIMCONNECT_RECV_ID_EXCEPTION
- _fields_ = [
- ("dwException", DWORD), # see SIMCONNECT_EXCEPTION
- ("UNKNOWN_SENDID", DWORD), #
- ("dwSendID", DWORD), # see SimConnect_GetLastSentPacketID
- ("UNKNOWN_INDEX", DWORD), #
- ("dwIndex", DWORD), # index of parameter that was source of error
- ]
+ _fields_ = [
+ ("dwException", DWORD), # see SIMCONNECT_EXCEPTION
+ ("UNKNOWN_SENDID", DWORD), #
+ ("dwSendID", DWORD), # see SimConnect_GetLastSentPacketID
+ ("UNKNOWN_INDEX", DWORD), #
+ ("dwIndex", DWORD), # index of parameter that was source of error
+ ]
class SIMCONNECT_RECV_OPEN(SIMCONNECT_RECV): # when dwID == SIMCONNECT_RECV_ID_OPEN
- _fields_ = [
- ("szApplicationName", c_char * 256),
- ("dwApplicationVersionMajor", DWORD),
- ("dwApplicationVersionMinor", DWORD),
- ("dwApplicationBuildMajor", DWORD),
- ("dwApplicationBuildMinor", DWORD),
- ("dwSimConnectVersionMajor", DWORD),
- ("dwSimConnectVersionMinor", DWORD),
- ("dwSimConnectBuildMajor", DWORD),
- ("dwSimConnectBuildMinor", DWORD),
- ("dwReserved1", DWORD),
- ("dwReserved2", DWORD),
- ]
+ _fields_ = [
+ ("szApplicationName", c_char * 256),
+ ("dwApplicationVersionMajor", DWORD),
+ ("dwApplicationVersionMinor", DWORD),
+ ("dwApplicationBuildMajor", DWORD),
+ ("dwApplicationBuildMinor", DWORD),
+ ("dwSimConnectVersionMajor", DWORD),
+ ("dwSimConnectVersionMinor", DWORD),
+ ("dwSimConnectBuildMajor", DWORD),
+ ("dwSimConnectBuildMinor", DWORD),
+ ("dwReserved1", DWORD),
+ ("dwReserved2", DWORD),
+ ]
class SIMCONNECT_RECV_QUIT(SIMCONNECT_RECV): # when dwID == SIMCONNECT_RECV_ID_QUIT
- pass
+ pass
class SIMCONNECT_RECV_EVENT(SIMCONNECT_RECV): # when dwID == SIMCONNECT_RECV_ID_EVENT
- UNKNOWN_GROUP = DWORD_MAX
- _fields_ = [
- ("uGroupID", DWORD),
- ("uEventID", DWORD),
- ("dwData", DWORD), # uEventID-dependent context
- ]
+ UNKNOWN_GROUP = DWORD_MAX
+ _fields_ = [
+ ("uGroupID", DWORD),
+ ("uEventID", DWORD),
+ ("dwData", DWORD), # uEventID-dependent context
+ ]
+
class SIMCONNECT_RECV_LIST_TEMPLATE(SIMCONNECT_RECV):
- _fields_ = [
- ("dwRequestID", DWORD),
- ("dwArraySize", DWORD),
- ("dwEntryNumber", DWORD), # when the array of items is too big for one send, which send this is (0..dwOutOf-1)
- ("dwOutOf", DWORD), # total number of transmissions the list is chopped into
- ]
+ _fields_ = [
+ ("dwRequestID", DWORD),
+ ("dwArraySize", DWORD),
+ (
+ "dwEntryNumber",
+ DWORD,
+ ), # when the array of items is too big for one send, which send this is (0..dwOutOf-1)
+ ("dwOutOf", DWORD), # total number of transmissions the list is chopped into
+ ]
class SIMCONNECT_RECV_EVENT_FILENAME(
- SIMCONNECT_RECV_EVENT
+ SIMCONNECT_RECV_EVENT
): # when dwID == SIMCONNECT_RECV_ID_EVENT_FILENAME
- _fields_ = [
- ("zFileName", c_char * MAX_PATH), # uEventID-dependent context
- ("dwFlags", DWORD),
- ]
+ _fields_ = [
+ ("zFileName", c_char * MAX_PATH), # uEventID-dependent context
+ ("dwFlags", DWORD),
+ ]
class SIMCONNECT_RECV_EVENT_OBJECT_ADDREMOVE(
- SIMCONNECT_RECV_EVENT
+ SIMCONNECT_RECV_EVENT
): # when dwID == SIMCONNECT_RECV_ID_EVENT_FILENAME
- eObjType = SIMCONNECT_SIMOBJECT_TYPE
+ eObjType = SIMCONNECT_SIMOBJECT_TYPE
class SIMCONNECT_RECV_EVENT_FRAME(
- SIMCONNECT_RECV_EVENT
+ SIMCONNECT_RECV_EVENT
): # when dwID == SIMCONNECT_RECV_ID_EVENT_FRAME
- _fields_ = [("fFrameRate", c_float), ("fSimSpeed", c_float)]
+ _fields_ = [("fFrameRate", c_float), ("fSimSpeed", c_float)]
class SIMCONNECT_RECV_EVENT_MULTIPLAYER_SERVER_STARTED(SIMCONNECT_RECV_EVENT):
- # when dwID == SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SERVER_STARTED
- # No event specific data, for now
- pass
+ # when dwID == SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SERVER_STARTED
+ # No event specific data, for now
+ pass
class SIMCONNECT_RECV_EVENT_MULTIPLAYER_CLIENT_STARTED(SIMCONNECT_RECV_EVENT):
- # when dwID == SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_CLIENT_STARTED
- # No event specific data, for now
- pass
+ # when dwID == SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_CLIENT_STARTED
+ # No event specific data, for now
+ pass
class SIMCONNECT_RECV_EVENT_MULTIPLAYER_SESSION_ENDED(SIMCONNECT_RECV_EVENT):
- # when dwID == SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SESSION_ENDED
- # No event specific data, for now
- pass
+ # when dwID == SIMCONNECT_RECV_ID_EVENT_MULTIPLAYER_SESSION_ENDED
+ # No event specific data, for now
+ pass
# SIMCONNECT_DATA_RACE_RESULT
class SIMCONNECT_DATA_RACE_RESULT(Structure):
- _fields_ = [
- ("dwNumberOfRacers", DWORD), # The total number of racers
- ("szPlayerName", c_char * MAX_PATH), # The name of the player
- (
- "szSessionType",
- c_char * MAX_PATH,
- ), # The type of the multiplayer session: "LAN", "GAMESPY")
- ("szAircraft", c_char * MAX_PATH), # The aircraft type
- ("szPlayerRole", c_char * MAX_PATH), # The player role in the mission
- ("fTotalTime", c_double), # Total time in seconds, 0 means DNF
- ("fPenaltyTime", c_double), # Total penalty time in seconds
- (
- "MissionGUID",
- DWORD,
- ), # The name of the mission to execute, NULL if no mission
- ("dwIsDisqualified", c_double), # non 0 - disqualified, 0 - not disqualified
- ]
+ _fields_ = [
+ ("dwNumberOfRacers", DWORD), # The total number of racers
+ ("szPlayerName", c_char * MAX_PATH), # The name of the player
+ (
+ "szSessionType",
+ c_char * MAX_PATH,
+ ), # The type of the multiplayer session: "LAN", "GAMESPY")
+ ("szAircraft", c_char * MAX_PATH), # The aircraft type
+ ("szPlayerRole", c_char * MAX_PATH), # The player role in the mission
+ ("fTotalTime", c_double), # Total time in seconds, 0 means DNF
+ ("fPenaltyTime", c_double), # Total penalty time in seconds
+ (
+ "MissionGUID",
+ DWORD,
+ ), # The name of the mission to execute, NULL if no mission
+ ("dwIsDisqualified", c_double), # non 0 - disqualified, 0 - not disqualified
+ ]
class SIMCONNECT_RECV_EVENT_RACE_END(
- SIMCONNECT_RECV_EVENT
+ SIMCONNECT_RECV_EVENT
): # when dwID == SIMCONNECT_RECV_ID_EVENT_RACE_END
- RacerData = SIMCONNECT_DATA_RACE_RESULT
- _fields_ = [("dwRacerNumber", DWORD)] # The index of the racer the results are for
+ RacerData = SIMCONNECT_DATA_RACE_RESULT
+ _fields_ = [("dwRacerNumber", DWORD)] # The index of the racer the results are for
class SIMCONNECT_RECV_EVENT_RACE_LAP(
- SIMCONNECT_RECV_EVENT
+ SIMCONNECT_RECV_EVENT
): # when dwID == SIMCONNECT_RECV_ID_EVENT_RACE_LAP
- RacerData = SIMCONNECT_DATA_RACE_RESULT
- _fields_ = [("dwLapIndex", DWORD)] # The index of the lap the results are for
+ RacerData = SIMCONNECT_DATA_RACE_RESULT
+ _fields_ = [("dwLapIndex", DWORD)] # The index of the lap the results are for
class SIMCONNECT_RECV_SIMOBJECT_DATA(SIMCONNECT_RECV):
- _fields_ = [
- ("dwRequestID", DWORD),
- ("dwObjectID", DWORD),
- ("dwDefineID", DWORD),
- ("dwFlags", DWORD),
- ("dwentrynumber", DWORD),
- ("dwoutof", DWORD),
- ("dwDefineCount", DWORD),
- ("dwData", DWORD * 8192),
- ]
+ _fields_ = [
+ ("dwRequestID", DWORD),
+ ("dwObjectID", DWORD),
+ ("dwDefineID", DWORD),
+ ("dwFlags", DWORD),
+ ("dwentrynumber", DWORD),
+ ("dwoutof", DWORD),
+ ("dwDefineCount", DWORD),
+ ("dwData", DWORD * 8192),
+ ]
-class SIMCONNECT_RECV_CLIENT_BYTE_DATA(SIMCONNECT_RECV):
- _fields_ = [
- ("dwRequestID", DWORD),
- ("dwObjectID", DWORD),
- ("dwDefineID", DWORD),
- ("dwFlags", DWORD),
- ("dwentrynumber", DWORD),
- ("dwoutof", DWORD),
- ("dwDefineCount", DWORD),
- ("dwData", BYTE * 8192),
- ]
+class SIMCONNECT_RECV_CLIENT_BYTE_DATA(SIMCONNECT_RECV):
+ _fields_ = [
+ ("dwRequestID", DWORD),
+ ("dwObjectID", DWORD),
+ ("dwDefineID", DWORD),
+ ("dwFlags", DWORD),
+ ("dwentrynumber", DWORD),
+ ("dwoutof", DWORD),
+ ("dwDefineCount", DWORD),
+ ("dwData", BYTE * 8192),
+ ]
class SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE(SIMCONNECT_RECV_SIMOBJECT_DATA):
- _fields_ = []
+ _fields_ = []
class SIMCONNECT_RECV_CLIENT_DATA(
- SIMCONNECT_RECV_SIMOBJECT_DATA
+ SIMCONNECT_RECV_SIMOBJECT_DATA
): # when dwID == SIMCONNECT_RECV_ID_CLIENT_DATA
- #_fields_ = []
- pass
+ # _fields_ = []
+ pass
class SIMCONNECT_RECV_WEATHER_OBSERVATION(
- SIMCONNECT_RECV
+ SIMCONNECT_RECV
): # when dwID == SIMCONNECT_RECV_ID_WEATHER_OBSERVATION
- _fields_ = [
- ("dwRequestID", DWORD),
- (
- "szMetar",
- c_char * MAX_METAR_LENGTH.value,
- ), # Variable length string whose maximum size is MAX_METAR_LENGTH
- ]
+ _fields_ = [
+ ("dwRequestID", DWORD),
+ (
+ "szMetar",
+ c_char * MAX_METAR_LENGTH.value,
+ ), # Variable length string whose maximum size is MAX_METAR_LENGTH
+ ]
SIMCONNECT_CLOUD_STATE_ARRAY_WIDTH = 64
SIMCONNECT_CLOUD_STATE_ARRAY_SIZE = (
- SIMCONNECT_CLOUD_STATE_ARRAY_WIDTH * SIMCONNECT_CLOUD_STATE_ARRAY_WIDTH
+ SIMCONNECT_CLOUD_STATE_ARRAY_WIDTH * SIMCONNECT_CLOUD_STATE_ARRAY_WIDTH
)
class SIMCONNECT_RECV_CLOUD_STATE(SIMCONNECT_RECV):
- # when dwID == SIMCONNECT_RECV_ID_CLOUD_STATE
- _fields_ = [
- ("dwRequestID", DWORD),
- ("dwArraySize", DWORD),
- # SIMCONNECT_FIXEDTYPE_DATAV(BYTE, rgbData, dwArraySize, U1 /*member of UnmanagedType enum*/ , System::Byte /*cli type*/);
- ]
+ # when dwID == SIMCONNECT_RECV_ID_CLOUD_STATE
+ _fields_ = [
+ ("dwRequestID", DWORD),
+ ("dwArraySize", DWORD),
+ # SIMCONNECT_FIXEDTYPE_DATAV(BYTE, rgbData, dwArraySize, U1 /*member of UnmanagedType enum*/ , System::Byte /*cli type*/);
+ ]
-class SIMCONNECT_RECV_ASSIGNED_OBJECT_ID(SIMCONNECT_RECV): # when dwID == SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID
- _fields_ = [("dwRequestID", DWORD), ("dwObjectID", DWORD)]
+class SIMCONNECT_RECV_ASSIGNED_OBJECT_ID(
+ SIMCONNECT_RECV
+): # when dwID == SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID
+ _fields_ = [("dwRequestID", DWORD), ("dwObjectID", DWORD)]
-class SIMCONNECT_RECV_RESERVED_KEY(SIMCONNECT_RECV): # when dwID == SIMCONNECT_RECV_ID_RESERVED_KEY
- _fields_ = [("szChoiceReserved", c_char * 30), ("szReservedKey", c_char * 30)]
+class SIMCONNECT_RECV_RESERVED_KEY(
+ SIMCONNECT_RECV
+): # when dwID == SIMCONNECT_RECV_ID_RESERVED_KEY
+ _fields_ = [("szChoiceReserved", c_char * 30), ("szReservedKey", c_char * 30)]
-class SIMCONNECT_RECV_SYSTEM_STATE(SIMCONNECT_RECV): # when dwID == SIMCONNECT_RECV_ID_SYSTEM_STATE
- _fields_ = [
- ("dwRequestID", DWORD),
- ("dwInteger", DWORD),
- ("fFloat", c_float),
- ("szString", c_char * MAX_PATH),
- ]
+class SIMCONNECT_RECV_SYSTEM_STATE(
+ SIMCONNECT_RECV
+): # when dwID == SIMCONNECT_RECV_ID_SYSTEM_STATE
+ _fields_ = [
+ ("dwRequestID", DWORD),
+ ("dwInteger", DWORD),
+ ("fFloat", c_float),
+ ("szString", c_char * MAX_PATH),
+ ]
class SIMCONNECT_RECV_CUSTOM_ACTION(SIMCONNECT_RECV_EVENT): #
- _fields_ = [
- ("guidInstanceId", DWORD), # Instance id of the action that executed
- ("dwWaitForCompletion", DWORD), # Wait for completion flag on the action
- (
- "szPayLoad",
- c_char,
- ), # Variable length string payload associated with the mission action.
- ]
+ _fields_ = [
+ ("guidInstanceId", DWORD), # Instance id of the action that executed
+ ("dwWaitForCompletion", DWORD), # Wait for completion flag on the action
+ (
+ "szPayLoad",
+ c_char,
+ ), # Variable length string payload associated with the mission action.
+ ]
class SIMCONNECT_RECV_EVENT_WEATHER_MODE(SIMCONNECT_RECV_EVENT): #
- _fields_ = (
- []
- ) # No event specific data - the new weather mode is in the base structure dwData member.
+ _fields_ = [] # No event specific data - the new weather mode is in the base structure dwData member.
+
class SIMCONNECT_ENUMERATE_SIMOBJECT_LIVERY(Structure):
- _fields_ = [
- ("AircraftTitle", c_char * 256),
- ("LiveryName", c_char * 256)]
-
+ _fields_ = [("AircraftTitle", c_char * 256), ("LiveryName", c_char * 256)]
+
# SIMCONNECT Object/Livery list
class SIMCONNECT_RECV_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST(SIMCONNECT_RECV):
- _fields_ = [
- ("dwRequestID", DWORD),
- ("dwArraySize", DWORD),
- ("dwEntryNumber", DWORD,), # when the array of items is too big for one send, which send this is (0..dwOutOf-1)
- ("dwOutOf", DWORD), # total number of transmissions the list is chopped into
- ]
-
-
+ _fields_ = [
+ ("dwRequestID", DWORD),
+ ("dwArraySize", DWORD),
+ (
+ "dwEntryNumber",
+ DWORD,
+ ), # when the array of items is too big for one send, which send this is (0..dwOutOf-1)
+ ("dwOutOf", DWORD), # total number of transmissions the list is chopped into
+ ]
+
+
+def SIMCONNECT_RECV_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST_FACTORY(num_items: int):
+ """creates a dynamic structure holding the returned data once the number of items returned is known"""
+
+ class factory(SIMCONNECT_RECV):
+ _fields_ = [
+ ("dwRequestID", DWORD),
+ ("dwArraySize", DWORD),
+ (
+ "dwEntryNumber",
+ DWORD,
+ ), # when the array of items is too big for one send, which send this is (0..dwOutOf-1)
+ (
+ "dwOutOf",
+ DWORD,
+ ), # total number of transmissions the list is chopped into
+ ("rgData", SIMCONNECT_ENUMERATE_SIMOBJECT_LIVERY * num_items),
+ ]
+
+ return factory
-def SIMCONNECT_RECV_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST_FACTORY(num_items : int):
- ''' creates a dynamic structure holding the returned data once the number of items returned is known '''
- class factory(SIMCONNECT_RECV):
- _fields_ = [
- ("dwRequestID", DWORD),
- ("dwArraySize", DWORD),
- ("dwEntryNumber", DWORD,), # when the array of items is too big for one send, which send this is (0..dwOutOf-1)
- ("dwOutOf", DWORD), # total number of transmissions the list is chopped into
- ("rgData", SIMCONNECT_ENUMERATE_SIMOBJECT_LIVERY * num_items)
- ]
-
- return factory
# SIMCONNECT_RECV_FACILITIES_LIST
class SIMCONNECT_RECV_FACILITIES_LIST(SIMCONNECT_RECV): #
- _fields_ = [
- ("dwRequestID", DWORD),
- ("dwArraySize", DWORD),
- ("dwEntryNumber", DWORD,), # when the array of items is too big for one send, which send this is (0..dwOutOf-1)
- ("dwOutOf", DWORD), # total number of transmissions the list is chopped into
- ]
+ _fields_ = [
+ ("dwRequestID", DWORD),
+ ("dwArraySize", DWORD),
+ (
+ "dwEntryNumber",
+ DWORD,
+ ), # when the array of items is too big for one send, which send this is (0..dwOutOf-1)
+ ("dwOutOf", DWORD), # total number of transmissions the list is chopped into
+ ]
# SIMCONNECT_DATA_FACILITY_AIRPORT
class SIMCONNECT_DATA_FACILITY_AIRPORT(Structure): #
- _fields_ = [
- ("Icao", c_char * 9), # ICAO of the object
- ("Latitude", c_double), # degrees
- ("Longitude", c_double), # degrees
- ("Altitude", c_double), # meters
- ]
+ _fields_ = [
+ ("Icao", c_char * 9), # ICAO of the object
+ ("Latitude", c_double), # degrees
+ ("Longitude", c_double), # degrees
+ ("Altitude", c_double), # meters
+ ]
# SIMCONNECT_RECV_AIRPORT_LIST
@@ -695,7 +746,7 @@ class SIMCONNECT_DATA_FACILITY_AIRPORT(Structure): #
# SIMCONNECT_DATA_FACILITY_WAYPOINT
class SIMCONNECT_DATA_FACILITY_WAYPOINT(SIMCONNECT_DATA_FACILITY_AIRPORT): #
- _fields_ = [("fMagVar", c_float)] # Magvar in degrees
+ _fields_ = [("fMagVar", c_float)] # Magvar in degrees
# SIMCONNECT_RECV_WAYPOINT_LIST
@@ -712,7 +763,7 @@ class SIMCONNECT_DATA_FACILITY_WAYPOINT(SIMCONNECT_DATA_FACILITY_AIRPORT): #
# SIMCONNECT_DATA_FACILITY_NDB
class SIMCONNECT_DATA_FACILITY_NDB(SIMCONNECT_DATA_FACILITY_WAYPOINT): #
- _fields_ = [("fFrequency", DWORD)] # frequency in Hz
+ _fields_ = [("fFrequency", DWORD)] # frequency in Hz
# SIMCONNECT_RECV_NDB_LIST
@@ -729,14 +780,14 @@ class SIMCONNECT_DATA_FACILITY_NDB(SIMCONNECT_DATA_FACILITY_WAYPOINT): #
# SIMCONNECT_DATA_FACILITY_VOR
class SIMCONNECT_DATA_FACILITY_VOR(SIMCONNECT_DATA_FACILITY_NDB): #
- _fields_ = [
- ("Flags", DWORD), # SIMCONNECT_VOR_FLAGS
- ("fLocalizer", c_float), # Localizer in degrees
- ("GlideLat", c_double), # Glide Slope Location (deg, deg, meters)
- ("GlideLon", c_double), #
- ("GlideAlt", c_double), #
- ("fGlideSlopeAngle", c_float), # Glide Slope in degrees
- ]
+ _fields_ = [
+ ("Flags", DWORD), # SIMCONNECT_VOR_FLAGS
+ ("fLocalizer", c_float), # Localizer in degrees
+ ("GlideLat", c_double), # Glide Slope Location (deg, deg, meters)
+ ("GlideLon", c_double), #
+ ("GlideAlt", c_double), #
+ ("fGlideSlopeAngle", c_float), # Glide Slope in degrees
+ ]
# SIMCONNECT_RECV_VOR_LIST
@@ -751,63 +802,62 @@ class SIMCONNECT_DATA_FACILITY_VOR(SIMCONNECT_DATA_FACILITY_NDB): #
class SIMCONNECT_RECV_PICK(
- SIMCONNECT_RECV
+ SIMCONNECT_RECV
): # when dwID == SIMCONNECT_RECV_ID_RESERVED_KEY
- _fields_ = [
- ("hContext", HANDLE),
- ("dwFlags", DWORD),
- ("Latitude", c_double), # degrees
- ("Longitude", c_double), # degrees
- ("Altitude", c_double), # feet
- ("xPos", c_int), # reserved
- ("yPos", c_int), # reserved
- ("dwSimObjectID", DWORD),
- ("hSceneryObject", HANDLE),
- (
- "dwentrynumber",
- DWORD,
- ), # if multiple objects returned, this is number out of .
- ("dwoutof", DWORD), # note: starts with 1, not 0.
- ]
+ _fields_ = [
+ ("hContext", HANDLE),
+ ("dwFlags", DWORD),
+ ("Latitude", c_double), # degrees
+ ("Longitude", c_double), # degrees
+ ("Altitude", c_double), # feet
+ ("xPos", c_int), # reserved
+ ("yPos", c_int), # reserved
+ ("dwSimObjectID", DWORD),
+ ("hSceneryObject", HANDLE),
+ (
+ "dwentrynumber",
+ DWORD,
+ ), # if multiple objects returned, this is number out of .
+ ("dwoutof", DWORD), # note: starts with 1, not 0.
+ ]
# SIMCONNECT_DATATYPE_INITPOSITION
class SIMCONNECT_DATA_INITPOSITION(Structure): #
- _fields_ = [
- ("Latitude", c_double), # degrees
- ("Longitude", c_double), # degrees
- ("Altitude", c_double), # feet
- ("Pitch", c_double), # degrees
- ("Bank", c_double), # degrees
- ("Heading", c_double), # degrees
- ("OnGround", DWORD), # 1=force to be on the ground
- ("Airspeed", DWORD), # knots
- ]
+ _fields_ = [
+ ("Latitude", c_double), # degrees
+ ("Longitude", c_double), # degrees
+ ("Altitude", c_double), # feet
+ ("Pitch", c_double), # degrees
+ ("Bank", c_double), # degrees
+ ("Heading", c_double), # degrees
+ ("OnGround", DWORD), # 1=force to be on the ground
+ ("Airspeed", DWORD), # knots
+ ]
# SIMCONNECT_DATATYPE_MARKERSTATE
class SIMCONNECT_DATA_MARKERSTATE(Structure): #
- _fields_ = [("szMarkerName", c_char * 64), ("dwMarkerState", DWORD)]
+ _fields_ = [("szMarkerName", c_char * 64), ("dwMarkerState", DWORD)]
# SIMCONNECT_DATATYPE_WAYPOINT
class SIMCONNECT_DATA_WAYPOINT(Structure): #
- _fields_ = [
- ("Latitude", c_double), # degrees
- ("Longitude", c_double), # degrees
- ("Altitude", c_double), # feet
- ("Flags", c_ulong),
- ("ktsSpeed", c_double), # knots
- ("percentThrottle", c_double),
- ]
+ _fields_ = [
+ ("Latitude", c_double), # degrees
+ ("Longitude", c_double), # degrees
+ ("Altitude", c_double), # feet
+ ("Flags", c_ulong),
+ ("ktsSpeed", c_double), # knots
+ ("percentThrottle", c_double),
+ ]
# SIMCONNECT_DATA_LATLONALT
class SIMCONNECT_DATA_LATLONALT(Structure): #
- _fields_ = [("Latitude", c_double), ("Longitude", c_double), ("Altitude", c_double)]
+ _fields_ = [("Latitude", c_double), ("Longitude", c_double), ("Altitude", c_double)]
# SIMCONNECT_DATA_XYZ
class SIMCONNECT_DATA_XYZ(Structure): #
- _fields_ = [("x", c_double), ("y", c_double), ("z", c_double)]
-
+ _fields_ = [("x", c_double), ("y", c_double), ("z", c_double)]
diff --git a/action_plugins/map_to_simconnect/SimConnect/EventList.py b/action_plugins/map_to_simconnect/SimConnect/EventList.py
index b3569874..db468a02 100644
--- a/action_plugins/map_to_simconnect/SimConnect/EventList.py
+++ b/action_plugins/map_to_simconnect/SimConnect/EventList.py
@@ -1,578 +1,1515 @@
from .SimConnect import *
import logging
+
syslog = logging.getLogger("system")
-class Event(object):
- def __call__(self, value=0):
- if self.event is None:
- self.event = self.sm.map_to_sim_event(self.deff)
- if self.sm._dll:
- # syslog.info(f"SIMCONNECT: send {self.description} {value:0.3f}")
- self.sm.send_event(self.event, DWORD(int(value)))
+class Event(object):
+ def __call__(self, value=0):
+ if self.event is None:
+ self.event = self.sm.map_to_sim_event(self.deff)
+ if self.sm._dll:
+ # syslog.info(f"SIMCONNECT: send {self.description} {value:0.3f}")
+ self.sm.send_event(self.event, DWORD(int(value)))
- def __init__(self, definition, sm, description=''):
- self.deff = definition
- self.event = None
- self.description = description
- self.sm = sm
+ def __init__(self, definition, sm, description=""):
+ self.deff = definition
+ self.event = None
+ self.description = description
+ self.sm = sm
class EventHelper:
- def __init__(self, _sm):
- self.sm = _sm
-
- def __getattr__(self, _name):
- for key in self.list:
- if _name == key[0].decode():
- ne = Event(key[0], self.sm, description=key[1])
- setattr(self, _name, ne)
- return ne
- return None
+ def __init__(self, _sm):
+ self.sm = _sm
- def get(self, _name):
- return getattr(self, _name)
+ def __getattr__(self, _name):
+ for key in self.list:
+ if _name == key[0].decode():
+ ne = Event(key[0], self.sm, description=key[1])
+ setattr(self, _name, ne)
+ return ne
+ return None
- def set(self, _name, _value=0):
- setattr(self, _name, _value)
+ def get(self, _name):
+ return getattr(self, _name)
+ def set(self, _name, _value=0):
+ setattr(self, _name, _value)
-class AircraftEvents():
- def __init__(self, _sm):
- self.sm = _sm
- self.list = []
- self.Engine = self.__Engine(_sm)
- self.list.append(self.Engine)
- self.Flight_Controls = self.__Flight_Controls(_sm)
- self.list.append(self.Flight_Controls)
- self.Autopilot = self.__Autopilot(_sm)
- self.list.append(self.Autopilot)
- self.Fuel_System = self.__Fuel_System(_sm)
- self.list.append(self.Fuel_System)
- self.Fuel_Selection_Keys = self.__Fuel_Selection_Keys(_sm)
- self.list.append(self.Fuel_Selection_Keys)
- self.Avionics = self.__Avionics(_sm)
- self.list.append(self.Avionics)
- self.Instruments = self.__Instruments(_sm)
- self.list.append(self.Instruments)
- self.Lights = self.__Lights(_sm)
- self.list.append(self.Lights)
- self.Failures = self.__Failures(_sm)
- self.list.append(self.Failures)
- self.Miscellaneous_Systems = self.__Miscellaneous_Systems(_sm)
- self.list.append(self.Miscellaneous_Systems)
- self.Nose_wheel_steering = self.__Nose_wheel_steering(_sm)
- self.list.append(self.Nose_wheel_steering)
- self.Cabin_pressurization = self.__Cabin_pressurization(_sm)
- self.list.append(self.Cabin_pressurization)
- self.Catapult_Launches = self.__Catapult_Launches(_sm)
- self.list.append(self.Catapult_Launches)
- self.Helicopter_Specific_Systems = self.__Helicopter_Specific_Systems(_sm)
- self.list.append(self.Helicopter_Specific_Systems)
- self.Slings_and_Hoists = self.__Slings_and_Hoists(_sm)
- self.list.append(self.Slings_and_Hoists)
- self.Slew_System = self.__Slew_System(_sm)
- self.list.append(self.Slew_System)
- self.View_System = self.__View_System(_sm)
- self.list.append(self.View_System)
- self.Miscellaneous_Events = self.__Miscellaneous_Events(_sm)
- self.list.append(self.Miscellaneous_Events)
- self.Freezing_position = self.__Freezing_position(_sm)
- self.list.append(self.Freezing_position)
- self.Mission_Keys = self.__Mission_Keys(_sm)
- self.list.append(self.Mission_Keys)
- self.ATC = self.__ATC(_sm)
- self.list.append(self.ATC)
- self.Multiplayer = self.__Multiplayer(_sm)
- self.list.append(self.Multiplayer)
- def find(self, key):
- for clas in self.list:
- for test in clas.list:
- if key == test[0].decode():
- return getattr(clas, key)
- return None
-
+class AircraftEvents:
+ def __init__(self, _sm):
+ self.sm = _sm
+ self.list = []
+ self.Engine = self.__Engine(_sm)
+ self.list.append(self.Engine)
+ self.Flight_Controls = self.__Flight_Controls(_sm)
+ self.list.append(self.Flight_Controls)
+ self.Autopilot = self.__Autopilot(_sm)
+ self.list.append(self.Autopilot)
+ self.Fuel_System = self.__Fuel_System(_sm)
+ self.list.append(self.Fuel_System)
+ self.Fuel_Selection_Keys = self.__Fuel_Selection_Keys(_sm)
+ self.list.append(self.Fuel_Selection_Keys)
+ self.Avionics = self.__Avionics(_sm)
+ self.list.append(self.Avionics)
+ self.Instruments = self.__Instruments(_sm)
+ self.list.append(self.Instruments)
+ self.Lights = self.__Lights(_sm)
+ self.list.append(self.Lights)
+ self.Failures = self.__Failures(_sm)
+ self.list.append(self.Failures)
+ self.Miscellaneous_Systems = self.__Miscellaneous_Systems(_sm)
+ self.list.append(self.Miscellaneous_Systems)
+ self.Nose_wheel_steering = self.__Nose_wheel_steering(_sm)
+ self.list.append(self.Nose_wheel_steering)
+ self.Cabin_pressurization = self.__Cabin_pressurization(_sm)
+ self.list.append(self.Cabin_pressurization)
+ self.Catapult_Launches = self.__Catapult_Launches(_sm)
+ self.list.append(self.Catapult_Launches)
+ self.Helicopter_Specific_Systems = self.__Helicopter_Specific_Systems(_sm)
+ self.list.append(self.Helicopter_Specific_Systems)
+ self.Slings_and_Hoists = self.__Slings_and_Hoists(_sm)
+ self.list.append(self.Slings_and_Hoists)
+ self.Slew_System = self.__Slew_System(_sm)
+ self.list.append(self.Slew_System)
+ self.View_System = self.__View_System(_sm)
+ self.list.append(self.View_System)
+ self.Miscellaneous_Events = self.__Miscellaneous_Events(_sm)
+ self.list.append(self.Miscellaneous_Events)
+ self.Freezing_position = self.__Freezing_position(_sm)
+ self.list.append(self.Freezing_position)
+ self.Mission_Keys = self.__Mission_Keys(_sm)
+ self.list.append(self.Mission_Keys)
+ self.ATC = self.__ATC(_sm)
+ self.list.append(self.ATC)
+ self.Multiplayer = self.__Multiplayer(_sm)
+ self.list.append(self.Multiplayer)
+ def find(self, key):
+ for clas in self.list:
+ for test in clas.list:
+ if key == test[0].decode():
+ return getattr(clas, key)
+ return None
- class __Engine(EventHelper):
- list = [
- (b'THROTTLE_FULL', "Set throttles max", "Shared Cockpit"),
- (b'THROTTLE_INCR', "Increment throttles", "Shared Cockpit"),
- (b'THROTTLE_INCR_SMALL', "Increment throttles small", "Shared Cockpit"),
- (b'THROTTLE_DECR', "Decrement throttles", "Shared Cockpit"),
- (b'THROTTLE_DECR_SMALL', "Decrease throttles small", "Shared Cockpit"),
- (b'THROTTLE_CUT', "Set throttles to idle", "Shared Cockpit"),
- (b'INCREASE_THROTTLE', "Increment throttles", "Shared Cockpit"),
- (b'DECREASE_THROTTLE', "Decrement throttles", "Shared Cockpit"),
- (b'THROTTLE_SET', "Set throttles exactly (0- 16383),", "Shared Cockpit"),
- (b'AXIS_THROTTLE_SET', "Set throttles (0- 16383),", "Shared Cockpit (Pilot only, transmitted to Co-pilot if in a helicopter, not-transmitted otherwise)."),
- (b'THROTTLE1_SET', "Set throttle 1 exactly (0 to 16383),", "Shared Cockpit"),
- (b'THROTTLE2_SET', "Set throttle 2 exactly (0 to 16383),", "Shared Cockpit"),
- (b'THROTTLE3_SET', "Set throttle 3 exactly (0 to 16383),", "Shared Cockpit"),
- (b'THROTTLE4_SET', "Set throttle 4 exactly (0 to 16383),", "Shared Cockpit"),
- (b'THROTTLE1_FULL', "Set throttle 1 max", "Shared Cockpit"),
- (b'THROTTLE1_INCR', "Increment throttle 1", "Shared Cockpit"),
- (b'THROTTLE1_INCR_SMALL', "Increment throttle 1 small", "Shared Cockpit"),
- (b'THROTTLE1_DECR', "Decrement throttle 1", "Shared Cockpit"),
- (b'THROTTLE1_CUT', "Set throttle 1 to idle", "Shared Cockpit"),
- (b'THROTTLE2_FULL', "Set throttle 2 max", "Shared Cockpit"),
- (b'THROTTLE2_INCR', "Increment throttle 2", "Shared Cockpit"),
- (b'THROTTLE2_INCR_SMALL', "Increment throttle 2 small", "Shared Cockpit"),
- (b'THROTTLE2_DECR', "Decrement throttle 2", "Shared Cockpit"),
- (b'THROTTLE2_CUT', "Set throttle 2 to idle", "Shared Cockpit"),
- (b'THROTTLE3_FULL', "Set throttle 3 max", "Shared Cockpit"),
- (b'THROTTLE3_INCR', "Increment throttle 3", "Shared Cockpit"),
- (b'THROTTLE3_INCR_SMALL', "Increment throttle 3 small", "Shared Cockpit"),
- (b'THROTTLE3_DECR', "Decrement throttle 3", "Shared Cockpit"),
- (b'THROTTLE3_CUT', "Set throttle 3 to idle", "Shared Cockpit"),
- (b'THROTTLE4_FULL', "Set throttle 4 max", "Shared Cockpit"),
- (b'THROTTLE4_INCR', "Increment throttle 4", "Shared Cockpit"),
- (b'THROTTLE4_INCR_SMALL', "Increment throttle 4 small", "Shared Cockpit"),
- (b'THROTTLE4_DECR', "Decrement throttle 4", "Shared Cockpit"),
- (b'THROTTLE4_CUT', "Set throttle 4 to idle", "Shared Cockpit"),
- (b'THROTTLE_10', "Set throttles to 10%", "Shared Cockpit"),
- (b'THROTTLE_20', "Set throttles to 20%", "Shared Cockpit"),
- (b'THROTTLE_30', "Set throttles to 30%", "Shared Cockpit"),
- (b'THROTTLE_40', "Set throttles to 40%", "Shared Cockpit"),
- (b'THROTTLE_50', "Set throttles to 50%", "Shared Cockpit"),
- (b'THROTTLE_60', "Set throttles to 60%", "Shared Cockpit"),
- (b'THROTTLE_70', "Set throttles to 70%", "Shared Cockpit"),
- (b'THROTTLE_80', "Set throttles to 80%", "Shared Cockpit"),
- (b'THROTTLE_90', "Set throttles to 90%", "Shared Cockpit"),
- (b'AXIS_THROTTLE1_SET', "Set throttle 1 exactly (-16383 - +16383),", "Shared Cockpit"),
- (b'AXIS_THROTTLE2_SET', "Set throttle 2 exactly (-16383 - +16383),", "Shared Cockpit"),
- (b'AXIS_THROTTLE3_SET', "Set throttle 3 exactly (-16383 - +16383),", "Shared Cockpit"),
- (b'AXIS_THROTTLE4_SET', "Set throttle 4 exactly (-16383 - +16383),", "Shared Cockpit"),
- (b'THROTTLE_AXIS_SET_EX1', "Set throttles exactly (-16383 - +16383),", "Shared Cockpit"), # https://docs.flightsimulator.com/html/Programming_Tools/Event_IDs/Aircraft_Engine_Events.htm#THROTTLE1_AXIS_SET_EX1
- (b'THROTTLE1_AXIS_SET_EX1', "Set throttle 1 exactly (-16383 - +16383),", "Shared Cockpit"),
- (b'THROTTLE2_AXIS_SET_EX1', "Set throttle 2 exactly (-16383 - +16383),", "Shared Cockpit"),
- (b'THROTTLE3_AXIS_SET_EX1', "Set throttle 3 exactly (-16383 - +16383),", "Shared Cockpit"),
- (b'THROTTLE4_AXIS_SET_EX1', "Set throttle 4 exactly (-16383 - +16383),", "Shared Cockpit"),
- (b'THROTTLE1_DECR_SMALL', "Decrease throttle 1 small", "Shared Cockpit"),
- (b'THROTTLE2_DECR_SMALL', "Decrease throttle 2 small", "Shared Cockpit"),
- (b'THROTTLE3_DECR_SMALL', "Decrease throttle 3 small", "Shared Cockpit"),
- (b'THROTTLE4_DECR_SMALL', "Decrease throttle 4 small", "Shared Cockpit"),
- (b'THROTTLE1_CUT_EX1', "Cut throttle 1", "Shared Cockpit"),
- (b'THROTTLE2_CUT_EX1', "Cut throttle 2", "Shared Cockpit"),
- (b'THROTTLE3_CUT_EX1', "Cut throttle 3", "Shared Cockpit"),
- (b'THROTTLE4_CUT_EX1', "Cut throttle 4", "Shared Cockpit"),
- (b'THROTTLE1_DECREASE_EX1', "Decrease throttle 1", "Shared Cockpit"),
- (b'THROTTLE2_DECREASE_EX1', "Decrease throttle 2", "Shared Cockpit"),
- (b'THROTTLE3_DECREASE_EX1', "Decrease throttle 3", "Shared Cockpit"),
- (b'THROTTLE4_DECREASE_EX1', "Decrease throttle 4", "Shared Cockpit"),
- (b'THROTTLE_DECREASE_SMALL_EX1', "Decrease throttles small", "Shared Cockpit"),
- (b'THROTTLE1_DECREASE_SMALL_EX1', "Decrease throttle 1 small", "Shared Cockpit"),
- (b'THROTTLE2_DECREASE_SMALL_EX1', "Decrease throttle 2 small", "Shared Cockpit"),
- (b'THROTTLE3_DECREASE_SMALL_EX1', "Decrease throttle 3 small", "Shared Cockpit"),
- (b'THROTTLE4_DECREASE_SMALL_EX1', "Decrease throttle 4 small", "Shared Cockpit"),
- (b'THROTTLE_DECR_SMALL_EX1', "Decrease throttles small", "Shared Cockpit"),
- (b'THROTTLE1_DECR_SMALL_EX1', "Decrease throttle 1 small", "Shared Cockpit"),
- (b'THROTTLE2_DECR_SMALL_EX1', "Decrease throttle 2 small", "Shared Cockpit"),
- (b'THROTTLE3_DECR_SMALL_EX1', "Decrease throttle 3 small", "Shared Cockpit"),
- (b'THROTTLE4_DECR_SMALL_EX1', "Decrease throttle 4 small", "Shared Cockpit"),
- (b'THROTTLE_INCREASE_SMALL_EX1', "Increase throttles small", "Shared Cockpit"),
- (b'THROTTLE1_INCREASE_SMALL_EX1', "Increase throttle 1 small", "Shared Cockpit"),
- (b'THROTTLE2_INCREASE_SMALL_EX1', "Increase throttle 2 small", "Shared Cockpit"),
- (b'THROTTLE3_INCREASE_SMALL_EX1', "Increase throttle 3 small", "Shared Cockpit"),
- (b'THROTTLE4_INCREASE_SMALL_EX1', "Increase throttle 4 small", "Shared Cockpit"),
- (b'THROTTLE_FULL_EX1', "Set throttles max", "Shared Cockpit"),
- (b'THROTTLE1_FULL_EX1', "Set throttle 1 max", "Shared Cockpit"),
- (b'THROTTLE2_FULL_EX1', "Set throttle 2 max", "Shared Cockpit"),
- (b'THROTTLE3_FULL_EX1', "Set throttle 3 max", "Shared Cockpit"),
- (b'THROTTLE4_FULL_EX1', "Set throttle 4 max", "Shared Cockpit"),
- (b'THROTTLE1_REVERSE_THRUST_HOLD', "Set throttle 1 hold", "Shared Cockpit"),
- (b'THROTTLE2_REVERSE_THRUST_HOLD', "Set throttle 2 hold", "Shared Cockpit"),
- (b'THROTTLE3_REVERSE_THRUST_HOLD', "Set throttle 3 hold", "Shared Cockpit"),
- (b'THROTTLE4_REVERSE_THRUST_HOLD', "Set throttle 4 hold", "Shared Cockpit"),
- (b'TOGGLE_THROTTLE1_REVERSE_THRUST', "Toogle reverse thrust throttle 1", "Shared Cockpit"),
- (b'TOGGLE_THROTTLE2_REVERSE_THRUST', "Toogle reverse thrust throttle 1", "Shared Cockpit"),
- (b'TOGGLE_THROTTLE3_REVERSE_THRUST', "Toogle reverse thrust throttle 1", "Shared Cockpit"),
- (b'TOGGLE_THROTTLE4_REVERSE_THRUST', "Toogle reverse thrust throttle 1", "Shared Cockpit"),
- (b'SET_THROTTLE1_REVERSE_THRUST_OFF', " Turn off the throttle 1 reverse thrust for engine", "Shared Cockpit"),
- (b'SET_THROTTLE2_REVERSE_THRUST_OFF', " Turn off the throttle 1 reverse thrust for engine", "Shared Cockpit"),
- (b'SET_THROTTLE3_REVERSE_THRUST_OFF', " Turn off the throttle 1 reverse thrust for engine", "Shared Cockpit"),
- (b'SET_THROTTLE4_REVERSE_THRUST_OFF', " Turn off the throttle 1 reverse thrust for engine", "Shared Cockpit"),
- (b'SET_THROTTLE1_REVERSE_THRUST_ON', " Turn on the throttle 1 reverse thrust for engine", "Shared Cockpit"),
- (b'SET_THROTTLE2_REVERSE_THRUST_ON', " Turn on the throttle 2 reverse thrust for engine", "Shared Cockpit"),
- (b'SET_THROTTLE3_REVERSE_THRUST_ON', " Turn on the throttle 3 reverse thrust for engine", "Shared Cockpit"),
- (b'SET_THROTTLE4_REVERSE_THRUST_ON', " Turn on the throttle 4 reverse thrust for engine", "Shared Cockpit"),
- (b'PROP_PITCH_DECR_SMALL', "Decrease prop levers small", "Shared Cockpit"),
- (b'PROP_PITCH1_DECR_SMALL', "Decrease prop lever 1 small", "Shared Cockpit"),
- (b'PROP_PITCH2_DECR_SMALL', "Decrease prop lever 2 small", "Shared Cockpit"),
- (b'PROP_PITCH3_DECR_SMALL', "Decrease prop lever 3 small", "Shared Cockpit"),
- (b'PROP_PITCH4_DECR_SMALL', "Decrease prop lever 4 small", "Shared Cockpit"),
- (b'MIXTURE1_RICH', "Set mixture lever 1 to max rich", "Shared Cockpit"),
- (b'MIXTURE1_INCR', "Increment mixture lever 1", "Shared Cockpit"),
- (b'MIXTURE1_INCR_SMALL', "Increment mixture lever 1 small", "Shared Cockpit"),
- (b'MIXTURE1_DECR', "Decrement mixture lever 1", "Shared Cockpit"),
- (b'MIXTURE1_LEAN', "Set mixture lever 1 to max lean", "Shared Cockpit"),
- (b'MIXTURE2_RICH', "Set mixture lever 2 to max rich", "Shared Cockpit"),
- (b'MIXTURE2_INCR', "Increment mixture lever 2", "Shared Cockpit"),
- (b'MIXTURE2_INCR_SMALL', "Increment mixture lever 2 small", "Shared Cockpit"),
- (b'MIXTURE2_DECR', "Decrement mixture lever 2", "Shared Cockpit"),
- (b'MIXTURE2_LEAN', "Set mixture lever 2 to max lean", "Shared Cockpit"),
- (b'MIXTURE3_RICH', "Set mixture lever 3 to max rich", "Shared Cockpit"),
- (b'MIXTURE3_INCR', "Increment mixture lever 3", "Shared Cockpit"),
- (b'MIXTURE3_INCR_SMALL', "Increment mixture lever 3 small", "Shared Cockpit"),
- (b'MIXTURE3_DECR', "Decrement mixture lever 3", "Shared Cockpit"),
- (b'MIXTURE3_LEAN', "Set mixture lever 3 to max lean", "Shared Cockpit"),
- (b'MIXTURE4_RICH', "Set mixture lever 4 to max rich", "Shared Cockpit"),
- (b'MIXTURE4_INCR', "Increment mixture lever 4", "Shared Cockpit"),
- (b'MIXTURE4_INCR_SMALL', "Increment mixture lever 4 small", "Shared Cockpit"),
- (b'MIXTURE4_DECR', "Decrement mixture lever 4", "Shared Cockpit"),
- (b'MIXTURE4_LEAN', "Set mixture lever 4 to max lean", "Shared Cockpit"),
- (b'MIXTURE_SET', "Set mixture levers to exact value (0 to 16383),", "Shared Cockpit"),
- (b'MIXTURE_RICH', "Set mixture levers to max rich", "Shared Cockpit"),
- (b'MIXTURE_INCR', "Increment mixture levers", "Shared Cockpit"),
- (b'MIXTURE_INCR_SMALL', "Increment mixture levers small", "Shared Cockpit"),
- (b'MIXTURE_DECR', "Decrement mixture levers", "Shared Cockpit"),
- (b'MIXTURE_LEAN', "Set mixture levers to max lean", "Shared Cockpit"),
- (b'MIXTURE1_SET', "Set mixture lever 1 exact value (0 to 16383),", "Shared Cockpit"),
- (b'MIXTURE2_SET', "Set mixture lever 2 exact value (0 to 16383),", "Shared Cockpit"),
- (b'MIXTURE3_SET', "Set mixture lever 3 exact value (0 to 16383),", "Shared Cockpit"),
- (b'MIXTURE4_SET', "Set mixture lever 4 exact value (0 to 16383),", "Shared Cockpit"),
- (b'AXIS_MIXTURE_SET', "Set mixture levers exact value (-16383 to +16383),", "Shared Cockpit"),
- (b'AXIS_MIXTURE1_SET', "Set mixture lever 1 exact value (-16383 to +16383),", "Shared Cockpit"),
- (b'AXIS_MIXTURE2_SET', "Set mixture lever 2 exact value (-16383 to +16383),", "Shared Cockpit"),
- (b'AXIS_MIXTURE3_SET', "Set mixture lever 3 exact value (-16383 to +16383),", "Shared Cockpit"),
- (b'AXIS_MIXTURE4_SET', "Set mixture lever 4 exact value (-16383 to +16383),", "Shared Cockpit"),
- (b'MIXTURE_SET_BEST', "Set mixture levers to current best power setting", "Shared Cockpit"),
- (b'MIXTURE_DECR_SMALL', "Decrement mixture levers small", "Shared Cockpit"),
- (b'MIXTURE1_DECR_SMALL', "Decrement mixture lever 1 small", "Shared Cockpit"),
- (b'MIXTURE2_DECR_SMALL', "Decrement mixture lever 4 small", "Shared Cockpit"),
- (b'MIXTURE3_DECR_SMALL', "Decrement mixture lever 4 small", "Shared Cockpit"),
- (b'MIXTURE4_DECR_SMALL', "Decrement mixture lever 4 small", "Shared Cockpit"),
- (b'PROP_PITCH_SET', "Set prop pitch levers (0 to 16383),", "Shared Cockpit"),
- (b'PROP_PITCH_LO', "Set prop pitch levers max (lo pitch),", "Shared Cockpit"),
- (b'PROP_PITCH_INCR', "Increment prop pitch levers", "Shared Cockpit"),
- (b'PROP_PITCH_INCR_SMALL', "Increment prop pitch levers small", "Shared Cockpit"),
- (b'PROP_PITCH_DECR', "Decrement prop pitch levers", "Shared Cockpit"),
- (b'PROP_PITCH_HI', "Set prop pitch levers min (hi pitch),", "Shared Cockpit"),
- (b'PROP_PITCH1_SET', "Set prop pitch lever 1 exact value (0 to 16383)", "Shared Cockpit"),
- (b'PROP_PITCH2_SET', "Set prop pitch lever 2 exact value (0 to 16383)", "Shared Cockpit"),
- (b'PROP_PITCH3_SET', "Set prop pitch lever 3 exact value (0 to 16383)", "Shared Cockpit"),
- (b'PROP_PITCH4_SET', "Set prop pitch lever 4 exact value (0 to 16383)", "Shared Cockpit"),
- (b'PROP_PITCH1_LO_EX1', "Set prop pitch lever 1 max (lo pitch)", "Shared Cockpit"),
- (b'PROP_PITCH2_LO_EX1', "Set prop pitch lever 2 max (lo pitch)", "Shared Cockpit"),
- (b'PROP_PITCH3_LO_EX1', "Set prop pitch lever 3 max (lo pitch)", "Shared Cockpit"),
- (b'PROP_PITCH4_LO_EX1', "Set prop pitch lever 4 max (lo pitch)", "Shared Cockpit"),
- (b'PROP_PITCH1_HI_EX1', "Set prop pitch lever 1 min (hi pitch)", "Shared Cockpit"),
- (b'PROP_PITCH2_HI_EX1', "Set prop pitch lever 2 min (hi pitch)", "Shared Cockpit"),
- (b'PROP_PITCH3_HI_EX1', "Set prop pitch lever 3 min (hi pitch)", "Shared Cockpit"),
- (b'PROP_PITCH4_HI_EX1', "Set prop pitch lever 4 min (hi pitch)", "Shared Cockpit"),
- (b'PROP_PITCH1_DECREASE_EX1', "Decrease prop pitch lever 1", "Shared Cockpit"),
- (b'PROP_PITCH2_DECREASE_EX1', "Decrease prop pitch lever 2", "Shared Cockpit"),
- (b'PROP_PITCH3_DECREASE_EX1', "Decrease prop pitch lever 3", "Shared Cockpit"),
- (b'PROP_PITCH4_DECREASE_EX1', "Decrease prop pitch lever 4", "Shared Cockpit"),
- (b'PROP_PITCH1_INCREASE_EX1', "Increase prop pitch lever 1", "Shared Cockpit"),
- (b'PROP_PITCH2_INCREASE_EX1', "Increase prop pitch lever 1", "Shared Cockpit"),
- (b'PROP_PITCH3_INCREASE_EX1', "Increase prop pitch lever 1", "Shared Cockpit"),
- (b'PROP_PITCH4_INCREASE_EX1', "Increase prop pitch lever 1", "Shared Cockpit"),
- (b'PROP_PITCH1_DECREASE_SMALL_EX1', "Decrease prop pitch small lever 1", "Shared Cockpit"),
- (b'PROP_PITCH2_DECREASE_SMALL_EX1', "Decrease prop pitch small lever 2", "Shared Cockpit"),
- (b'PROP_PITCH3_DECREASE_SMALL_EX1', "Decrease prop pitch small lever 3", "Shared Cockpit"),
- (b'PROP_PITCH4_DECREASE_SMALL_EX1', "Decrease prop pitch small lever 4", "Shared Cockpit"),
- (b'PROP_PITCH1_INCREASE_SMALL_EX1', "Increase prop pitch small lever 1", "Shared Cockpit"),
- (b'PROP_PITCH2_INCREASE_SMALL_EX1', "Increase prop pitch small lever 2", "Shared Cockpit"),
- (b'PROP_PITCH3_INCREASE_SMALL_EX1', "Increase prop pitch small lever 3", "Shared Cockpit"),
- (b'PROP_PITCH4_INCREASE_SMALL_EX1', "Increase prop pitch small lever 4", "Shared Cockpit"),
- (b'PROP_PITCH1_LO', "Set prop pitch lever 1 max (lo pitch),", "Shared Cockpit"),
- (b'PROP_PITCH1_INCR', "Increment prop pitch lever 1", "Shared Cockpit"),
- (b'PROP_PITCH1_INCR_SMALL', "Increment prop pitch lever 1 small", "Shared Cockpit"),
- (b'PROP_PITCH1_DECR', "Decrement prop pitch lever 1", "Shared Cockpit"),
- (b'PROP_PITCH1_HI', "Set prop pitch lever 1 min (hi pitch),", "Shared Cockpit"),
- (b'PROP_PITCH2_LO', "Set prop pitch lever 2 max (lo pitch),", "Shared Cockpit"),
- (b'PROP_PITCH2_INCR', "Increment prop pitch lever 2", "Shared Cockpit"),
- (b'PROP_PITCH2_INCR_SMALL', "Increment prop pitch lever 2 small", "Shared Cockpit"),
- (b'PROP_PITCH2_DECR', "Decrement prop pitch lever 2", "Shared Cockpit"),
- (b'PROP_PITCH2_HI', "Set prop pitch lever 2 min (hi pitch),", "Shared Cockpit"),
- (b'PROP_PITCH3_LO', "Set prop pitch lever 3 max (lo pitch),", "Shared Cockpit"),
- (b'PROP_PITCH3_INCR', "Increment prop pitch lever 3", "Shared Cockpit"),
- (b'PROP_PITCH3_INCR_SMALL', "Increment prop pitch lever 3 small", "Shared Cockpit"),
- (b'PROP_PITCH3_DECR', "Decrement prop pitch lever 3", "Shared Cockpit"),
- (b'PROP_PITCH3_HI', "Set prop pitch lever 3 min (hi pitch),", "Shared Cockpit"),
- (b'PROP_PITCH4_LO', "Set prop pitch lever 4 max (lo pitch),", "Shared Cockpit"),
- (b'PROP_PITCH4_INCR', "Increment prop pitch lever 4", "Shared Cockpit"),
- (b'PROP_PITCH4_INCR_SMALL', "Increment prop pitch lever 4 small", "Shared Cockpit"),
- (b'PROP_PITCH4_DECR', "Decrement prop pitch lever 4", "Shared Cockpit"),
- (b'PROP_PITCH4_HI', "Set prop pitch lever 4 min (hi pitch),", "Shared Cockpit"),
- (b'AXIS_PROPELLER_SET', "Set propeller levers exact value (-16383 to +16383),", "Shared Cockpit"),
- (b'AXIS_PROPELLER1_SET', "Set propeller lever 1 exact value (-16383 to +16383),", "Shared Cockpit"),
- (b'AXIS_PROPELLER2_SET', "Set propeller lever 2 exact value (-16383 to +16383),", "Shared Cockpit"),
- (b'AXIS_PROPELLER3_SET', "Set propeller lever 3 exact value (-16383 to +16383),", "Shared Cockpit"),
- (b'AXIS_PROPELLER4_SET', "Set propeller lever 4 exact value (-16383 to +16383),", "Shared Cockpit"),
- (b'JET_STARTER', "Selects jet engine starter (for +/- sequence),", "Shared Cockpit"),
- (b'MAGNETO_SET', "Sets magnetos (0,1),", "Shared Cockpit"),
- (b'TOGGLE_STARTER1', "Toggle starter 1", "Shared Cockpit"),
- (b'TOGGLE_STARTER2', "Toggle starter 2", "Shared Cockpit"),
- (b'TOGGLE_STARTER3', "Toggle starter 3", "Shared Cockpit"),
- (b'TOGGLE_STARTER4', "Toggle starter 4", "Shared Cockpit"),
- (b'TOGGLE_ALL_STARTERS', "Toggle starters", "Shared Cockpit"),
- (b'ENGINE_AUTO_START', "Triggers auto-start", "Shared Cockpit"),
- (b'ENGINE_AUTO_SHUTDOWN', "Triggers auto-shutdown", "Shared Cockpit"),
- (b'MAGNETO', "Selects magnetos (for +/- sequence),", "Shared Cockpit"),
- (b'MAGNETO_DECR', "Decrease magneto switches positions", "Shared Cockpit"),
- (b'MAGNETO_INCR', "Increase magneto switches positions", "Shared Cockpit"),
- (b'MAGNETO1_OFF', "Set engine 1 magnetos off", "Shared Cockpit"),
- (b'MAGNETO1_RIGHT', "Toggle engine 1 right magneto", "All aircraft"),
- (b'MAGNETO1_LEFT', "Toggle engine 1 left magneto", "All aircraft"),
- (b'MAGNETO1_BOTH', "Set engine 1 magnetos on", "Shared Cockpit"),
- (b'MAGNETO1_START', "Set engine 1 magnetos on and toggle starter", "Shared Cockpit"),
- (b'MAGNETO2_OFF', "Set engine 2 magnetos off", "Shared Cockpit"),
- (b'MAGNETO2_RIGHT', "Toggle engine 2 right magneto", "All aircraft"),
- (b'MAGNETO2_LEFT', "Toggle engine 2 left magneto", "All aircraft"),
- (b'MAGNETO2_BOTH', "Set engine 2 magnetos on", "Shared Cockpit"),
- (b'MAGNETO2_START', "Set engine 2 magnetos on and toggle starter", "Shared Cockpit"),
- (b'MAGNETO3_OFF', "Set engine 3 magnetos off", "Shared Cockpit"),
- (b'MAGNETO3_RIGHT', "Toggle engine 3 right magneto", "All aircraft"),
- (b'MAGNETO3_LEFT', "Toggle engine 3 left magneto", "All aircraft"),
- (b'MAGNETO3_BOTH', "Set engine 3 magnetos on", "Shared Cockpit"),
- (b'MAGNETO3_START', "Set engine 3 magnetos on and toggle starter", "Shared Cockpit"),
- (b'MAGNETO4_OFF', "Set engine 4 magnetos off", "Shared Cockpit"),
- (b'MAGNETO4_RIGHT', "Toggle engine 4 right magneto", "All aircraft"),
- (b'MAGNETO4_LEFT', "Toggle engine 4 left magneto", "All aircraft"),
- (b'MAGNETO4_BOTH', "Set engine 4 magnetos on", "Shared Cockpit"),
- (b'MAGNETO4_START', "Set engine 4 magnetos on and toggle starter", "Shared Cockpit"),
- (b'MAGNETO_OFF', "Set engine magnetos off", "Shared Cockpit"),
- (b'MAGNETO_RIGHT', "Set engine right magnetos on", "Shared Cockpit"),
- (b'MAGNETO_LEFT', "Set engine left magnetos on", "Shared Cockpit"),
- (b'MAGNETO_BOTH', "Set engine magnetos on", "Shared Cockpit"),
- (b'MAGNETO_START', "Set engine magnetos on and toggle starters", "Shared Cockpit"),
- (b'MAGNETO1_DECR', "Decrease engine 1 magneto switch position", "Shared Cockpit"),
- (b'MAGNETO1_INCR', "Increase engine 1 magneto switch position", "Shared Cockpit"),
- (b'MAGNETO2_DECR', "Decrease engine 2 magneto switch position", "Shared Cockpit"),
- (b'MAGNETO2_INCR', "Increase engine 2 magneto switch position", "Shared Cockpit"),
- (b'MAGNETO3_DECR', "Decrease engine 3 magneto switch position", "Shared Cockpit"),
- (b'MAGNETO3_INCR', "Increase engine 3 magneto switch position", "Shared Cockpit"),
- (b'MAGNETO4_DECR', "Decrease engine 4 magneto switch position", "Shared Cockpit"),
- (b'MAGNETO4_INCR', "Increase engine 4 magneto switch position", "Shared Cockpit"),
- (b'Not supported', "Set engine magneto switches", "Shared Cockpit"),
- (b'MAGNETO1_SET', "Set engine 1 magneto switch", "Shared Cockpit"),
- (b'MAGNETO2_SET', "Set engine 2 magneto switch", "Shared Cockpit"),
- (b'MAGNETO3_SET', "Set engine 3 magneto switch", "Shared Cockpit"),
- (b'MAGNETO4_SET', "Set engine 4 magneto switch", "Shared Cockpit"),
- (b'ANTI_ICE_ON', "Sets anti-ice switches on", "Shared Cockpit"),
- (b'ANTI_ICE_OFF', "Sets anti-ice switches off", "Shared Cockpit"),
- (b'ANTI_ICE_SET', "Sets anti-ice switches from argument (0,1),", "Shared Cockpit"),
- (b'ANTI_ICE_TOGGLE', "Toggle anti-ice switches", "Shared Cockpit"),
- (b'ANTI_ICE_TOGGLE_ENG1', "Toggle engine 1 anti-ice switch", "Shared Cockpit"),
- (b'ANTI_ICE_TOGGLE_ENG2', "Toggle engine 2 anti-ice switch", "Shared Cockpit"),
- (b'ANTI_ICE_TOGGLE_ENG3', "Toggle engine 3 anti-ice switch", "Shared Cockpit"),
- (b'ANTI_ICE_TOGGLE_ENG4', "Toggle engine 4 anti-ice switch", "Shared Cockpit"),
- (b'ANTI_ICE_SET_ENG1', "Sets engine 1 anti-ice switch (0,1),", "Shared Cockpit"),
- (b'ANTI_ICE_SET_ENG2', "Sets engine 2 anti-ice switch (0,1),", "Shared Cockpit"),
- (b'ANTI_ICE_SET_ENG3', "Sets engine 3 anti-ice switch (0,1),", "Shared Cockpit"),
- (b'ANTI_ICE_SET_ENG4', "Sets engine 4 anti-ice switch (0,1),", "Shared Cockpit"),
- (b'TOGGLE_FUEL_VALVE_ALL', "Toggle engine fuel valves", "Shared Cockpit"),
- (b'TOGGLE_FUEL_VALVE_ENG1', "Toggle engine 1 fuel valve", "All aircraft"),
- (b'TOGGLE_FUEL_VALVE_ENG2', "Toggle engine 2 fuel valve", "All aircraft"),
- (b'TOGGLE_FUEL_VALVE_ENG3', "Toggle engine 3 fuel valve", "All aircraft"),
- (b'TOGGLE_FUEL_VALVE_ENG4', "Toggle engine 4 fuel valve", "All aircraft"),
- (b'COWLFLAP1_SET', "Sets engine 1 cowl flap lever position (0 to 16383),", "Shared Cockpit"),
- (b'COWLFLAP2_SET', "Sets engine 2 cowl flap lever position (0 to 16383),", "Shared Cockpit"),
- (b'COWLFLAP3_SET', "Sets engine 3 cowl flap lever position (0 to 16383),", "Shared Cockpit"),
- (b'COWLFLAP4_SET', "Sets engine 4 cowl flap lever position (0 to 16383),", "Shared Cockpit"),
- (b'INC_COWL_FLAPS', "Increment cowl flap levers", "Shared Cockpit"),
- (b'DEC_COWL_FLAPS', "Decrement cowl flap levers", "Shared Cockpit"),
- (b'INC_COWL_FLAPS1', "Increment engine 1 cowl flap lever", "Shared Cockpit"),
- (b'DEC_COWL_FLAPS1', "Decrement engine 1 cowl flap lever", "Shared Cockpit"),
- (b'INC_COWL_FLAPS2', "Increment engine 2 cowl flap lever", "Shared Cockpit"),
- (b'DEC_COWL_FLAPS2', "Decrement engine 2 cowl flap lever", "Shared Cockpit"),
- (b'INC_COWL_FLAPS3', "Increment engine 3 cowl flap lever", "Shared Cockpit"),
- (b'DEC_COWL_FLAPS3', "Decrement engine 3 cowl flap lever", "Shared Cockpit"),
- (b'INC_COWL_FLAPS4', "Increment engine 4 cowl flap lever", "Shared Cockpit"),
- (b'DEC_COWL_FLAPS4', "Decrement engine 4 cowl flap lever", "Shared Cockpit"),
- (b'FUEL_PUMP', "Toggle electric fuel pumps", "Shared Cockpit"),
- (b'TOGGLE_ELECT_FUEL_PUMP', "Toggle electric fuel pumps", "Shared Cockpit"),
- (b'TOGGLE_ELECT_FUEL_PUMP1', "Toggle engine 1 electric fuel pump", "All aircraft"),
- (b'TOGGLE_ELECT_FUEL_PUMP2', "Toggle engine 2 electric fuel pump", "All aircraft"),
- (b'TOGGLE_ELECT_FUEL_PUMP3', "Toggle engine 3 electric fuel pump", "All aircraft"),
- (b'TOGGLE_ELECT_FUEL_PUMP4', "Toggle engine 4 electric fuel pump", "All aircraft"),
- (b'ENGINE_PRIMER', "Trigger engine primers", "Shared Cockpit"),
- (b'TOGGLE_PRIMER', "Trigger engine primers", "Shared Cockpit"),
- (b'TOGGLE_PRIMER1', "Trigger engine 1 primer", "Shared Cockpit"),
- (b'TOGGLE_PRIMER2', "Trigger engine 2 primer", "Shared Cockpit"),
- (b'TOGGLE_PRIMER3', "Trigger engine 3 primer", "Shared Cockpit"),
- (b'TOGGLE_PRIMER4', "Trigger engine 4 primer", "Shared Cockpit"),
- (b'TOGGLE_FEATHER_SWITCHES', "Trigger propeller switches", "Shared Cockpit"),
- (b'TOGGLE_FEATHER_SWITCH_1', "Trigger propeller 1 switch", "Shared Cockpit"),
- (b'TOGGLE_FEATHER_SWITCH_2', "Trigger propeller 2 switch", "Shared Cockpit"),
- (b'TOGGLE_FEATHER_SWITCH_3', "Trigger propeller 3 switch", "Shared Cockpit"),
- (b'TOGGLE_FEATHER_SWITCH_4', "Trigger propeller 4 switch", "Shared Cockpit"),
- (b'TOGGLE_PROPELLER_SYNC', "Turns propeller synchronization switch on", "Shared Cockpit"),
- (b'TOGGLE_AUTOFEATHER_ARM', "Turns auto-feather arming switch on.", "Shared Cockpit"),
- (b'TOGGLE_AFTERBURNER', "Toggles afterburners", "Shared Cockpit"),
- (b'TOGGLE_AFTERBURNER1', "Toggles engine 1 afterburner", "Shared Cockpit"),
- (b'TOGGLE_AFTERBURNER2', "Toggles engine 2 afterburner", "Shared Cockpit"),
- (b'TOGGLE_AFTERBURNER3', "Toggles engine 3 afterburner", "Shared Cockpit"),
- (b'TOGGLE_AFTERBURNER4', "Toggles engine 4 afterburner", "Shared Cockpit"),
- (b'ENGINE', "Sets engines for 1,2,3,4 selection (to be followed by SELECT_n),", "Shared Cockpit"),
- ]
+ class __Engine(EventHelper):
+ list = [
+ (b"THROTTLE_FULL", "Set throttles max", "Shared Cockpit"),
+ (b"THROTTLE_INCR", "Increment throttles", "Shared Cockpit"),
+ (b"THROTTLE_INCR_SMALL", "Increment throttles small", "Shared Cockpit"),
+ (b"THROTTLE_DECR", "Decrement throttles", "Shared Cockpit"),
+ (b"THROTTLE_DECR_SMALL", "Decrease throttles small", "Shared Cockpit"),
+ (b"THROTTLE_CUT", "Set throttles to idle", "Shared Cockpit"),
+ (b"INCREASE_THROTTLE", "Increment throttles", "Shared Cockpit"),
+ (b"DECREASE_THROTTLE", "Decrement throttles", "Shared Cockpit"),
+ (b"THROTTLE_SET", "Set throttles exactly (0- 16383),", "Shared Cockpit"),
+ (
+ b"AXIS_THROTTLE_SET",
+ "Set throttles (0- 16383),",
+ "Shared Cockpit (Pilot only, transmitted to Co-pilot if in a helicopter, not-transmitted otherwise).",
+ ),
+ (
+ b"THROTTLE1_SET",
+ "Set throttle 1 exactly (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE2_SET",
+ "Set throttle 2 exactly (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE3_SET",
+ "Set throttle 3 exactly (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE4_SET",
+ "Set throttle 4 exactly (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (b"THROTTLE1_FULL", "Set throttle 1 max", "Shared Cockpit"),
+ (b"THROTTLE1_INCR", "Increment throttle 1", "Shared Cockpit"),
+ (b"THROTTLE1_INCR_SMALL", "Increment throttle 1 small", "Shared Cockpit"),
+ (b"THROTTLE1_DECR", "Decrement throttle 1", "Shared Cockpit"),
+ (b"THROTTLE1_CUT", "Set throttle 1 to idle", "Shared Cockpit"),
+ (b"THROTTLE2_FULL", "Set throttle 2 max", "Shared Cockpit"),
+ (b"THROTTLE2_INCR", "Increment throttle 2", "Shared Cockpit"),
+ (b"THROTTLE2_INCR_SMALL", "Increment throttle 2 small", "Shared Cockpit"),
+ (b"THROTTLE2_DECR", "Decrement throttle 2", "Shared Cockpit"),
+ (b"THROTTLE2_CUT", "Set throttle 2 to idle", "Shared Cockpit"),
+ (b"THROTTLE3_FULL", "Set throttle 3 max", "Shared Cockpit"),
+ (b"THROTTLE3_INCR", "Increment throttle 3", "Shared Cockpit"),
+ (b"THROTTLE3_INCR_SMALL", "Increment throttle 3 small", "Shared Cockpit"),
+ (b"THROTTLE3_DECR", "Decrement throttle 3", "Shared Cockpit"),
+ (b"THROTTLE3_CUT", "Set throttle 3 to idle", "Shared Cockpit"),
+ (b"THROTTLE4_FULL", "Set throttle 4 max", "Shared Cockpit"),
+ (b"THROTTLE4_INCR", "Increment throttle 4", "Shared Cockpit"),
+ (b"THROTTLE4_INCR_SMALL", "Increment throttle 4 small", "Shared Cockpit"),
+ (b"THROTTLE4_DECR", "Decrement throttle 4", "Shared Cockpit"),
+ (b"THROTTLE4_CUT", "Set throttle 4 to idle", "Shared Cockpit"),
+ (b"THROTTLE_10", "Set throttles to 10%", "Shared Cockpit"),
+ (b"THROTTLE_20", "Set throttles to 20%", "Shared Cockpit"),
+ (b"THROTTLE_30", "Set throttles to 30%", "Shared Cockpit"),
+ (b"THROTTLE_40", "Set throttles to 40%", "Shared Cockpit"),
+ (b"THROTTLE_50", "Set throttles to 50%", "Shared Cockpit"),
+ (b"THROTTLE_60", "Set throttles to 60%", "Shared Cockpit"),
+ (b"THROTTLE_70", "Set throttles to 70%", "Shared Cockpit"),
+ (b"THROTTLE_80", "Set throttles to 80%", "Shared Cockpit"),
+ (b"THROTTLE_90", "Set throttles to 90%", "Shared Cockpit"),
+ (
+ b"AXIS_THROTTLE1_SET",
+ "Set throttle 1 exactly (-16383 - +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_THROTTLE2_SET",
+ "Set throttle 2 exactly (-16383 - +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_THROTTLE3_SET",
+ "Set throttle 3 exactly (-16383 - +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_THROTTLE4_SET",
+ "Set throttle 4 exactly (-16383 - +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE_AXIS_SET_EX1",
+ "Set throttles exactly (-16383 - +16383),",
+ "Shared Cockpit",
+ ), # https://docs.flightsimulator.com/html/Programming_Tools/Event_IDs/Aircraft_Engine_Events.htm#THROTTLE1_AXIS_SET_EX1
+ (
+ b"THROTTLE1_AXIS_SET_EX1",
+ "Set throttle 1 exactly (-16383 - +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE2_AXIS_SET_EX1",
+ "Set throttle 2 exactly (-16383 - +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE3_AXIS_SET_EX1",
+ "Set throttle 3 exactly (-16383 - +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE4_AXIS_SET_EX1",
+ "Set throttle 4 exactly (-16383 - +16383),",
+ "Shared Cockpit",
+ ),
+ (b"THROTTLE1_DECR_SMALL", "Decrease throttle 1 small", "Shared Cockpit"),
+ (b"THROTTLE2_DECR_SMALL", "Decrease throttle 2 small", "Shared Cockpit"),
+ (b"THROTTLE3_DECR_SMALL", "Decrease throttle 3 small", "Shared Cockpit"),
+ (b"THROTTLE4_DECR_SMALL", "Decrease throttle 4 small", "Shared Cockpit"),
+ (b"THROTTLE1_CUT_EX1", "Cut throttle 1", "Shared Cockpit"),
+ (b"THROTTLE2_CUT_EX1", "Cut throttle 2", "Shared Cockpit"),
+ (b"THROTTLE3_CUT_EX1", "Cut throttle 3", "Shared Cockpit"),
+ (b"THROTTLE4_CUT_EX1", "Cut throttle 4", "Shared Cockpit"),
+ (b"THROTTLE1_DECREASE_EX1", "Decrease throttle 1", "Shared Cockpit"),
+ (b"THROTTLE2_DECREASE_EX1", "Decrease throttle 2", "Shared Cockpit"),
+ (b"THROTTLE3_DECREASE_EX1", "Decrease throttle 3", "Shared Cockpit"),
+ (b"THROTTLE4_DECREASE_EX1", "Decrease throttle 4", "Shared Cockpit"),
+ (
+ b"THROTTLE_DECREASE_SMALL_EX1",
+ "Decrease throttles small",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE1_DECREASE_SMALL_EX1",
+ "Decrease throttle 1 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE2_DECREASE_SMALL_EX1",
+ "Decrease throttle 2 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE3_DECREASE_SMALL_EX1",
+ "Decrease throttle 3 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE4_DECREASE_SMALL_EX1",
+ "Decrease throttle 4 small",
+ "Shared Cockpit",
+ ),
+ (b"THROTTLE_DECR_SMALL_EX1", "Decrease throttles small", "Shared Cockpit"),
+ (
+ b"THROTTLE1_DECR_SMALL_EX1",
+ "Decrease throttle 1 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE2_DECR_SMALL_EX1",
+ "Decrease throttle 2 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE3_DECR_SMALL_EX1",
+ "Decrease throttle 3 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE4_DECR_SMALL_EX1",
+ "Decrease throttle 4 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE_INCREASE_SMALL_EX1",
+ "Increase throttles small",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE1_INCREASE_SMALL_EX1",
+ "Increase throttle 1 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE2_INCREASE_SMALL_EX1",
+ "Increase throttle 2 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE3_INCREASE_SMALL_EX1",
+ "Increase throttle 3 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"THROTTLE4_INCREASE_SMALL_EX1",
+ "Increase throttle 4 small",
+ "Shared Cockpit",
+ ),
+ (b"THROTTLE_FULL_EX1", "Set throttles max", "Shared Cockpit"),
+ (b"THROTTLE1_FULL_EX1", "Set throttle 1 max", "Shared Cockpit"),
+ (b"THROTTLE2_FULL_EX1", "Set throttle 2 max", "Shared Cockpit"),
+ (b"THROTTLE3_FULL_EX1", "Set throttle 3 max", "Shared Cockpit"),
+ (b"THROTTLE4_FULL_EX1", "Set throttle 4 max", "Shared Cockpit"),
+ (b"THROTTLE1_REVERSE_THRUST_HOLD", "Set throttle 1 hold", "Shared Cockpit"),
+ (b"THROTTLE2_REVERSE_THRUST_HOLD", "Set throttle 2 hold", "Shared Cockpit"),
+ (b"THROTTLE3_REVERSE_THRUST_HOLD", "Set throttle 3 hold", "Shared Cockpit"),
+ (b"THROTTLE4_REVERSE_THRUST_HOLD", "Set throttle 4 hold", "Shared Cockpit"),
+ (
+ b"TOGGLE_THROTTLE1_REVERSE_THRUST",
+ "Toogle reverse thrust throttle 1",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_THROTTLE2_REVERSE_THRUST",
+ "Toogle reverse thrust throttle 1",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_THROTTLE3_REVERSE_THRUST",
+ "Toogle reverse thrust throttle 1",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_THROTTLE4_REVERSE_THRUST",
+ "Toogle reverse thrust throttle 1",
+ "Shared Cockpit",
+ ),
+ (
+ b"SET_THROTTLE1_REVERSE_THRUST_OFF",
+ " Turn off the throttle 1 reverse thrust for engine",
+ "Shared Cockpit",
+ ),
+ (
+ b"SET_THROTTLE2_REVERSE_THRUST_OFF",
+ " Turn off the throttle 1 reverse thrust for engine",
+ "Shared Cockpit",
+ ),
+ (
+ b"SET_THROTTLE3_REVERSE_THRUST_OFF",
+ " Turn off the throttle 1 reverse thrust for engine",
+ "Shared Cockpit",
+ ),
+ (
+ b"SET_THROTTLE4_REVERSE_THRUST_OFF",
+ " Turn off the throttle 1 reverse thrust for engine",
+ "Shared Cockpit",
+ ),
+ (
+ b"SET_THROTTLE1_REVERSE_THRUST_ON",
+ " Turn on the throttle 1 reverse thrust for engine",
+ "Shared Cockpit",
+ ),
+ (
+ b"SET_THROTTLE2_REVERSE_THRUST_ON",
+ " Turn on the throttle 2 reverse thrust for engine",
+ "Shared Cockpit",
+ ),
+ (
+ b"SET_THROTTLE3_REVERSE_THRUST_ON",
+ " Turn on the throttle 3 reverse thrust for engine",
+ "Shared Cockpit",
+ ),
+ (
+ b"SET_THROTTLE4_REVERSE_THRUST_ON",
+ " Turn on the throttle 4 reverse thrust for engine",
+ "Shared Cockpit",
+ ),
+ (b"PROP_PITCH_DECR_SMALL", "Decrease prop levers small", "Shared Cockpit"),
+ (
+ b"PROP_PITCH1_DECR_SMALL",
+ "Decrease prop lever 1 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH2_DECR_SMALL",
+ "Decrease prop lever 2 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH3_DECR_SMALL",
+ "Decrease prop lever 3 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH4_DECR_SMALL",
+ "Decrease prop lever 4 small",
+ "Shared Cockpit",
+ ),
+ (b"MIXTURE1_RICH", "Set mixture lever 1 to max rich", "Shared Cockpit"),
+ (b"MIXTURE1_INCR", "Increment mixture lever 1", "Shared Cockpit"),
+ (
+ b"MIXTURE1_INCR_SMALL",
+ "Increment mixture lever 1 small",
+ "Shared Cockpit",
+ ),
+ (b"MIXTURE1_DECR", "Decrement mixture lever 1", "Shared Cockpit"),
+ (b"MIXTURE1_LEAN", "Set mixture lever 1 to max lean", "Shared Cockpit"),
+ (b"MIXTURE2_RICH", "Set mixture lever 2 to max rich", "Shared Cockpit"),
+ (b"MIXTURE2_INCR", "Increment mixture lever 2", "Shared Cockpit"),
+ (
+ b"MIXTURE2_INCR_SMALL",
+ "Increment mixture lever 2 small",
+ "Shared Cockpit",
+ ),
+ (b"MIXTURE2_DECR", "Decrement mixture lever 2", "Shared Cockpit"),
+ (b"MIXTURE2_LEAN", "Set mixture lever 2 to max lean", "Shared Cockpit"),
+ (b"MIXTURE3_RICH", "Set mixture lever 3 to max rich", "Shared Cockpit"),
+ (b"MIXTURE3_INCR", "Increment mixture lever 3", "Shared Cockpit"),
+ (
+ b"MIXTURE3_INCR_SMALL",
+ "Increment mixture lever 3 small",
+ "Shared Cockpit",
+ ),
+ (b"MIXTURE3_DECR", "Decrement mixture lever 3", "Shared Cockpit"),
+ (b"MIXTURE3_LEAN", "Set mixture lever 3 to max lean", "Shared Cockpit"),
+ (b"MIXTURE4_RICH", "Set mixture lever 4 to max rich", "Shared Cockpit"),
+ (b"MIXTURE4_INCR", "Increment mixture lever 4", "Shared Cockpit"),
+ (
+ b"MIXTURE4_INCR_SMALL",
+ "Increment mixture lever 4 small",
+ "Shared Cockpit",
+ ),
+ (b"MIXTURE4_DECR", "Decrement mixture lever 4", "Shared Cockpit"),
+ (b"MIXTURE4_LEAN", "Set mixture lever 4 to max lean", "Shared Cockpit"),
+ (
+ b"MIXTURE_SET",
+ "Set mixture levers to exact value (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (b"MIXTURE_RICH", "Set mixture levers to max rich", "Shared Cockpit"),
+ (b"MIXTURE_INCR", "Increment mixture levers", "Shared Cockpit"),
+ (b"MIXTURE_INCR_SMALL", "Increment mixture levers small", "Shared Cockpit"),
+ (b"MIXTURE_DECR", "Decrement mixture levers", "Shared Cockpit"),
+ (b"MIXTURE_LEAN", "Set mixture levers to max lean", "Shared Cockpit"),
+ (
+ b"MIXTURE1_SET",
+ "Set mixture lever 1 exact value (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"MIXTURE2_SET",
+ "Set mixture lever 2 exact value (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"MIXTURE3_SET",
+ "Set mixture lever 3 exact value (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"MIXTURE4_SET",
+ "Set mixture lever 4 exact value (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_MIXTURE_SET",
+ "Set mixture levers exact value (-16383 to +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_MIXTURE1_SET",
+ "Set mixture lever 1 exact value (-16383 to +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_MIXTURE2_SET",
+ "Set mixture lever 2 exact value (-16383 to +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_MIXTURE3_SET",
+ "Set mixture lever 3 exact value (-16383 to +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_MIXTURE4_SET",
+ "Set mixture lever 4 exact value (-16383 to +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"MIXTURE_SET_BEST",
+ "Set mixture levers to current best power setting",
+ "Shared Cockpit",
+ ),
+ (b"MIXTURE_DECR_SMALL", "Decrement mixture levers small", "Shared Cockpit"),
+ (
+ b"MIXTURE1_DECR_SMALL",
+ "Decrement mixture lever 1 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"MIXTURE2_DECR_SMALL",
+ "Decrement mixture lever 4 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"MIXTURE3_DECR_SMALL",
+ "Decrement mixture lever 4 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"MIXTURE4_DECR_SMALL",
+ "Decrement mixture lever 4 small",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH_SET",
+ "Set prop pitch levers (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH_LO",
+ "Set prop pitch levers max (lo pitch),",
+ "Shared Cockpit",
+ ),
+ (b"PROP_PITCH_INCR", "Increment prop pitch levers", "Shared Cockpit"),
+ (
+ b"PROP_PITCH_INCR_SMALL",
+ "Increment prop pitch levers small",
+ "Shared Cockpit",
+ ),
+ (b"PROP_PITCH_DECR", "Decrement prop pitch levers", "Shared Cockpit"),
+ (
+ b"PROP_PITCH_HI",
+ "Set prop pitch levers min (hi pitch),",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH1_SET",
+ "Set prop pitch lever 1 exact value (0 to 16383)",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH2_SET",
+ "Set prop pitch lever 2 exact value (0 to 16383)",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH3_SET",
+ "Set prop pitch lever 3 exact value (0 to 16383)",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH4_SET",
+ "Set prop pitch lever 4 exact value (0 to 16383)",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH1_LO_EX1",
+ "Set prop pitch lever 1 max (lo pitch)",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH2_LO_EX1",
+ "Set prop pitch lever 2 max (lo pitch)",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH3_LO_EX1",
+ "Set prop pitch lever 3 max (lo pitch)",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH4_LO_EX1",
+ "Set prop pitch lever 4 max (lo pitch)",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH1_HI_EX1",
+ "Set prop pitch lever 1 min (hi pitch)",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH2_HI_EX1",
+ "Set prop pitch lever 2 min (hi pitch)",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH3_HI_EX1",
+ "Set prop pitch lever 3 min (hi pitch)",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH4_HI_EX1",
+ "Set prop pitch lever 4 min (hi pitch)",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH1_DECREASE_EX1",
+ "Decrease prop pitch lever 1",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH2_DECREASE_EX1",
+ "Decrease prop pitch lever 2",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH3_DECREASE_EX1",
+ "Decrease prop pitch lever 3",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH4_DECREASE_EX1",
+ "Decrease prop pitch lever 4",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH1_INCREASE_EX1",
+ "Increase prop pitch lever 1",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH2_INCREASE_EX1",
+ "Increase prop pitch lever 1",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH3_INCREASE_EX1",
+ "Increase prop pitch lever 1",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH4_INCREASE_EX1",
+ "Increase prop pitch lever 1",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH1_DECREASE_SMALL_EX1",
+ "Decrease prop pitch small lever 1",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH2_DECREASE_SMALL_EX1",
+ "Decrease prop pitch small lever 2",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH3_DECREASE_SMALL_EX1",
+ "Decrease prop pitch small lever 3",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH4_DECREASE_SMALL_EX1",
+ "Decrease prop pitch small lever 4",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH1_INCREASE_SMALL_EX1",
+ "Increase prop pitch small lever 1",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH2_INCREASE_SMALL_EX1",
+ "Increase prop pitch small lever 2",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH3_INCREASE_SMALL_EX1",
+ "Increase prop pitch small lever 3",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH4_INCREASE_SMALL_EX1",
+ "Increase prop pitch small lever 4",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH1_LO",
+ "Set prop pitch lever 1 max (lo pitch),",
+ "Shared Cockpit",
+ ),
+ (b"PROP_PITCH1_INCR", "Increment prop pitch lever 1", "Shared Cockpit"),
+ (
+ b"PROP_PITCH1_INCR_SMALL",
+ "Increment prop pitch lever 1 small",
+ "Shared Cockpit",
+ ),
+ (b"PROP_PITCH1_DECR", "Decrement prop pitch lever 1", "Shared Cockpit"),
+ (
+ b"PROP_PITCH1_HI",
+ "Set prop pitch lever 1 min (hi pitch),",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH2_LO",
+ "Set prop pitch lever 2 max (lo pitch),",
+ "Shared Cockpit",
+ ),
+ (b"PROP_PITCH2_INCR", "Increment prop pitch lever 2", "Shared Cockpit"),
+ (
+ b"PROP_PITCH2_INCR_SMALL",
+ "Increment prop pitch lever 2 small",
+ "Shared Cockpit",
+ ),
+ (b"PROP_PITCH2_DECR", "Decrement prop pitch lever 2", "Shared Cockpit"),
+ (
+ b"PROP_PITCH2_HI",
+ "Set prop pitch lever 2 min (hi pitch),",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH3_LO",
+ "Set prop pitch lever 3 max (lo pitch),",
+ "Shared Cockpit",
+ ),
+ (b"PROP_PITCH3_INCR", "Increment prop pitch lever 3", "Shared Cockpit"),
+ (
+ b"PROP_PITCH3_INCR_SMALL",
+ "Increment prop pitch lever 3 small",
+ "Shared Cockpit",
+ ),
+ (b"PROP_PITCH3_DECR", "Decrement prop pitch lever 3", "Shared Cockpit"),
+ (
+ b"PROP_PITCH3_HI",
+ "Set prop pitch lever 3 min (hi pitch),",
+ "Shared Cockpit",
+ ),
+ (
+ b"PROP_PITCH4_LO",
+ "Set prop pitch lever 4 max (lo pitch),",
+ "Shared Cockpit",
+ ),
+ (b"PROP_PITCH4_INCR", "Increment prop pitch lever 4", "Shared Cockpit"),
+ (
+ b"PROP_PITCH4_INCR_SMALL",
+ "Increment prop pitch lever 4 small",
+ "Shared Cockpit",
+ ),
+ (b"PROP_PITCH4_DECR", "Decrement prop pitch lever 4", "Shared Cockpit"),
+ (
+ b"PROP_PITCH4_HI",
+ "Set prop pitch lever 4 min (hi pitch),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_PROPELLER_SET",
+ "Set propeller levers exact value (-16383 to +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_PROPELLER1_SET",
+ "Set propeller lever 1 exact value (-16383 to +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_PROPELLER2_SET",
+ "Set propeller lever 2 exact value (-16383 to +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_PROPELLER3_SET",
+ "Set propeller lever 3 exact value (-16383 to +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_PROPELLER4_SET",
+ "Set propeller lever 4 exact value (-16383 to +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"JET_STARTER",
+ "Selects jet engine starter (for +/- sequence),",
+ "Shared Cockpit",
+ ),
+ (b"MAGNETO_SET", "Sets magnetos (0,1),", "Shared Cockpit"),
+ (b"TOGGLE_STARTER1", "Toggle starter 1", "Shared Cockpit"),
+ (b"TOGGLE_STARTER2", "Toggle starter 2", "Shared Cockpit"),
+ (b"TOGGLE_STARTER3", "Toggle starter 3", "Shared Cockpit"),
+ (b"TOGGLE_STARTER4", "Toggle starter 4", "Shared Cockpit"),
+ (b"TOGGLE_ALL_STARTERS", "Toggle starters", "Shared Cockpit"),
+ (b"ENGINE_AUTO_START", "Triggers auto-start", "Shared Cockpit"),
+ (b"ENGINE_AUTO_SHUTDOWN", "Triggers auto-shutdown", "Shared Cockpit"),
+ (b"MAGNETO", "Selects magnetos (for +/- sequence),", "Shared Cockpit"),
+ (b"MAGNETO_DECR", "Decrease magneto switches positions", "Shared Cockpit"),
+ (b"MAGNETO_INCR", "Increase magneto switches positions", "Shared Cockpit"),
+ (b"MAGNETO1_OFF", "Set engine 1 magnetos off", "Shared Cockpit"),
+ (b"MAGNETO1_RIGHT", "Toggle engine 1 right magneto", "All aircraft"),
+ (b"MAGNETO1_LEFT", "Toggle engine 1 left magneto", "All aircraft"),
+ (b"MAGNETO1_BOTH", "Set engine 1 magnetos on", "Shared Cockpit"),
+ (
+ b"MAGNETO1_START",
+ "Set engine 1 magnetos on and toggle starter",
+ "Shared Cockpit",
+ ),
+ (b"MAGNETO2_OFF", "Set engine 2 magnetos off", "Shared Cockpit"),
+ (b"MAGNETO2_RIGHT", "Toggle engine 2 right magneto", "All aircraft"),
+ (b"MAGNETO2_LEFT", "Toggle engine 2 left magneto", "All aircraft"),
+ (b"MAGNETO2_BOTH", "Set engine 2 magnetos on", "Shared Cockpit"),
+ (
+ b"MAGNETO2_START",
+ "Set engine 2 magnetos on and toggle starter",
+ "Shared Cockpit",
+ ),
+ (b"MAGNETO3_OFF", "Set engine 3 magnetos off", "Shared Cockpit"),
+ (b"MAGNETO3_RIGHT", "Toggle engine 3 right magneto", "All aircraft"),
+ (b"MAGNETO3_LEFT", "Toggle engine 3 left magneto", "All aircraft"),
+ (b"MAGNETO3_BOTH", "Set engine 3 magnetos on", "Shared Cockpit"),
+ (
+ b"MAGNETO3_START",
+ "Set engine 3 magnetos on and toggle starter",
+ "Shared Cockpit",
+ ),
+ (b"MAGNETO4_OFF", "Set engine 4 magnetos off", "Shared Cockpit"),
+ (b"MAGNETO4_RIGHT", "Toggle engine 4 right magneto", "All aircraft"),
+ (b"MAGNETO4_LEFT", "Toggle engine 4 left magneto", "All aircraft"),
+ (b"MAGNETO4_BOTH", "Set engine 4 magnetos on", "Shared Cockpit"),
+ (
+ b"MAGNETO4_START",
+ "Set engine 4 magnetos on and toggle starter",
+ "Shared Cockpit",
+ ),
+ (b"MAGNETO_OFF", "Set engine magnetos off", "Shared Cockpit"),
+ (b"MAGNETO_RIGHT", "Set engine right magnetos on", "Shared Cockpit"),
+ (b"MAGNETO_LEFT", "Set engine left magnetos on", "Shared Cockpit"),
+ (b"MAGNETO_BOTH", "Set engine magnetos on", "Shared Cockpit"),
+ (
+ b"MAGNETO_START",
+ "Set engine magnetos on and toggle starters",
+ "Shared Cockpit",
+ ),
+ (
+ b"MAGNETO1_DECR",
+ "Decrease engine 1 magneto switch position",
+ "Shared Cockpit",
+ ),
+ (
+ b"MAGNETO1_INCR",
+ "Increase engine 1 magneto switch position",
+ "Shared Cockpit",
+ ),
+ (
+ b"MAGNETO2_DECR",
+ "Decrease engine 2 magneto switch position",
+ "Shared Cockpit",
+ ),
+ (
+ b"MAGNETO2_INCR",
+ "Increase engine 2 magneto switch position",
+ "Shared Cockpit",
+ ),
+ (
+ b"MAGNETO3_DECR",
+ "Decrease engine 3 magneto switch position",
+ "Shared Cockpit",
+ ),
+ (
+ b"MAGNETO3_INCR",
+ "Increase engine 3 magneto switch position",
+ "Shared Cockpit",
+ ),
+ (
+ b"MAGNETO4_DECR",
+ "Decrease engine 4 magneto switch position",
+ "Shared Cockpit",
+ ),
+ (
+ b"MAGNETO4_INCR",
+ "Increase engine 4 magneto switch position",
+ "Shared Cockpit",
+ ),
+ (b"Not supported", "Set engine magneto switches", "Shared Cockpit"),
+ (b"MAGNETO1_SET", "Set engine 1 magneto switch", "Shared Cockpit"),
+ (b"MAGNETO2_SET", "Set engine 2 magneto switch", "Shared Cockpit"),
+ (b"MAGNETO3_SET", "Set engine 3 magneto switch", "Shared Cockpit"),
+ (b"MAGNETO4_SET", "Set engine 4 magneto switch", "Shared Cockpit"),
+ (b"ANTI_ICE_ON", "Sets anti-ice switches on", "Shared Cockpit"),
+ (b"ANTI_ICE_OFF", "Sets anti-ice switches off", "Shared Cockpit"),
+ (
+ b"ANTI_ICE_SET",
+ "Sets anti-ice switches from argument (0,1),",
+ "Shared Cockpit",
+ ),
+ (b"ANTI_ICE_TOGGLE", "Toggle anti-ice switches", "Shared Cockpit"),
+ (
+ b"ANTI_ICE_TOGGLE_ENG1",
+ "Toggle engine 1 anti-ice switch",
+ "Shared Cockpit",
+ ),
+ (
+ b"ANTI_ICE_TOGGLE_ENG2",
+ "Toggle engine 2 anti-ice switch",
+ "Shared Cockpit",
+ ),
+ (
+ b"ANTI_ICE_TOGGLE_ENG3",
+ "Toggle engine 3 anti-ice switch",
+ "Shared Cockpit",
+ ),
+ (
+ b"ANTI_ICE_TOGGLE_ENG4",
+ "Toggle engine 4 anti-ice switch",
+ "Shared Cockpit",
+ ),
+ (
+ b"ANTI_ICE_SET_ENG1",
+ "Sets engine 1 anti-ice switch (0,1),",
+ "Shared Cockpit",
+ ),
+ (
+ b"ANTI_ICE_SET_ENG2",
+ "Sets engine 2 anti-ice switch (0,1),",
+ "Shared Cockpit",
+ ),
+ (
+ b"ANTI_ICE_SET_ENG3",
+ "Sets engine 3 anti-ice switch (0,1),",
+ "Shared Cockpit",
+ ),
+ (
+ b"ANTI_ICE_SET_ENG4",
+ "Sets engine 4 anti-ice switch (0,1),",
+ "Shared Cockpit",
+ ),
+ (b"TOGGLE_FUEL_VALVE_ALL", "Toggle engine fuel valves", "Shared Cockpit"),
+ (b"TOGGLE_FUEL_VALVE_ENG1", "Toggle engine 1 fuel valve", "All aircraft"),
+ (b"TOGGLE_FUEL_VALVE_ENG2", "Toggle engine 2 fuel valve", "All aircraft"),
+ (b"TOGGLE_FUEL_VALVE_ENG3", "Toggle engine 3 fuel valve", "All aircraft"),
+ (b"TOGGLE_FUEL_VALVE_ENG4", "Toggle engine 4 fuel valve", "All aircraft"),
+ (
+ b"COWLFLAP1_SET",
+ "Sets engine 1 cowl flap lever position (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"COWLFLAP2_SET",
+ "Sets engine 2 cowl flap lever position (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"COWLFLAP3_SET",
+ "Sets engine 3 cowl flap lever position (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"COWLFLAP4_SET",
+ "Sets engine 4 cowl flap lever position (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (b"INC_COWL_FLAPS", "Increment cowl flap levers", "Shared Cockpit"),
+ (b"DEC_COWL_FLAPS", "Decrement cowl flap levers", "Shared Cockpit"),
+ (
+ b"INC_COWL_FLAPS1",
+ "Increment engine 1 cowl flap lever",
+ "Shared Cockpit",
+ ),
+ (
+ b"DEC_COWL_FLAPS1",
+ "Decrement engine 1 cowl flap lever",
+ "Shared Cockpit",
+ ),
+ (
+ b"INC_COWL_FLAPS2",
+ "Increment engine 2 cowl flap lever",
+ "Shared Cockpit",
+ ),
+ (
+ b"DEC_COWL_FLAPS2",
+ "Decrement engine 2 cowl flap lever",
+ "Shared Cockpit",
+ ),
+ (
+ b"INC_COWL_FLAPS3",
+ "Increment engine 3 cowl flap lever",
+ "Shared Cockpit",
+ ),
+ (
+ b"DEC_COWL_FLAPS3",
+ "Decrement engine 3 cowl flap lever",
+ "Shared Cockpit",
+ ),
+ (
+ b"INC_COWL_FLAPS4",
+ "Increment engine 4 cowl flap lever",
+ "Shared Cockpit",
+ ),
+ (
+ b"DEC_COWL_FLAPS4",
+ "Decrement engine 4 cowl flap lever",
+ "Shared Cockpit",
+ ),
+ (b"FUEL_PUMP", "Toggle electric fuel pumps", "Shared Cockpit"),
+ (b"TOGGLE_ELECT_FUEL_PUMP", "Toggle electric fuel pumps", "Shared Cockpit"),
+ (
+ b"TOGGLE_ELECT_FUEL_PUMP1",
+ "Toggle engine 1 electric fuel pump",
+ "All aircraft",
+ ),
+ (
+ b"TOGGLE_ELECT_FUEL_PUMP2",
+ "Toggle engine 2 electric fuel pump",
+ "All aircraft",
+ ),
+ (
+ b"TOGGLE_ELECT_FUEL_PUMP3",
+ "Toggle engine 3 electric fuel pump",
+ "All aircraft",
+ ),
+ (
+ b"TOGGLE_ELECT_FUEL_PUMP4",
+ "Toggle engine 4 electric fuel pump",
+ "All aircraft",
+ ),
+ (b"ENGINE_PRIMER", "Trigger engine primers", "Shared Cockpit"),
+ (b"TOGGLE_PRIMER", "Trigger engine primers", "Shared Cockpit"),
+ (b"TOGGLE_PRIMER1", "Trigger engine 1 primer", "Shared Cockpit"),
+ (b"TOGGLE_PRIMER2", "Trigger engine 2 primer", "Shared Cockpit"),
+ (b"TOGGLE_PRIMER3", "Trigger engine 3 primer", "Shared Cockpit"),
+ (b"TOGGLE_PRIMER4", "Trigger engine 4 primer", "Shared Cockpit"),
+ (
+ b"TOGGLE_FEATHER_SWITCHES",
+ "Trigger propeller switches",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_FEATHER_SWITCH_1",
+ "Trigger propeller 1 switch",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_FEATHER_SWITCH_2",
+ "Trigger propeller 2 switch",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_FEATHER_SWITCH_3",
+ "Trigger propeller 3 switch",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_FEATHER_SWITCH_4",
+ "Trigger propeller 4 switch",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_PROPELLER_SYNC",
+ "Turns propeller synchronization switch on",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_AUTOFEATHER_ARM",
+ "Turns auto-feather arming switch on.",
+ "Shared Cockpit",
+ ),
+ (b"TOGGLE_AFTERBURNER", "Toggles afterburners", "Shared Cockpit"),
+ (b"TOGGLE_AFTERBURNER1", "Toggles engine 1 afterburner", "Shared Cockpit"),
+ (b"TOGGLE_AFTERBURNER2", "Toggles engine 2 afterburner", "Shared Cockpit"),
+ (b"TOGGLE_AFTERBURNER3", "Toggles engine 3 afterburner", "Shared Cockpit"),
+ (b"TOGGLE_AFTERBURNER4", "Toggles engine 4 afterburner", "Shared Cockpit"),
+ (
+ b"ENGINE",
+ "Sets engines for 1,2,3,4 selection (to be followed by SELECT_n),",
+ "Shared Cockpit",
+ ),
+ ]
- class __Flight_Controls(EventHelper):
- list = [
- (b'SPOILERS_TOGGLE', "Toggles spoiler handle ", "All aircraft"),
- (b'FLAPS_UP', "Sets flap handle to full retract position", "All aircraft"),
- (b'FLAPS_1', "Sets flap handle to first extension position", "All aircraft"),
- (b'FLAPS_2', "Sets flap handle to second extension position", "All aircraft"),
- (b'FLAPS_3', "Sets flap handle to third extension position", "All aircraft"),
- (b'FLAPS_DOWN', "Sets flap handle to full extension position", "All aircraft"),
- (b'ELEV_TRIM_DN', "Increments elevator trim down", "Shared Cockpit"),
- (b'ELEV_DOWN', "Increments elevator down", "Shared Cockpit (Pilot only),."),
- (b'AILERONS_LEFT', "Increments ailerons left", "Shared Cockpit (Pilot only),."),
- (b'CENTER_AILER_RUDDER', "Centers aileron and rudder positions", "Shared Cockpit"),
- (b'AILERONS_RIGHT', "Increments ailerons right", "Shared Cockpit (Pilot only),."),
- (b'ELEV_TRIM_UP', "Increment elevator trim up", "Shared Cockpit"),
- (b'ELEV_UP', "Increments elevator up", "Shared Cockpit (Pilot only),."),
- (b'Unsupported', "Increments elevator down", "Shared Cockpit"),
- (b'Unsupported', "Increments elevator up", "Shared Cockpit"),
- (b'Unsupported', "Increments ailerons left", "Shared Cockpit"),
- (b'Unsupported', "Centers aileron position", "Shared Cockpit"),
- (b'Unsupported', "Increments ailerons right", "Shared Cockpit"),
- (b'RUDDER_LEFT', "Increments rudder left", "Shared Cockpit"),
- (b'RUDDER_CENTER', "Centers rudder position", "Shared Cockpit"),
- (b'RUDDER_RIGHT', "Increments rudder right", "Shared Cockpit"),
- (b'ELEVATOR_SET', "Sets elevator position (-16383 - +16383),", "Shared Cockpit"),
- (b'AILERON_SET', "Sets aileron position (-16383 - +16383),", "Shared Cockpit"),
- (b'RUDDER_SET', "Sets rudder position (-16383 - +16383),", "Shared Cockpit"),
- (b'FLAPS_INCR', "Increments flap handle position", "All aircraft"),
- (b'FLAPS_DECR', "Decrements flap handle position", "All aircraft"),
- (b'AXIS_ELEVATOR_SET', "Sets elevator position (-16383 - +16383),", "Shared Cockpit (Pilot only, and not transmitted to Co-pilot)"),
- (b'AXIS_AILERONS_SET', "Sets aileron position (-16383 - +16383),", "Shared Cockpit (Pilot only, and not transmitted to Co-pilot)"),
- (b'AXIS_RUDDER_SET', "Sets rudder position (-16383 - +16383),", "Shared Cockpit (Pilot only, and not transmitted to Co-pilot)"),
- (b'AXIS_ELEV_TRIM_SET', "Sets elevator trim position (-16383 - +16383),", "Shared Cockpit"),
- (b'SPOILERS_SET', "Sets spoiler handle position (0 to 16383),", "All aircraft"),
- (b'SPOILERS_ARM_TOGGLE', "Toggles arming of auto-spoilers", "All aircraft"),
- (b'SPOILERS_ON', "Sets spoiler handle to full extend position", "All aircraft"),
- (b'SPOILERS_OFF', "Sets spoiler handle to full retract position", "All aircraft"),
- (b'SPOILERS_ARM_ON', "Sets auto-spoiler arming on", "All aircraft"),
- (b'SPOILERS_ARM_OFF', "Sets auto-spoiler arming off", "All aircraft"),
- (b'SPOILERS_ARM_SET', "Sets auto-spoiler arming (0,1),", "All aircraft"),
- (b'AILERON_TRIM_LEFT', "Increments aileron trim left", "Shared Cockpit"),
- (b'AILERON_TRIM_RIGHT', "Increments aileron trim right", "Shared Cockpit"),
- (b'RUDDER_TRIM_LEFT', "Increments rudder trim left", "Shared Cockpit"),
- (b'RUDDER_TRIM_RIGHT', "Increments aileron trim right", "Shared Cockpit"),
- (b'AXIS_SPOILER_SET', "Sets spoiler handle position (-16383 - +16383),", "All aircraft"),
- (b'FLAPS_SET', "Sets flap handle to closest increment (0 to 16383),", "All aircraft"),
- (b'ELEVATOR_TRIM_SET', "Sets elevator trim position (0 to 16383),", "Shared Cockpit"),
- (b'AXIS_FLAPS_SET', "Sets flap handle to closest increment (-16383 - +16383),", "Shared Cockpit"),
- ]
+ class __Flight_Controls(EventHelper):
+ list = [
+ (b"SPOILERS_TOGGLE", "Toggles spoiler handle ", "All aircraft"),
+ (b"FLAPS_UP", "Sets flap handle to full retract position", "All aircraft"),
+ (
+ b"FLAPS_1",
+ "Sets flap handle to first extension position",
+ "All aircraft",
+ ),
+ (
+ b"FLAPS_2",
+ "Sets flap handle to second extension position",
+ "All aircraft",
+ ),
+ (
+ b"FLAPS_3",
+ "Sets flap handle to third extension position",
+ "All aircraft",
+ ),
+ (
+ b"FLAPS_DOWN",
+ "Sets flap handle to full extension position",
+ "All aircraft",
+ ),
+ (b"ELEV_TRIM_DN", "Increments elevator trim down", "Shared Cockpit"),
+ (b"ELEV_DOWN", "Increments elevator down", "Shared Cockpit (Pilot only),."),
+ (
+ b"AILERONS_LEFT",
+ "Increments ailerons left",
+ "Shared Cockpit (Pilot only),.",
+ ),
+ (
+ b"CENTER_AILER_RUDDER",
+ "Centers aileron and rudder positions",
+ "Shared Cockpit",
+ ),
+ (
+ b"AILERONS_RIGHT",
+ "Increments ailerons right",
+ "Shared Cockpit (Pilot only),.",
+ ),
+ (b"ELEV_TRIM_UP", "Increment elevator trim up", "Shared Cockpit"),
+ (b"ELEV_UP", "Increments elevator up", "Shared Cockpit (Pilot only),."),
+ (b"Unsupported", "Increments elevator down", "Shared Cockpit"),
+ (b"Unsupported", "Increments elevator up", "Shared Cockpit"),
+ (b"Unsupported", "Increments ailerons left", "Shared Cockpit"),
+ (b"Unsupported", "Centers aileron position", "Shared Cockpit"),
+ (b"Unsupported", "Increments ailerons right", "Shared Cockpit"),
+ (b"RUDDER_LEFT", "Increments rudder left", "Shared Cockpit"),
+ (b"RUDDER_CENTER", "Centers rudder position", "Shared Cockpit"),
+ (b"RUDDER_RIGHT", "Increments rudder right", "Shared Cockpit"),
+ (
+ b"ELEVATOR_SET",
+ "Sets elevator position (-16383 - +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AILERON_SET",
+ "Sets aileron position (-16383 - +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"RUDDER_SET",
+ "Sets rudder position (-16383 - +16383),",
+ "Shared Cockpit",
+ ),
+ (b"FLAPS_INCR", "Increments flap handle position", "All aircraft"),
+ (b"FLAPS_DECR", "Decrements flap handle position", "All aircraft"),
+ (
+ b"AXIS_ELEVATOR_SET",
+ "Sets elevator position (-16383 - +16383),",
+ "Shared Cockpit (Pilot only, and not transmitted to Co-pilot)",
+ ),
+ (
+ b"AXIS_AILERONS_SET",
+ "Sets aileron position (-16383 - +16383),",
+ "Shared Cockpit (Pilot only, and not transmitted to Co-pilot)",
+ ),
+ (
+ b"AXIS_RUDDER_SET",
+ "Sets rudder position (-16383 - +16383),",
+ "Shared Cockpit (Pilot only, and not transmitted to Co-pilot)",
+ ),
+ (
+ b"AXIS_ELEV_TRIM_SET",
+ "Sets elevator trim position (-16383 - +16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"SPOILERS_SET",
+ "Sets spoiler handle position (0 to 16383),",
+ "All aircraft",
+ ),
+ (b"SPOILERS_ARM_TOGGLE", "Toggles arming of auto-spoilers", "All aircraft"),
+ (
+ b"SPOILERS_ON",
+ "Sets spoiler handle to full extend position",
+ "All aircraft",
+ ),
+ (
+ b"SPOILERS_OFF",
+ "Sets spoiler handle to full retract position",
+ "All aircraft",
+ ),
+ (b"SPOILERS_ARM_ON", "Sets auto-spoiler arming on", "All aircraft"),
+ (b"SPOILERS_ARM_OFF", "Sets auto-spoiler arming off", "All aircraft"),
+ (b"SPOILERS_ARM_SET", "Sets auto-spoiler arming (0,1),", "All aircraft"),
+ (b"AILERON_TRIM_LEFT", "Increments aileron trim left", "Shared Cockpit"),
+ (b"AILERON_TRIM_RIGHT", "Increments aileron trim right", "Shared Cockpit"),
+ (b"RUDDER_TRIM_LEFT", "Increments rudder trim left", "Shared Cockpit"),
+ (b"RUDDER_TRIM_RIGHT", "Increments aileron trim right", "Shared Cockpit"),
+ (
+ b"AXIS_SPOILER_SET",
+ "Sets spoiler handle position (-16383 - +16383),",
+ "All aircraft",
+ ),
+ (
+ b"FLAPS_SET",
+ "Sets flap handle to closest increment (0 to 16383),",
+ "All aircraft",
+ ),
+ (
+ b"ELEVATOR_TRIM_SET",
+ "Sets elevator trim position (0 to 16383),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_FLAPS_SET",
+ "Sets flap handle to closest increment (-16383 - +16383),",
+ "Shared Cockpit",
+ ),
+ ]
- class __Autopilot(EventHelper):
- list = [
- (b'AP_MASTER', "Toggles AP on/off", "Shared Cockpit"),
- (b'AUTOPILOT_OFF', "Turns AP off", "Shared Cockpit"),
- (b'AUTOPILOT_ON', "Turns AP on", "Shared Cockpit"),
- (b'YAW_DAMPER_TOGGLE', "Toggles yaw damper on/off", "Shared Cockpit"),
- (b'AP_PANEL_HEADING_HOLD', "Toggles heading hold mode on/off", "Shared Cockpit"),
- (b'AP_PANEL_ALTITUDE_HOLD', "Toggles altitude hold mode on/off", "Shared Cockpit"),
- (b'AP_ATT_HOLD_ON', "Turns on AP wing leveler and pitch hold mode", "Shared Cockpit"),
- (b'AP_LOC_HOLD_ON', "Turns AP localizer hold on/armed and glide-slope hold mode off", "Shared Cockpit"),
- (b'AP_APR_HOLD_ON', "Turns both AP localizer and glide-slope modes on/armed", "Shared Cockpit"),
- (b'AP_HDG_HOLD_ON', "Turns heading hold mode on", "Shared Cockpit"),
- (b'AP_ALT_HOLD_ON', "Turns altitude hold mode on", "Shared Cockpit"),
- (b'AP_WING_LEVELER_ON', "Turns wing leveler mode on", "Shared Cockpit"),
- (b'AP_BC_HOLD_ON', "Turns localizer back course hold mode on/armed", "Shared Cockpit"),
- (b'AP_NAV1_HOLD_ON', "Turns lateral hold mode on", "Shared Cockpit"),
- (b'AP_ATT_HOLD_OFF', "Turns off attitude hold mode", "Shared Cockpit"),
- (b'AP_LOC_HOLD_OFF', "Turns off localizer hold mode", "Shared Cockpit"),
- (b'AP_APR_HOLD_OFF', "Turns off approach hold mode", "Shared Cockpit"),
- (b'AP_HDG_HOLD_OFF', "Turns off heading hold mode", "Shared Cockpit"),
- (b'AP_ALT_HOLD_OFF', "Turns off altitude hold mode", "Shared Cockpit"),
- (b'AP_WING_LEVELER_OFF', "Turns off wing leveler mode", "Shared Cockpit"),
- (b'AP_BC_HOLD_OFF', "Turns off backcourse mode for localizer hold", "Shared Cockpit"),
- (b'AP_NAV1_HOLD_OFF', "Turns off nav hold mode", "Shared Cockpit"),
- (b'AP_AIRSPEED_HOLD', "Toggles airspeed hold mode", "Shared Cockpit"),
- (b'AUTO_THROTTLE_ARM', "Toggles autothrottle arming mode", "Shared Cockpit"),
- (b'AUTO_THROTTLE_TO_GA', "Toggles Takeoff/Go Around mode", "Shared Cockpit"),
- (b'HEADING_BUG_INC', "Increments heading hold reference bug", "Shared Cockpit"),
- (b'HEADING_BUG_DEC', "Decrements heading hold reference bug", "Shared Cockpit"),
- (b'HEADING_BUG_SET', "Set heading hold reference bug (degrees),", "Shared Cockpit"),
- (b'AP_PANEL_SPEED_HOLD', "Toggles airspeed hold mode", "Shared Cockpit"),
- (b'AP_ALT_VAR_INC', "Increments reference altitude", "Shared Cockpit"),
- (b'AP_ALT_VAR_DEC', "Decrements reference altitude", "Shared Cockpit"),
- (b'AP_VS_VAR_INC', "Increments vertical speed reference", "Shared Cockpit"),
- (b'AP_VS_VAR_DEC', "Decrements vertical speed reference", "Shared Cockpit"),
- (b'AP_SPD_VAR_INC', "Increments airspeed hold reference", "Shared Cockpit"),
- (b'AP_SPD_VAR_DEC', "Decrements airspeed hold reference", "Shared Cockpit"),
- (b'AP_PANEL_MACH_HOLD', "Toggles mach hold", "Shared Cockpit"),
- (b'AP_MACH_VAR_INC', "Increments reference mach", "Shared Cockpit"),
- (b'AP_MACH_VAR_DEC', "Decrements reference mach", "Shared Cockpit"),
- (b'AP_MACH_HOLD', "Toggles mach hold", "Shared Cockpit"),
- (b'AP_ALT_VAR_SET_METRIC', "Sets reference altitude in meters", "Shared Cockpit"),
- (b'AP_VS_VAR_SET_ENGLISH', "Sets reference vertical speed in feet per minute", "Shared Cockpit"),
- (b'AP_SPD_VAR_SET', "Sets airspeed reference in knots", "Shared Cockpit"),
- (b'AP_MACH_VAR_SET', "Sets mach reference", "Shared Cockpit"),
- (b'YAW_DAMPER_ON', "Turns yaw damper on", "Shared Cockpit"),
- (b'YAW_DAMPER_OFF', "Turns yaw damper off", "Shared Cockpit"),
- (b'YAW_DAMPER_SET', "Sets yaw damper on/off (1,0),", "Shared Cockpit"),
- (b'AP_AIRSPEED_ON', "Turns airspeed hold on", "Shared Cockpit"),
- (b'AP_AIRSPEED_OFF', "Turns airspeed hold off", "Shared Cockpit"),
- (b'AP_AIRSPEED_SET', "Sets airspeed hold on/off (1,0),", "Shared Cockpit"),
- (b'AP_MACH_ON', "Turns mach hold on", "Shared Cockpit"),
- (b'AP_MACH_OFF', "Turns mach hold off", "Shared Cockpit"),
- (b'AP_MACH_SET', "Sets mach hold on/off (1,0),", "Shared Cockpit"),
- (b'AP_PANEL_ALTITUDE_ON', "Turns altitude hold mode on (without capturing current altitude),", "Shared Cockpit"),
- (b'AP_PANEL_ALTITUDE_OFF', "Turns altitude hold mode off", "Shared Cockpit"),
- (b'AP_PANEL_ALTITUDE_SET', "Sets altitude hold mode on/off (1,0),", "Shared Cockpit"),
- (b'AP_PANEL_HEADING_ON', "Turns heading mode on (without capturing current heading),", "Shared Cockpit"),
- (b'AP_PANEL_HEADING_OFF', "Turns heading mode off", "Shared Cockpit"),
- (b'AP_PANEL_HEADING_SET', "Set heading mode on/off (1,0),", "Shared Cockpit"),
- (b'AP_PANEL_MACH_ON', "Turns on mach hold", "Shared Cockpit"),
- (b'AP_PANEL_MACH_OFF', "Turns off mach hold", "Shared Cockpit"),
- (b'AP_PANEL_MACH_SET', "Sets mach hold on/off (1,0),", "Shared Cockpit"),
- (b'AP_PANEL_SPEED_ON', "Turns on speed hold mode", "Shared Cockpit"),
- (b'AP_PANEL_SPEED_OFF', "Turns off speed hold mode", "Shared Cockpit"),
- (b'AP_PANEL_SPEED_SET', "Set speed hold mode on/off (1,0),", "Shared Cockpit"),
- (b'AP_ALT_VAR_SET_ENGLISH', "Sets altitude reference in feet", "Shared Cockpit"),
- (b'AP_VS_VAR_SET_METRIC', "Sets vertical speed reference in meters per minute", "Shared Cockpit"),
- (b'TOGGLE_FLIGHT_DIRECTOR', "Toggles flight director on/off", "Shared Cockpit"),
- (b'SYNC_FLIGHT_DIRECTOR_PITCH', "Synchronizes flight director pitch with current aircraft pitch", "Shared Cockpit"),
- (b'INCREASE_AUTOBRAKE_CONTROL', "Increments autobrake level", "Shared Cockpit"),
- (b'DECREASE_AUTOBRAKE_CONTROL', "Decrements autobrake level", "Shared Cockpit"),
- (b'AP_PANEL_SPEED_HOLD_TOGGLE', "Turns airspeed hold mode on with current airspeed", "Shared Cockpit"),
- (b'Unsupported', "Sets airspeed reference to current airspeed", "Shared Cockpit"),
- (b'AP_PANEL_MACH_HOLD_TOGGLE', "Sets mach hold reference to current mach", "Shared Cockpit"),
- (b'AP_NAV_SELECT_SET', "Sets the nav (1 or 2), which is used by the Nav hold modes", "Shared Cockpit"),
- (b'HEADING_BUG_SELECT', "Selects the heading bug for use with +/-", "Shared Cockpit"),
- (b'ALTITUDE_BUG_SELECT', "Selects the altitude reference for use with +/-", "Shared Cockpit"),
- (b'VSI_BUG_SELECT', "Selects the vertical speed reference for use with +/-", "Shared Cockpit"),
- (b'AIRSPEED_BUG_SELECT', "Selects the airspeed reference for use with +/-", "Shared Cockpit"),
- (b'AP_PITCH_REF_INC_UP', "Increments the pitch reference for pitch hold mode", "Shared Cockpit"),
- (b'AP_PITCH_REF_INC_DN', "Decrements the pitch reference for pitch hold mode", "Shared Cockpit"),
- (b'AP_PITCH_REF_SELECT', "Selects pitch reference for use with +/-", "Shared Cockpit"),
- (b'AP_ATT_HOLD', "Toggle attitude hold mode", "Shared Cockpit"),
- (b'AP_LOC_HOLD', "Toggles localizer (only), hold mode", "Shared Cockpit"),
- (b'AP_APR_HOLD', "Toggles approach hold (localizer and glide-slope),", "Shared Cockpit"),
- (b'AP_HDG_HOLD', "Toggles heading hold mode", "Shared Cockpit"),
- (b'AP_ALT_HOLD', "Toggles altitude hold mode", "Shared Cockpit"),
- (b'AP_WING_LEVELER', "Toggles wing leveler mode", "Shared Cockpit"),
- (b'AP_BC_HOLD', "Toggles the backcourse mode for the localizer hold", "Shared Cockpit"),
- (b'AP_NAV1_HOLD', "Toggles the nav hold mode", "Shared Cockpit"),
- (b'AP_MAX_BANK_INC', "Autopilot max bank angle increment.", "Shared Cockpit"),
- (b'AP_MAX_BANK_DEC', "Autopilot max bank angle decrement.", "Shared Cockpit"),
- (b'AP_N1_HOLD', "Autopilot, hold the N1 percentage at its current level.", "Shared Cockpit"),
- (b'AP_N1_REF_INC', "Increment the autopilot N1 reference.", "Shared Cockpit"),
- (b'AP_N1_REF_DEC', "Decrement the autopilot N1 reference.", "Shared Cockpit"),
- (b'AP_N1_REF_SET', "Sets the autopilot N1 reference.", "Shared Cockpit"),
- (b'FLY_BY_WIRE_ELAC_TOGGLE', "Turn on or off the fly by wire Elevators and Ailerons computer.", "Shared Cockpit"),
- (b'FLY_BY_WIRE_FAC_TOGGLE', "Turn on or off the fly by wire Flight Augmentation computer.", "Shared Cockpit"),
- (b'FLY_BY_WIRE_SEC_TOGGLE', "Turn on or off the fly by wire Spoilers and Elevators computer.", "Shared Cockpit"),
- (b'AP_VS_HOLD', "Toggle VS hold mode", "Shared Cockpit"),
- (b'FLIGHT_LEVEL_CHANGE', "Toggle FLC mode", "Shared Cockpit"),
- ]
+ class __Autopilot(EventHelper):
+ list = [
+ (b"AP_MASTER", "Toggles AP on/off", "Shared Cockpit"),
+ (b"AUTOPILOT_OFF", "Turns AP off", "Shared Cockpit"),
+ (b"AUTOPILOT_ON", "Turns AP on", "Shared Cockpit"),
+ (b"YAW_DAMPER_TOGGLE", "Toggles yaw damper on/off", "Shared Cockpit"),
+ (
+ b"AP_PANEL_HEADING_HOLD",
+ "Toggles heading hold mode on/off",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_PANEL_ALTITUDE_HOLD",
+ "Toggles altitude hold mode on/off",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_ATT_HOLD_ON",
+ "Turns on AP wing leveler and pitch hold mode",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_LOC_HOLD_ON",
+ "Turns AP localizer hold on/armed and glide-slope hold mode off",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_APR_HOLD_ON",
+ "Turns both AP localizer and glide-slope modes on/armed",
+ "Shared Cockpit",
+ ),
+ (b"AP_HDG_HOLD_ON", "Turns heading hold mode on", "Shared Cockpit"),
+ (b"AP_ALT_HOLD_ON", "Turns altitude hold mode on", "Shared Cockpit"),
+ (b"AP_WING_LEVELER_ON", "Turns wing leveler mode on", "Shared Cockpit"),
+ (
+ b"AP_BC_HOLD_ON",
+ "Turns localizer back course hold mode on/armed",
+ "Shared Cockpit",
+ ),
+ (b"AP_NAV1_HOLD_ON", "Turns lateral hold mode on", "Shared Cockpit"),
+ (b"AP_ATT_HOLD_OFF", "Turns off attitude hold mode", "Shared Cockpit"),
+ (b"AP_LOC_HOLD_OFF", "Turns off localizer hold mode", "Shared Cockpit"),
+ (b"AP_APR_HOLD_OFF", "Turns off approach hold mode", "Shared Cockpit"),
+ (b"AP_HDG_HOLD_OFF", "Turns off heading hold mode", "Shared Cockpit"),
+ (b"AP_ALT_HOLD_OFF", "Turns off altitude hold mode", "Shared Cockpit"),
+ (b"AP_WING_LEVELER_OFF", "Turns off wing leveler mode", "Shared Cockpit"),
+ (
+ b"AP_BC_HOLD_OFF",
+ "Turns off backcourse mode for localizer hold",
+ "Shared Cockpit",
+ ),
+ (b"AP_NAV1_HOLD_OFF", "Turns off nav hold mode", "Shared Cockpit"),
+ (b"AP_AIRSPEED_HOLD", "Toggles airspeed hold mode", "Shared Cockpit"),
+ (
+ b"AUTO_THROTTLE_ARM",
+ "Toggles autothrottle arming mode",
+ "Shared Cockpit",
+ ),
+ (
+ b"AUTO_THROTTLE_TO_GA",
+ "Toggles Takeoff/Go Around mode",
+ "Shared Cockpit",
+ ),
+ (
+ b"HEADING_BUG_INC",
+ "Increments heading hold reference bug",
+ "Shared Cockpit",
+ ),
+ (
+ b"HEADING_BUG_DEC",
+ "Decrements heading hold reference bug",
+ "Shared Cockpit",
+ ),
+ (
+ b"HEADING_BUG_SET",
+ "Set heading hold reference bug (degrees),",
+ "Shared Cockpit",
+ ),
+ (b"AP_PANEL_SPEED_HOLD", "Toggles airspeed hold mode", "Shared Cockpit"),
+ (b"AP_ALT_VAR_INC", "Increments reference altitude", "Shared Cockpit"),
+ (b"AP_ALT_VAR_DEC", "Decrements reference altitude", "Shared Cockpit"),
+ (b"AP_VS_VAR_INC", "Increments vertical speed reference", "Shared Cockpit"),
+ (b"AP_VS_VAR_DEC", "Decrements vertical speed reference", "Shared Cockpit"),
+ (b"AP_SPD_VAR_INC", "Increments airspeed hold reference", "Shared Cockpit"),
+ (b"AP_SPD_VAR_DEC", "Decrements airspeed hold reference", "Shared Cockpit"),
+ (b"AP_PANEL_MACH_HOLD", "Toggles mach hold", "Shared Cockpit"),
+ (b"AP_MACH_VAR_INC", "Increments reference mach", "Shared Cockpit"),
+ (b"AP_MACH_VAR_DEC", "Decrements reference mach", "Shared Cockpit"),
+ (b"AP_MACH_HOLD", "Toggles mach hold", "Shared Cockpit"),
+ (
+ b"AP_ALT_VAR_SET_METRIC",
+ "Sets reference altitude in meters",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_VS_VAR_SET_ENGLISH",
+ "Sets reference vertical speed in feet per minute",
+ "Shared Cockpit",
+ ),
+ (b"AP_SPD_VAR_SET", "Sets airspeed reference in knots", "Shared Cockpit"),
+ (b"AP_MACH_VAR_SET", "Sets mach reference", "Shared Cockpit"),
+ (b"YAW_DAMPER_ON", "Turns yaw damper on", "Shared Cockpit"),
+ (b"YAW_DAMPER_OFF", "Turns yaw damper off", "Shared Cockpit"),
+ (b"YAW_DAMPER_SET", "Sets yaw damper on/off (1,0),", "Shared Cockpit"),
+ (b"AP_AIRSPEED_ON", "Turns airspeed hold on", "Shared Cockpit"),
+ (b"AP_AIRSPEED_OFF", "Turns airspeed hold off", "Shared Cockpit"),
+ (b"AP_AIRSPEED_SET", "Sets airspeed hold on/off (1,0),", "Shared Cockpit"),
+ (b"AP_MACH_ON", "Turns mach hold on", "Shared Cockpit"),
+ (b"AP_MACH_OFF", "Turns mach hold off", "Shared Cockpit"),
+ (b"AP_MACH_SET", "Sets mach hold on/off (1,0),", "Shared Cockpit"),
+ (
+ b"AP_PANEL_ALTITUDE_ON",
+ "Turns altitude hold mode on (without capturing current altitude),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_PANEL_ALTITUDE_OFF",
+ "Turns altitude hold mode off",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_PANEL_ALTITUDE_SET",
+ "Sets altitude hold mode on/off (1,0),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_PANEL_HEADING_ON",
+ "Turns heading mode on (without capturing current heading),",
+ "Shared Cockpit",
+ ),
+ (b"AP_PANEL_HEADING_OFF", "Turns heading mode off", "Shared Cockpit"),
+ (
+ b"AP_PANEL_HEADING_SET",
+ "Set heading mode on/off (1,0),",
+ "Shared Cockpit",
+ ),
+ (b"AP_PANEL_MACH_ON", "Turns on mach hold", "Shared Cockpit"),
+ (b"AP_PANEL_MACH_OFF", "Turns off mach hold", "Shared Cockpit"),
+ (b"AP_PANEL_MACH_SET", "Sets mach hold on/off (1,0),", "Shared Cockpit"),
+ (b"AP_PANEL_SPEED_ON", "Turns on speed hold mode", "Shared Cockpit"),
+ (b"AP_PANEL_SPEED_OFF", "Turns off speed hold mode", "Shared Cockpit"),
+ (
+ b"AP_PANEL_SPEED_SET",
+ "Set speed hold mode on/off (1,0),",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_ALT_VAR_SET_ENGLISH",
+ "Sets altitude reference in feet",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_VS_VAR_SET_METRIC",
+ "Sets vertical speed reference in meters per minute",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_FLIGHT_DIRECTOR",
+ "Toggles flight director on/off",
+ "Shared Cockpit",
+ ),
+ (
+ b"SYNC_FLIGHT_DIRECTOR_PITCH",
+ "Synchronizes flight director pitch with current aircraft pitch",
+ "Shared Cockpit",
+ ),
+ (
+ b"INCREASE_AUTOBRAKE_CONTROL",
+ "Increments autobrake level",
+ "Shared Cockpit",
+ ),
+ (
+ b"DECREASE_AUTOBRAKE_CONTROL",
+ "Decrements autobrake level",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_PANEL_SPEED_HOLD_TOGGLE",
+ "Turns airspeed hold mode on with current airspeed",
+ "Shared Cockpit",
+ ),
+ (
+ b"Unsupported",
+ "Sets airspeed reference to current airspeed",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_PANEL_MACH_HOLD_TOGGLE",
+ "Sets mach hold reference to current mach",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_NAV_SELECT_SET",
+ "Sets the nav (1 or 2), which is used by the Nav hold modes",
+ "Shared Cockpit",
+ ),
+ (
+ b"HEADING_BUG_SELECT",
+ "Selects the heading bug for use with +/-",
+ "Shared Cockpit",
+ ),
+ (
+ b"ALTITUDE_BUG_SELECT",
+ "Selects the altitude reference for use with +/-",
+ "Shared Cockpit",
+ ),
+ (
+ b"VSI_BUG_SELECT",
+ "Selects the vertical speed reference for use with +/-",
+ "Shared Cockpit",
+ ),
+ (
+ b"AIRSPEED_BUG_SELECT",
+ "Selects the airspeed reference for use with +/-",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_PITCH_REF_INC_UP",
+ "Increments the pitch reference for pitch hold mode",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_PITCH_REF_INC_DN",
+ "Decrements the pitch reference for pitch hold mode",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_PITCH_REF_SELECT",
+ "Selects pitch reference for use with +/-",
+ "Shared Cockpit",
+ ),
+ (b"AP_ATT_HOLD", "Toggle attitude hold mode", "Shared Cockpit"),
+ (b"AP_LOC_HOLD", "Toggles localizer (only), hold mode", "Shared Cockpit"),
+ (
+ b"AP_APR_HOLD",
+ "Toggles approach hold (localizer and glide-slope),",
+ "Shared Cockpit",
+ ),
+ (b"AP_HDG_HOLD", "Toggles heading hold mode", "Shared Cockpit"),
+ (b"AP_ALT_HOLD", "Toggles altitude hold mode", "Shared Cockpit"),
+ (b"AP_WING_LEVELER", "Toggles wing leveler mode", "Shared Cockpit"),
+ (
+ b"AP_BC_HOLD",
+ "Toggles the backcourse mode for the localizer hold",
+ "Shared Cockpit",
+ ),
+ (b"AP_NAV1_HOLD", "Toggles the nav hold mode", "Shared Cockpit"),
+ (
+ b"AP_MAX_BANK_INC",
+ "Autopilot max bank angle increment.",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_MAX_BANK_DEC",
+ "Autopilot max bank angle decrement.",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_N1_HOLD",
+ "Autopilot, hold the N1 percentage at its current level.",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_N1_REF_INC",
+ "Increment the autopilot N1 reference.",
+ "Shared Cockpit",
+ ),
+ (
+ b"AP_N1_REF_DEC",
+ "Decrement the autopilot N1 reference.",
+ "Shared Cockpit",
+ ),
+ (b"AP_N1_REF_SET", "Sets the autopilot N1 reference.", "Shared Cockpit"),
+ (
+ b"FLY_BY_WIRE_ELAC_TOGGLE",
+ "Turn on or off the fly by wire Elevators and Ailerons computer.",
+ "Shared Cockpit",
+ ),
+ (
+ b"FLY_BY_WIRE_FAC_TOGGLE",
+ "Turn on or off the fly by wire Flight Augmentation computer.",
+ "Shared Cockpit",
+ ),
+ (
+ b"FLY_BY_WIRE_SEC_TOGGLE",
+ "Turn on or off the fly by wire Spoilers and Elevators computer.",
+ "Shared Cockpit",
+ ),
+ (b"AP_VS_HOLD", "Toggle VS hold mode", "Shared Cockpit"),
+ (b"FLIGHT_LEVEL_CHANGE", "Toggle FLC mode", "Shared Cockpit"),
+ ]
- class __Fuel_System(EventHelper):
- list = [
- (b'FUEL_SELECTOR_OFF', "Turns selector 1 to OFF position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_ALL', "Turns selector 1 to ALL position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_LEFT', "Turns selector 1 to LEFT position (burns from tip then aux then main),", "Shared Cockpit"),
- (b'FUEL_SELECTOR_RIGHT', "Turns selector 1 to RIGHT position (burns from tip then aux then main),", "Shared Cockpit"),
- (b'FUEL_SELECTOR_LEFT_AUX', "Turns selector 1 to LEFT AUX position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_RIGHT_AUX', "Turns selector 1 to RIGHT AUX position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_CENTER', "Turns selector 1 to CENTER position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_SET', '''Sets selector 1 position (see code list below),
+ class __Fuel_System(EventHelper):
+ list = [
+ (
+ b"FUEL_SELECTOR_OFF",
+ "Turns selector 1 to OFF position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_ALL",
+ "Turns selector 1 to ALL position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_LEFT",
+ "Turns selector 1 to LEFT position (burns from tip then aux then main),",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_RIGHT",
+ "Turns selector 1 to RIGHT position (burns from tip then aux then main),",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_LEFT_AUX",
+ "Turns selector 1 to LEFT AUX position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_RIGHT_AUX",
+ "Turns selector 1 to RIGHT AUX position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_CENTER",
+ "Turns selector 1 to CENTER position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_SET",
+ """Sets selector 1 position (see code list below),
FUEL_TANK_SELECTOR_OFF = 0
FUEL_TANK_SELECTOR_ALL = 1
FUEL_TANK_SELECTOR_LEFT = 2
@@ -591,713 +1528,1943 @@ class __Fuel_System(EventHelper):
FUEL_TANK_SELECTOR_CROSSFEED_R2L = 15
FUEL_TANK_SELECTOR_BOTH = 16
FUEL_TANK_SELECTOR_EXTERNAL_ALL = 17
- FUEL_TANK_SELECTOR_ISOLATE = 18''', "Shared Cockpit"),
- (b'FUEL_SELECTOR_2_OFF', "Turns selector 2 to OFF position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_2_ALL', "Turns selector 2 to ALL position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_2_LEFT', "Turns selector 2 to LEFT position (burns from tip then aux then main),", "Shared Cockpit"),
- (b'FUEL_SELECTOR_2_RIGHT', "Turns selector 2 to RIGHT position (burns from tip then aux then main),", "Shared Cockpit"),
- (b'FUEL_SELECTOR_2_LEFT_AUX', "Turns selector 2 to LEFT AUX position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_2_RIGHT_AUX', "Turns selector 2 to RIGHT AUX position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_2_CENTER', "Turns selector 2 to CENTER position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_2_SET', "Sets selector 2 position (see code list below),", "Shared Cockpit"),
- (b'FUEL_SELECTOR_3_OFF', "Turns selector 3 to OFF position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_3_ALL', "Turns selector 3 to ALL position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_3_LEFT', "Turns selector 3 to LEFT position (burns from tip then aux then main),", "Shared Cockpit"),
- (b'FUEL_SELECTOR_3_RIGHT', "Turns selector 3 to RIGHT position (burns from tip then aux then main),", "Shared Cockpit"),
- (b'FUEL_SELECTOR_3_LEFT_AUX', "Turns selector 3 to LEFT AUX position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_3_RIGHT_AUX', "Turns selector 3 to RIGHT AUX position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_3_CENTER', "Turns selector 3 to CENTER position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_3_SET', "Sets selector 3 position (see code list below),", "Shared Cockpit"),
- (b'FUEL_SELECTOR_4_OFF', "Turns selector 4 to OFF position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_4_ALL', "Turns selector 4 to ALL position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_4_LEFT', "Turns selector 4 to LEFT position (burns from tip then aux then main),", "Shared Cockpit"),
- (b'FUEL_SELECTOR_4_RIGHT', "Turns selector 4 to RIGHT position (burns from tip then aux then main),", "Shared Cockpit"),
- (b'FUEL_SELECTOR_4_LEFT_AUX', "Turns selector 4 to LEFT AUX position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_4_RIGHT_AUX', "Turns selector 4 to RIGHT AUX position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_4_CENTER', "Turns selector 4 to CENTER position", "Shared Cockpit"),
- (b'FUEL_SELECTOR_4_SET', "Sets selector 4 position (see code list below),", "Shared Cockpit"),
- (b'CROSS_FEED_OPEN', "Opens cross feed valve (when used in conjunction with \"isolate\" tank),", "Shared Cockpit"),
- (b'CROSS_FEED_TOGGLE', "Toggles crossfeed valve (when used in conjunction with \"isolate\" tank),", "Shared Cockpit"),
- (b'CROSS_FEED_OFF', "Closes crossfeed valve (when used in conjunction with \"isolate\" tank),", "Shared Cockpit"),
- (b'FUEL_DUMP_SWITCH_SET', "Set to True or False. The switch can only be set to True if fuel_dump_rate is specified in the aircraft configuration file, which indicates that a fuel dump system exists.", "Shared Cockpit"),
- (b'ANTIDETONATION_TANK_VALVE_TOGGLE', "Toggle the antidetonation valve. Pass a value to determine which tank, if there are multiple tanks, to use. Tanks are indexed from 1. Refer to the document Notes on Aircraft Systems.", "Shared Cockpit"),
- (b'NITROUS_TANK_VALVE_TOGGLE', "Toggle the nitrous valve. Pass a value to determine which tank, if there are multiple tanks, to use. Tanks are indexed from 1.", "Shared Cockpit"),
- (b'REPAIR_AND_REFUEL', "Fully repair and refuel the user aircraft. Ignored if flight realism is enforced.", "Shared Cockpit"),
- (b'FUEL_DUMP_TOGGLE', "Turns on or off the fuel dump switch.", "Shared Cockpit"),
- (b'REQUEST_FUEL_KEY', "Request a fuel truck. The aircraft must be in a parking spot for this to be successful.", "Shared Cockpit"),
- ]
+ FUEL_TANK_SELECTOR_ISOLATE = 18""",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_2_OFF",
+ "Turns selector 2 to OFF position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_2_ALL",
+ "Turns selector 2 to ALL position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_2_LEFT",
+ "Turns selector 2 to LEFT position (burns from tip then aux then main),",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_2_RIGHT",
+ "Turns selector 2 to RIGHT position (burns from tip then aux then main),",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_2_LEFT_AUX",
+ "Turns selector 2 to LEFT AUX position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_2_RIGHT_AUX",
+ "Turns selector 2 to RIGHT AUX position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_2_CENTER",
+ "Turns selector 2 to CENTER position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_2_SET",
+ "Sets selector 2 position (see code list below),",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_3_OFF",
+ "Turns selector 3 to OFF position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_3_ALL",
+ "Turns selector 3 to ALL position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_3_LEFT",
+ "Turns selector 3 to LEFT position (burns from tip then aux then main),",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_3_RIGHT",
+ "Turns selector 3 to RIGHT position (burns from tip then aux then main),",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_3_LEFT_AUX",
+ "Turns selector 3 to LEFT AUX position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_3_RIGHT_AUX",
+ "Turns selector 3 to RIGHT AUX position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_3_CENTER",
+ "Turns selector 3 to CENTER position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_3_SET",
+ "Sets selector 3 position (see code list below),",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_4_OFF",
+ "Turns selector 4 to OFF position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_4_ALL",
+ "Turns selector 4 to ALL position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_4_LEFT",
+ "Turns selector 4 to LEFT position (burns from tip then aux then main),",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_4_RIGHT",
+ "Turns selector 4 to RIGHT position (burns from tip then aux then main),",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_4_LEFT_AUX",
+ "Turns selector 4 to LEFT AUX position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_4_RIGHT_AUX",
+ "Turns selector 4 to RIGHT AUX position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_4_CENTER",
+ "Turns selector 4 to CENTER position",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_4_SET",
+ "Sets selector 4 position (see code list below),",
+ "Shared Cockpit",
+ ),
+ (
+ b"CROSS_FEED_OPEN",
+ 'Opens cross feed valve (when used in conjunction with "isolate" tank),',
+ "Shared Cockpit",
+ ),
+ (
+ b"CROSS_FEED_TOGGLE",
+ 'Toggles crossfeed valve (when used in conjunction with "isolate" tank),',
+ "Shared Cockpit",
+ ),
+ (
+ b"CROSS_FEED_OFF",
+ 'Closes crossfeed valve (when used in conjunction with "isolate" tank),',
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_DUMP_SWITCH_SET",
+ "Set to True or False. The switch can only be set to True if fuel_dump_rate is specified in the aircraft configuration file, which indicates that a fuel dump system exists.",
+ "Shared Cockpit",
+ ),
+ (
+ b"ANTIDETONATION_TANK_VALVE_TOGGLE",
+ "Toggle the antidetonation valve. Pass a value to determine which tank, if there are multiple tanks, to use. Tanks are indexed from 1. Refer to the document Notes on Aircraft Systems.",
+ "Shared Cockpit",
+ ),
+ (
+ b"NITROUS_TANK_VALVE_TOGGLE",
+ "Toggle the nitrous valve. Pass a value to determine which tank, if there are multiple tanks, to use. Tanks are indexed from 1.",
+ "Shared Cockpit",
+ ),
+ (
+ b"REPAIR_AND_REFUEL",
+ "Fully repair and refuel the user aircraft. Ignored if flight realism is enforced.",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_DUMP_TOGGLE",
+ "Turns on or off the fuel dump switch.",
+ "Shared Cockpit",
+ ),
+ (
+ b"REQUEST_FUEL_KEY",
+ "Request a fuel truck. The aircraft must be in a parking spot for this to be successful.",
+ "Shared Cockpit",
+ ),
+ ]
- class __Fuel_Selection_Keys(EventHelper):
- list = [
- (b'FUEL_SELECTOR_LEFT_MAIN', "Sets the fuel selector. Fuel will be taken in the order left tip, left aux, then main fuel tanks.", "Shared Cockpit"),
- (b'FUEL_SELECTOR_2_LEFT_MAIN', "Sets the fuel selector for engine 2.", "Shared Cockpit"),
- (b'FUEL_SELECTOR_3_LEFT_MAIN', "Sets the fuel selector for engine 3.", "Shared Cockpit"),
- (b'FUEL_SELECTOR_4_LEFT_MAIN', "Sets the fuel selector for engine 4.", "Shared Cockpit"),
- (b'FUEL_SELECTOR_RIGHT_MAIN', "Sets the fuel selector. Fuel will be taken in the order right tip, right aux, then main fuel tanks.", "Shared Cockpit"),
- (b'FUEL_SELECTOR_2_RIGHT_MAIN', "Sets the fuel selector for engine 2.", "Shared Cockpit"),
- (b'FUEL_SELECTOR_3_RIGHT_MAIN', "Sets the fuel selector for engine 3.", "Shared Cockpit"),
- (b'FUEL_SELECTOR_4_RIGHT_MAIN', "Sets the fuel selector for engine 4.", "Shared Cockpit"),
- ]
+ class __Fuel_Selection_Keys(EventHelper):
+ list = [
+ (
+ b"FUEL_SELECTOR_LEFT_MAIN",
+ "Sets the fuel selector. Fuel will be taken in the order left tip, left aux, then main fuel tanks.",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_2_LEFT_MAIN",
+ "Sets the fuel selector for engine 2.",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_3_LEFT_MAIN",
+ "Sets the fuel selector for engine 3.",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_4_LEFT_MAIN",
+ "Sets the fuel selector for engine 4.",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_RIGHT_MAIN",
+ "Sets the fuel selector. Fuel will be taken in the order right tip, right aux, then main fuel tanks.",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_2_RIGHT_MAIN",
+ "Sets the fuel selector for engine 2.",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_3_RIGHT_MAIN",
+ "Sets the fuel selector for engine 3.",
+ "Shared Cockpit",
+ ),
+ (
+ b"FUEL_SELECTOR_4_RIGHT_MAIN",
+ "Sets the fuel selector for engine 4.",
+ "Shared Cockpit",
+ ),
+ ]
- class __Avionics(EventHelper):
- list = [
- (b'XPNDR', "Sequentially selects the transponder digits for use with +/-.", "Shared Cockpit"),
- (b'ADF', "Sequentially selects the ADF tuner digits for use with +/-. Follow by KEY_SELECT_2 for ADF 2.", "Shared Cockpit"),
- (b'DME', "Selects the DME for use with +/-", "Shared Cockpit"),
- (b'COM_RADIO', "Sequentially selects the COM tuner digits for use with +/-. Follow by KEY_SELECT_2 for COM 2.", "All aircraft"),
- (b'VOR_OBS', "Sequentially selects the VOR OBS for use with +/-. Follow by KEY_SELECT_2 for VOR 2.", "Shared Cockpit"),
- (b'NAV_RADIO', "Sequentially selects the NAV tuner digits for use with +/-. Follow by KEY_SELECT_2 for NAV 2.", "Shared Cockpit"),
- (b'COM_RADIO_WHOLE_DEC', "Decrements COM by one MHz", "All aircraft"),
- (b'COM_RADIO_WHOLE_INC', "Increments COM by one MHz", "All aircraft"),
- (b'COM_RADIO_FRACT_DEC', "Decrements COM by 25 KHz", "All aircraft"),
- (b'COM_RADIO_FRACT_INC', "Increments COM by 25 KHz", "All aircraft"),
- (b'NAV1_RADIO_WHOLE_DEC', "Decrements Nav 1 by one MHz", "Shared Cockpit"),
- (b'NAV1_RADIO_WHOLE_INC', "Increments Nav 1 by one MHz", "Shared Cockpit"),
- (b'NAV1_RADIO_FRACT_DEC', "Decrements Nav 1 by 25 KHz", "Shared Cockpit"),
- (b'NAV1_RADIO_FRACT_INC', "Increments Nav 1 by 25 KHz", "Shared Cockpit"),
- (b'NAV2_RADIO_WHOLE_DEC', "Decrements Nav 2 by one MHz", "Shared Cockpit"),
- (b'NAV2_RADIO_WHOLE_INC', "Increments Nav 2 by one MHz", "Shared Cockpit"),
- (b'NAV2_RADIO_FRACT_DEC', "Decrements Nav 2 by 25 KHz", "Shared Cockpit"),
- (b'NAV2_RADIO_FRACT_INC', "Increments Nav 2 by 25 KHz", "Shared Cockpit"),
- (b'ADF_100_INC', "Increments ADF by 100 KHz", "Shared Cockpit"),
- (b'ADF_10_INC', "Increments ADF by 10 KHz", "Shared Cockpit"),
- (b'ADF_1_INC', "Increments ADF by 1 KHz", "Shared Cockpit"),
- (b'XPNDR_1000_INC', "Increments first digit of transponder", "Shared Cockpit"),
- (b'XPNDR_100_INC', "Increments second digit of transponder", "Shared Cockpit"),
- (b'XPNDR_10_INC', "Increments third digit of transponder", "Shared Cockpit"),
- (b'XPNDR_1_INC', "Increments fourth digit of transponder", "Shared Cockpit"),
- (b'VOR1_OBI_DEC', "Decrements the VOR 1 OBS setting", "Shared Cockpit"),
- (b'VOR1_OBI_INC', "Increments the VOR 1 OBS setting", "Shared Cockpit"),
- (b'VOR2_OBI_DEC', "Decrements the VOR 2 OBS setting", "Shared Cockpit"),
- (b'VOR2_OBI_INC', "Increments the VOR 2 OBS setting", "Shared Cockpit"),
- (b'ADF_100_DEC', "Decrements ADF by 100 KHz", "Shared Cockpit"),
- (b'ADF_10_DEC', "Decrements ADF by 10 KHz", "Shared Cockpit"),
- (b'ADF_1_DEC', "Decrements ADF by 1 KHz", "Shared Cockpit"),
- (b'COM_RADIO_SET', "Sets COM frequency (BCD Hz),", "All aircraft"),
- (b'NAV1_RADIO_SET', "Sets NAV 1 frequency (BCD Hz),", "Shared Cockpit"),
- (b'NAV2_RADIO_SET', "Sets NAV 2 frequency (BCD Hz),", "Shared Cockpit"),
- (b'ADF_SET', "Sets ADF frequency (BCD Hz),", "Shared Cockpit"),
- (b'XPNDR_SET', "Sets transponder code (BCD),", "All aircraft"),
- (b'VOR1_SET', "Sets OBS 1 (0 to 360),", "Shared Cockpit"),
- (b'VOR2_SET', "Sets OBS 2 (0 to 360),", "Shared Cockpit"),
- (b'DME1_TOGGLE', "Sets DME display to Nav 1", "Shared Cockpit"),
- (b'DME2_TOGGLE', "Sets DME display to Nav 2", "Shared Cockpit"),
- (b'RADIO_VOR1_IDENT_DISABLE', "Turns NAV 1 ID off", "Shared Cockpit"),
- (b'RADIO_VOR2_IDENT_DISABLE', "Turns NAV 2 ID off", "Shared Cockpit"),
- (b'RADIO_DME1_IDENT_DISABLE', "Turns DME 1 ID off", "Shared Cockpit"),
- (b'RADIO_DME2_IDENT_DISABLE', "Turns DME 2 ID off", "Shared Cockpit"),
- (b'RADIO_ADF_IDENT_DISABLE', "Turns ADF 1 ID off", "Shared Cockpit"),
- (b'RADIO_VOR1_IDENT_ENABLE', "Turns NAV 1 ID on", "Shared Cockpit"),
- (b'RADIO_VOR2_IDENT_ENABLE', "Turns NAV 2 ID on", "Shared Cockpit"),
- (b'RADIO_DME1_IDENT_ENABLE', "Turns DME 1 ID on", "Shared Cockpit"),
- (b'RADIO_DME2_IDENT_ENABLE', "Turns DME 2 ID on", "Shared Cockpit"),
- (b'RADIO_ADF_IDENT_ENABLE', "Turns ADF 1 ID on", "Shared Cockpit"),
- (b'RADIO_VOR1_IDENT_TOGGLE', "Toggles NAV 1 ID", "Shared Cockpit"),
- (b'RADIO_VOR2_IDENT_TOGGLE', "Toggles NAV 2 ID", "Shared Cockpit"),
- (b'RADIO_DME1_IDENT_TOGGLE', "Toggles DME 1 ID", "Shared Cockpit"),
- (b'RADIO_DME2_IDENT_TOGGLE', "Toggles DME 2 ID", "Shared Cockpit"),
- (b'RADIO_ADF_IDENT_TOGGLE', "Toggles ADF 1 ID", "Shared Cockpit"),
- (b'RADIO_VOR1_IDENT_SET', "Sets NAV 1 ID (on/off),", "Shared Cockpit"),
- (b'RADIO_VOR2_IDENT_SET', "Sets NAV 2 ID (on/off),", "Shared Cockpit"),
- (b'RADIO_DME1_IDENT_SET', "Sets DME 1 ID (on/off),", "Shared Cockpit"),
- (b'RADIO_DME2_IDENT_SET', "Sets DME 2 ID (on/off),", "Shared Cockpit"),
- (b'RADIO_ADF_IDENT_SET', "Sets ADF 1 ID (on/off),", "Shared Cockpit"),
- (b'ADF_CARD_INC', "Increments ADF card", "Shared Cockpit"),
- (b'ADF_CARD_DEC', "Decrements ADF card", "Shared Cockpit"),
- (b'ADF_CARD_SET', "Sets ADF card (0-360),", "Shared Cockpit"),
- (b'TOGGLE_DME', "Toggles between NAV 1 and NAV 2", "Shared Cockpit"),
- (b'AVIONICS_MASTER_SET', "Sets the avionics master switch", "All aircraft"),
- (b'TOGGLE_AVIONICS_MASTER', "Toggles the avionics master switch", "All aircraft"),
- (b'COM_STBY_RADIO_SET', "Sets COM 1 standby frequency (BCD Hz),", "All aircraft"),
- (b'COM_STBY_RADIO_SWAP', "Swaps COM 1 frequency with standby", "All aircraft"),
- (b'COM_RADIO_FRACT_DEC_CARRY', "Decrement COM 1 frequency by 25 KHz, and carry when digit wraps", "All aircraft"),
- (b'COM_RADIO_FRACT_INC_CARRY', "Increment COM 1 frequency by 25 KHz, and carry when digit wraps", "All aircraft"),
- (b'COM2_RADIO_WHOLE_DEC', "Decrement COM 2 frequency by 1 MHz, with no carry when digit wraps", "All aircraft"),
- (b'COM2_RADIO_WHOLE_INC', "Increment COM 2 frequency by 1 MHz, with no carry when digit wraps", "All aircraft"),
- (b'COM2_RADIO_FRACT_DEC', "Decrement COM 2 frequency by 25 KHz, with no carry when digit wraps", "All aircraft"),
- (b'COM2_RADIO_FRACT_DEC_CARRY', "Decrement COM 2 frequency by 25 KHz, and carry when digit wraps", "All aircraft"),
- (b'COM2_RADIO_FRACT_INC', "Increment COM 2 frequency by 25 KHz, with no carry when digit wraps", "All aircraft"),
- (b'COM2_RADIO_FRACT_INC_CARRY', "Increment COM 2 frequency by 25 KHz, and carry when digit wraps", "All aircraft"),
- (b'COM2_RADIO_SET', "Sets COM 2 frequency (BCD Hz),", "All aircraft"),
- (b'COM2_STBY_RADIO_SET', "Sets COM 2 standby frequency (BCD Hz),", "All aircraft"),
- (b'COM2_RADIO_SWAP', "Swaps COM 2 frequency with standby", "All aircraft"),
- (b'NAV1_RADIO_FRACT_DEC_CARRY', "Decrement NAV 1 frequency by 50 KHz, and carry when digit wraps", "Shared Cockpit"),
- (b'NAV1_RADIO_FRACT_INC_CARRY', "Increment NAV 1 frequency by 50 KHz, and carry when digit wraps", "Shared Cockpit"),
- (b'NAV1_STBY_SET', "Sets NAV 1 standby frequency (BCD Hz),", "Shared Cockpit"),
- (b'NAV1_RADIO_SWAP', "Swaps NAV 1 frequency with standby", "Shared Cockpit"),
- (b'NAV2_RADIO_FRACT_DEC_CARRY', "Decrement NAV 2 frequency by 50 KHz, and carry when digit wraps", "Shared Cockpit"),
- (b'NAV2_RADIO_FRACT_INC_CARRY', "Increment NAV 2 frequency by 50 KHz, and carry when digit wraps", "Shared Cockpit"),
- (b'NAV2_STBY_SET', "Sets NAV 2 standby frequency (BCD Hz),", "Shared Cockpit"),
- (b'NAV2_RADIO_SWAP', "Swaps NAV 2 frequency with standby", "Shared Cockpit"),
- (b'ADF1_RADIO_TENTHS_DEC', "Decrements ADF 1 by 0.1 KHz.", "Shared Cockpit"),
- (b'ADF1_RADIO_TENTHS_INC', "Increments ADF 1 by 0.1 KHz.", "Shared Cockpit"),
- (b'XPNDR_1000_DEC', "Decrements first digit of transponder", "Shared Cockpit"),
- (b'XPNDR_100_DEC', "Decrements second digit of transponder", "Shared Cockpit"),
- (b'XPNDR_10_DEC', "Decrements third digit of transponder", "Shared Cockpit"),
- (b'XPNDR_1_DEC', "Decrements fourth digit of transponder", "Shared Cockpit"),
- (b'XPNDR_DEC_CARRY', "Decrements fourth digit of transponder, and with carry.", "Shared Cockpit"),
- (b'XPNDR_INC_CARRY', "Increments fourth digit of transponder, and with carry.", "Shared Cockpit"),
- (b'ADF_FRACT_DEC_CARRY', "Decrements ADF 1 frequency by 0.1 KHz, with carry", "Shared Cockpit"),
- (b'ADF_FRACT_INC_CARRY', "Increments ADF 1 frequency by 0.1 KHz, with carry", "Shared Cockpit"),
- (b'COM1_TRANSMIT_SELECT', "Selects COM 1 to transmit", "All aircraft"),
- (b'COM2_TRANSMIT_SELECT', "Selects COM 2 to transmit", "All aircraft"),
- (b'COM_RECEIVE_ALL_TOGGLE', "Toggles all COM radios to receive on", "All aircraft"),
- (b'COM_RECEIVE_ALL_SET', "Sets whether to receive on all COM radios (1,0),", "All aircraft"),
- (b'MARKER_SOUND_TOGGLE', "Toggles marker beacon sound on/off", "Shared Cockpit"),
- (b'Unsupported', "Sets marker beacon sound (1, 0),", "Shared Cockpit"),
- (b'ADF_COMPLETE_SET', "Sets ADF 1 frequency (BCD Hz),", "Shared Cockpit"),
- (b'ADF1_WHOLE_INC', "Increments ADF 1 by 1 KHz, with carry as digits wrap.", "Shared Cockpit"),
- (b'ADF1_WHOLE_DEC', "Decrements ADF 1 by 1 KHz, with carry as digits wrap.", "Shared Cockpit"),
- (b'ADF2_100_INC', "Increments the ADF 2 frequency 100 digit, with wrapping", "Shared Cockpit"),
- (b'ADF2_10_INC', "Increments the ADF 2 frequency 10 digit, with wrapping", "Shared Cockpit"),
- (b'ADF2_1_INC', "Increments the ADF 2 frequency 1 digit, with wrapping", "Shared Cockpit"),
- (b'ADF2_RADIO_TENTHS_INC', "Increments ADF 2 frequency 1/10 digit, with wrapping", "Shared Cockpit"),
- (b'ADF2_100_DEC', "Decrements the ADF 2 frequency 100 digit, with wrapping", "Shared Cockpit"),
- (b'ADF2_10_DEC', "Decrements the ADF 2 frequency 10 digit, with wrapping", "Shared Cockpit"),
- (b'ADF2_1_DEC', "Decrements the ADF 2 frequency 1 digit, with wrapping", "Shared Cockpit"),
- (b'ADF2_RADIO_TENTHS_DEC', "Decrements ADF 2 frequency 1/10 digit, with wrapping", "Shared Cockpit"),
- (b'ADF2_WHOLE_INC', "Increments ADF 2 by 1 KHz, with carry as digits wrap.", "Shared Cockpit"),
- (b'ADF2_WHOLE_DEC', "Decrements ADF 2 by 1 KHz, with carry as digits wrap.", "Shared Cockpit"),
- (b'ADF2_FRACT_DEC_CARRY', "Decrements ADF 2 frequency by 0.1 KHz, with carry", "Shared Cockpit"),
- (b'ADF2_FRACT_INC_CARRY', "Increments ADF 2 frequency by 0.1 KHz, with carry", "Shared Cockpit"),
- (b'ADF2_COMPLETE_SET', "Sets ADF 1 frequency (BCD Hz),", "Shared Cockpit"),
- (b'RADIO_ADF2_IDENT_DISABLE', "Turns ADF 2 ID off", "Shared Cockpit"),
- (b'RADIO_ADF2_IDENT_ENABLE', "Turns ADF 2 ID on", "Shared Cockpit"),
- (b'RADIO_ADF2_IDENT_TOGGLE', "Toggles ADF 2 ID", "Shared Cockpit"),
- (b'RADIO_ADF2_IDENT_SET', "Sets ADF 2 ID on/off (1,0),", "Shared Cockpit"),
- (b'FREQUENCY_SWAP', "Swaps frequency with standby on whichever NAV or COM radio is selected.", "Shared Cockpit"),
- (b'TOGGLE_GPS_DRIVES_NAV1', "Toggles between GPS and NAV 1 driving NAV 1 OBS display (and AP),", "Shared Cockpit"),
- (b'GPS_POWER_BUTTON', "Toggles power button", "Shared Cockpit"),
- (b'GPS_NEAREST_BUTTON', "Selects Nearest Airport Page", "Shared Cockpit"),
- (b'GPS_OBS_BUTTON', "Toggles automatic sequencing of waypoints", "Shared Cockpit"),
- (b'GPS_MSG_BUTTON', "Toggles the Message Page", "Shared Cockpit"),
- (b'GPS_MSG_BUTTON_DOWN', "Triggers the pressing of the message button.", "Shared Cockpit"),
- (b'GPS_MSG_BUTTON_UP', "Triggers the release of the message button", "Shared Cockpit"),
- (b'GPS_FLIGHTPLAN_BUTTON', "Displays the programmed flightplan.", "Shared Cockpit"),
- (b'GPS_TERRAIN_BUTTON', "Displays terrain information on default display", "Shared Cockpit"),
- (b'GPS_PROCEDURE_BUTTON', "Displays the approach procedure page.", "Shared Cockpit"),
- (b'GPS_ZOOMIN_BUTTON', "Zooms in default display", "Shared Cockpit"),
- (b'GPS_ZOOMOUT_BUTTON', "Zooms out default display", "Shared Cockpit"),
- (b'GPS_DIRECTTO_BUTTON', "Brings up the \"Direct To\" page", "Shared Cockpit"),
- (b'GPS_MENU_BUTTON', "Brings up page to select active legs in a flightplan.", "Shared Cockpit"),
- (b'GPS_CLEAR_BUTTON', "Clears entered data on a page", "Shared Cockpit"),
- (b'GPS_CLEAR_ALL_BUTTON', "Clears all data immediately", "Shared Cockpit"),
- (b'GPS_CLEAR_BUTTON_DOWN', "Triggers the pressing of the Clear button", "Shared Cockpit"),
- (b'GPS_CLEAR_BUTTON_UP', "Triggers the release of the Clear button.", "Shared Cockpit"),
- (b'GPS_ENTER_BUTTON', "Approves entered data.", "Shared Cockpit"),
- (b'GPS_CURSOR_BUTTON', "Selects GPS cursor", "Shared Cockpit"),
- (b'GPS_GROUP_KNOB_INC', "Increments cursor", "Shared Cockpit"),
- (b'GPS_GROUP_KNOB_DEC', "Decrements cursor", "Shared Cockpit"),
- (b'GPS_PAGE_KNOB_INC', "Increments through pages", "Shared Cockpit"),
- (b'GPS_PAGE_KNOB_DEC', "Decrements through pages", "Shared Cockpit"),
- (b'DME_SELECT', "Selects one of the two DME systems (1,2),.", "Shared Cockpit"),
- (b'RADIO_SELECTED_DME_IDENT_ENABLE', "Turns on the identification sound for the selected DME.", "Shared Cockpit"),
- (b'RADIO_SELECTED_DME_IDENT_DISABLE', "Turns off the identification sound for the selected DME.", "Shared Cockpit"),
- (b'RADIO_SELECTED_DME_IDENT_SET', "Sets the DME identification sound to the given filename.", "Shared Cockpit"),
- (b'RADIO_SELECTED_DME_IDENT_TOGGLE', "Turns on or off the identification sound for the selected DME.", "Shared Cockpit"),
- ]
+ class __Avionics(EventHelper):
+ list = [
+ (
+ b"XPNDR",
+ "Sequentially selects the transponder digits for use with +/-.",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF",
+ "Sequentially selects the ADF tuner digits for use with +/-. Follow by KEY_SELECT_2 for ADF 2.",
+ "Shared Cockpit",
+ ),
+ (b"DME", "Selects the DME for use with +/-", "Shared Cockpit"),
+ (
+ b"COM_RADIO",
+ "Sequentially selects the COM tuner digits for use with +/-. Follow by KEY_SELECT_2 for COM 2.",
+ "All aircraft",
+ ),
+ (
+ b"VOR_OBS",
+ "Sequentially selects the VOR OBS for use with +/-. Follow by KEY_SELECT_2 for VOR 2.",
+ "Shared Cockpit",
+ ),
+ (
+ b"NAV_RADIO",
+ "Sequentially selects the NAV tuner digits for use with +/-. Follow by KEY_SELECT_2 for NAV 2.",
+ "Shared Cockpit",
+ ),
+ (b"COM_RADIO_WHOLE_DEC", "Decrements COM by one MHz", "All aircraft"),
+ (b"COM_RADIO_WHOLE_INC", "Increments COM by one MHz", "All aircraft"),
+ (b"COM_RADIO_FRACT_DEC", "Decrements COM by 25 KHz", "All aircraft"),
+ (b"COM_RADIO_FRACT_INC", "Increments COM by 25 KHz", "All aircraft"),
+ (b"NAV1_RADIO_WHOLE_DEC", "Decrements Nav 1 by one MHz", "Shared Cockpit"),
+ (b"NAV1_RADIO_WHOLE_INC", "Increments Nav 1 by one MHz", "Shared Cockpit"),
+ (b"NAV1_RADIO_FRACT_DEC", "Decrements Nav 1 by 25 KHz", "Shared Cockpit"),
+ (b"NAV1_RADIO_FRACT_INC", "Increments Nav 1 by 25 KHz", "Shared Cockpit"),
+ (b"NAV2_RADIO_WHOLE_DEC", "Decrements Nav 2 by one MHz", "Shared Cockpit"),
+ (b"NAV2_RADIO_WHOLE_INC", "Increments Nav 2 by one MHz", "Shared Cockpit"),
+ (b"NAV2_RADIO_FRACT_DEC", "Decrements Nav 2 by 25 KHz", "Shared Cockpit"),
+ (b"NAV2_RADIO_FRACT_INC", "Increments Nav 2 by 25 KHz", "Shared Cockpit"),
+ (b"ADF_100_INC", "Increments ADF by 100 KHz", "Shared Cockpit"),
+ (b"ADF_10_INC", "Increments ADF by 10 KHz", "Shared Cockpit"),
+ (b"ADF_1_INC", "Increments ADF by 1 KHz", "Shared Cockpit"),
+ (
+ b"XPNDR_1000_INC",
+ "Increments first digit of transponder",
+ "Shared Cockpit",
+ ),
+ (
+ b"XPNDR_100_INC",
+ "Increments second digit of transponder",
+ "Shared Cockpit",
+ ),
+ (
+ b"XPNDR_10_INC",
+ "Increments third digit of transponder",
+ "Shared Cockpit",
+ ),
+ (
+ b"XPNDR_1_INC",
+ "Increments fourth digit of transponder",
+ "Shared Cockpit",
+ ),
+ (b"VOR1_OBI_DEC", "Decrements the VOR 1 OBS setting", "Shared Cockpit"),
+ (b"VOR1_OBI_INC", "Increments the VOR 1 OBS setting", "Shared Cockpit"),
+ (b"VOR2_OBI_DEC", "Decrements the VOR 2 OBS setting", "Shared Cockpit"),
+ (b"VOR2_OBI_INC", "Increments the VOR 2 OBS setting", "Shared Cockpit"),
+ (b"ADF_100_DEC", "Decrements ADF by 100 KHz", "Shared Cockpit"),
+ (b"ADF_10_DEC", "Decrements ADF by 10 KHz", "Shared Cockpit"),
+ (b"ADF_1_DEC", "Decrements ADF by 1 KHz", "Shared Cockpit"),
+ (b"COM_RADIO_SET", "Sets COM frequency (BCD Hz),", "All aircraft"),
+ (b"NAV1_RADIO_SET", "Sets NAV 1 frequency (BCD Hz),", "Shared Cockpit"),
+ (b"NAV2_RADIO_SET", "Sets NAV 2 frequency (BCD Hz),", "Shared Cockpit"),
+ (b"ADF_SET", "Sets ADF frequency (BCD Hz),", "Shared Cockpit"),
+ (b"XPNDR_SET", "Sets transponder code (BCD),", "All aircraft"),
+ (b"VOR1_SET", "Sets OBS 1 (0 to 360),", "Shared Cockpit"),
+ (b"VOR2_SET", "Sets OBS 2 (0 to 360),", "Shared Cockpit"),
+ (b"DME1_TOGGLE", "Sets DME display to Nav 1", "Shared Cockpit"),
+ (b"DME2_TOGGLE", "Sets DME display to Nav 2", "Shared Cockpit"),
+ (b"RADIO_VOR1_IDENT_DISABLE", "Turns NAV 1 ID off", "Shared Cockpit"),
+ (b"RADIO_VOR2_IDENT_DISABLE", "Turns NAV 2 ID off", "Shared Cockpit"),
+ (b"RADIO_DME1_IDENT_DISABLE", "Turns DME 1 ID off", "Shared Cockpit"),
+ (b"RADIO_DME2_IDENT_DISABLE", "Turns DME 2 ID off", "Shared Cockpit"),
+ (b"RADIO_ADF_IDENT_DISABLE", "Turns ADF 1 ID off", "Shared Cockpit"),
+ (b"RADIO_VOR1_IDENT_ENABLE", "Turns NAV 1 ID on", "Shared Cockpit"),
+ (b"RADIO_VOR2_IDENT_ENABLE", "Turns NAV 2 ID on", "Shared Cockpit"),
+ (b"RADIO_DME1_IDENT_ENABLE", "Turns DME 1 ID on", "Shared Cockpit"),
+ (b"RADIO_DME2_IDENT_ENABLE", "Turns DME 2 ID on", "Shared Cockpit"),
+ (b"RADIO_ADF_IDENT_ENABLE", "Turns ADF 1 ID on", "Shared Cockpit"),
+ (b"RADIO_VOR1_IDENT_TOGGLE", "Toggles NAV 1 ID", "Shared Cockpit"),
+ (b"RADIO_VOR2_IDENT_TOGGLE", "Toggles NAV 2 ID", "Shared Cockpit"),
+ (b"RADIO_DME1_IDENT_TOGGLE", "Toggles DME 1 ID", "Shared Cockpit"),
+ (b"RADIO_DME2_IDENT_TOGGLE", "Toggles DME 2 ID", "Shared Cockpit"),
+ (b"RADIO_ADF_IDENT_TOGGLE", "Toggles ADF 1 ID", "Shared Cockpit"),
+ (b"RADIO_VOR1_IDENT_SET", "Sets NAV 1 ID (on/off),", "Shared Cockpit"),
+ (b"RADIO_VOR2_IDENT_SET", "Sets NAV 2 ID (on/off),", "Shared Cockpit"),
+ (b"RADIO_DME1_IDENT_SET", "Sets DME 1 ID (on/off),", "Shared Cockpit"),
+ (b"RADIO_DME2_IDENT_SET", "Sets DME 2 ID (on/off),", "Shared Cockpit"),
+ (b"RADIO_ADF_IDENT_SET", "Sets ADF 1 ID (on/off),", "Shared Cockpit"),
+ (b"ADF_CARD_INC", "Increments ADF card", "Shared Cockpit"),
+ (b"ADF_CARD_DEC", "Decrements ADF card", "Shared Cockpit"),
+ (b"ADF_CARD_SET", "Sets ADF card (0-360),", "Shared Cockpit"),
+ (b"TOGGLE_DME", "Toggles between NAV 1 and NAV 2", "Shared Cockpit"),
+ (b"AVIONICS_MASTER_SET", "Sets the avionics master switch", "All aircraft"),
+ (
+ b"TOGGLE_AVIONICS_MASTER",
+ "Toggles the avionics master switch",
+ "All aircraft",
+ ),
+ (
+ b"COM_STBY_RADIO_SET",
+ "Sets COM 1 standby frequency (BCD Hz),",
+ "All aircraft",
+ ),
+ (
+ b"COM_STBY_RADIO_SWAP",
+ "Swaps COM 1 frequency with standby",
+ "All aircraft",
+ ),
+ (
+ b"COM_RADIO_FRACT_DEC_CARRY",
+ "Decrement COM 1 frequency by 25 KHz, and carry when digit wraps",
+ "All aircraft",
+ ),
+ (
+ b"COM_RADIO_FRACT_INC_CARRY",
+ "Increment COM 1 frequency by 25 KHz, and carry when digit wraps",
+ "All aircraft",
+ ),
+ (
+ b"COM2_RADIO_WHOLE_DEC",
+ "Decrement COM 2 frequency by 1 MHz, with no carry when digit wraps",
+ "All aircraft",
+ ),
+ (
+ b"COM2_RADIO_WHOLE_INC",
+ "Increment COM 2 frequency by 1 MHz, with no carry when digit wraps",
+ "All aircraft",
+ ),
+ (
+ b"COM2_RADIO_FRACT_DEC",
+ "Decrement COM 2 frequency by 25 KHz, with no carry when digit wraps",
+ "All aircraft",
+ ),
+ (
+ b"COM2_RADIO_FRACT_DEC_CARRY",
+ "Decrement COM 2 frequency by 25 KHz, and carry when digit wraps",
+ "All aircraft",
+ ),
+ (
+ b"COM2_RADIO_FRACT_INC",
+ "Increment COM 2 frequency by 25 KHz, with no carry when digit wraps",
+ "All aircraft",
+ ),
+ (
+ b"COM2_RADIO_FRACT_INC_CARRY",
+ "Increment COM 2 frequency by 25 KHz, and carry when digit wraps",
+ "All aircraft",
+ ),
+ (b"COM2_RADIO_SET", "Sets COM 2 frequency (BCD Hz),", "All aircraft"),
+ (
+ b"COM2_STBY_RADIO_SET",
+ "Sets COM 2 standby frequency (BCD Hz),",
+ "All aircraft",
+ ),
+ (b"COM2_RADIO_SWAP", "Swaps COM 2 frequency with standby", "All aircraft"),
+ (
+ b"NAV1_RADIO_FRACT_DEC_CARRY",
+ "Decrement NAV 1 frequency by 50 KHz, and carry when digit wraps",
+ "Shared Cockpit",
+ ),
+ (
+ b"NAV1_RADIO_FRACT_INC_CARRY",
+ "Increment NAV 1 frequency by 50 KHz, and carry when digit wraps",
+ "Shared Cockpit",
+ ),
+ (
+ b"NAV1_STBY_SET",
+ "Sets NAV 1 standby frequency (BCD Hz),",
+ "Shared Cockpit",
+ ),
+ (
+ b"NAV1_RADIO_SWAP",
+ "Swaps NAV 1 frequency with standby",
+ "Shared Cockpit",
+ ),
+ (
+ b"NAV2_RADIO_FRACT_DEC_CARRY",
+ "Decrement NAV 2 frequency by 50 KHz, and carry when digit wraps",
+ "Shared Cockpit",
+ ),
+ (
+ b"NAV2_RADIO_FRACT_INC_CARRY",
+ "Increment NAV 2 frequency by 50 KHz, and carry when digit wraps",
+ "Shared Cockpit",
+ ),
+ (
+ b"NAV2_STBY_SET",
+ "Sets NAV 2 standby frequency (BCD Hz),",
+ "Shared Cockpit",
+ ),
+ (
+ b"NAV2_RADIO_SWAP",
+ "Swaps NAV 2 frequency with standby",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF1_RADIO_TENTHS_DEC",
+ "Decrements ADF 1 by 0.1 KHz.",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF1_RADIO_TENTHS_INC",
+ "Increments ADF 1 by 0.1 KHz.",
+ "Shared Cockpit",
+ ),
+ (
+ b"XPNDR_1000_DEC",
+ "Decrements first digit of transponder",
+ "Shared Cockpit",
+ ),
+ (
+ b"XPNDR_100_DEC",
+ "Decrements second digit of transponder",
+ "Shared Cockpit",
+ ),
+ (
+ b"XPNDR_10_DEC",
+ "Decrements third digit of transponder",
+ "Shared Cockpit",
+ ),
+ (
+ b"XPNDR_1_DEC",
+ "Decrements fourth digit of transponder",
+ "Shared Cockpit",
+ ),
+ (
+ b"XPNDR_DEC_CARRY",
+ "Decrements fourth digit of transponder, and with carry.",
+ "Shared Cockpit",
+ ),
+ (
+ b"XPNDR_INC_CARRY",
+ "Increments fourth digit of transponder, and with carry.",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF_FRACT_DEC_CARRY",
+ "Decrements ADF 1 frequency by 0.1 KHz, with carry",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF_FRACT_INC_CARRY",
+ "Increments ADF 1 frequency by 0.1 KHz, with carry",
+ "Shared Cockpit",
+ ),
+ (b"COM1_TRANSMIT_SELECT", "Selects COM 1 to transmit", "All aircraft"),
+ (b"COM2_TRANSMIT_SELECT", "Selects COM 2 to transmit", "All aircraft"),
+ (
+ b"COM_RECEIVE_ALL_TOGGLE",
+ "Toggles all COM radios to receive on",
+ "All aircraft",
+ ),
+ (
+ b"COM_RECEIVE_ALL_SET",
+ "Sets whether to receive on all COM radios (1,0),",
+ "All aircraft",
+ ),
+ (
+ b"MARKER_SOUND_TOGGLE",
+ "Toggles marker beacon sound on/off",
+ "Shared Cockpit",
+ ),
+ (b"Unsupported", "Sets marker beacon sound (1, 0),", "Shared Cockpit"),
+ (b"ADF_COMPLETE_SET", "Sets ADF 1 frequency (BCD Hz),", "Shared Cockpit"),
+ (
+ b"ADF1_WHOLE_INC",
+ "Increments ADF 1 by 1 KHz, with carry as digits wrap.",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF1_WHOLE_DEC",
+ "Decrements ADF 1 by 1 KHz, with carry as digits wrap.",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF2_100_INC",
+ "Increments the ADF 2 frequency 100 digit, with wrapping",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF2_10_INC",
+ "Increments the ADF 2 frequency 10 digit, with wrapping",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF2_1_INC",
+ "Increments the ADF 2 frequency 1 digit, with wrapping",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF2_RADIO_TENTHS_INC",
+ "Increments ADF 2 frequency 1/10 digit, with wrapping",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF2_100_DEC",
+ "Decrements the ADF 2 frequency 100 digit, with wrapping",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF2_10_DEC",
+ "Decrements the ADF 2 frequency 10 digit, with wrapping",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF2_1_DEC",
+ "Decrements the ADF 2 frequency 1 digit, with wrapping",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF2_RADIO_TENTHS_DEC",
+ "Decrements ADF 2 frequency 1/10 digit, with wrapping",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF2_WHOLE_INC",
+ "Increments ADF 2 by 1 KHz, with carry as digits wrap.",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF2_WHOLE_DEC",
+ "Decrements ADF 2 by 1 KHz, with carry as digits wrap.",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF2_FRACT_DEC_CARRY",
+ "Decrements ADF 2 frequency by 0.1 KHz, with carry",
+ "Shared Cockpit",
+ ),
+ (
+ b"ADF2_FRACT_INC_CARRY",
+ "Increments ADF 2 frequency by 0.1 KHz, with carry",
+ "Shared Cockpit",
+ ),
+ (b"ADF2_COMPLETE_SET", "Sets ADF 1 frequency (BCD Hz),", "Shared Cockpit"),
+ (b"RADIO_ADF2_IDENT_DISABLE", "Turns ADF 2 ID off", "Shared Cockpit"),
+ (b"RADIO_ADF2_IDENT_ENABLE", "Turns ADF 2 ID on", "Shared Cockpit"),
+ (b"RADIO_ADF2_IDENT_TOGGLE", "Toggles ADF 2 ID", "Shared Cockpit"),
+ (b"RADIO_ADF2_IDENT_SET", "Sets ADF 2 ID on/off (1,0),", "Shared Cockpit"),
+ (
+ b"FREQUENCY_SWAP",
+ "Swaps frequency with standby on whichever NAV or COM radio is selected.",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_GPS_DRIVES_NAV1",
+ "Toggles between GPS and NAV 1 driving NAV 1 OBS display (and AP),",
+ "Shared Cockpit",
+ ),
+ (b"GPS_POWER_BUTTON", "Toggles power button", "Shared Cockpit"),
+ (b"GPS_NEAREST_BUTTON", "Selects Nearest Airport Page", "Shared Cockpit"),
+ (
+ b"GPS_OBS_BUTTON",
+ "Toggles automatic sequencing of waypoints",
+ "Shared Cockpit",
+ ),
+ (b"GPS_MSG_BUTTON", "Toggles the Message Page", "Shared Cockpit"),
+ (
+ b"GPS_MSG_BUTTON_DOWN",
+ "Triggers the pressing of the message button.",
+ "Shared Cockpit",
+ ),
+ (
+ b"GPS_MSG_BUTTON_UP",
+ "Triggers the release of the message button",
+ "Shared Cockpit",
+ ),
+ (
+ b"GPS_FLIGHTPLAN_BUTTON",
+ "Displays the programmed flightplan.",
+ "Shared Cockpit",
+ ),
+ (
+ b"GPS_TERRAIN_BUTTON",
+ "Displays terrain information on default display",
+ "Shared Cockpit",
+ ),
+ (
+ b"GPS_PROCEDURE_BUTTON",
+ "Displays the approach procedure page.",
+ "Shared Cockpit",
+ ),
+ (b"GPS_ZOOMIN_BUTTON", "Zooms in default display", "Shared Cockpit"),
+ (b"GPS_ZOOMOUT_BUTTON", "Zooms out default display", "Shared Cockpit"),
+ (
+ b"GPS_DIRECTTO_BUTTON",
+ 'Brings up the "Direct To" page',
+ "Shared Cockpit",
+ ),
+ (
+ b"GPS_MENU_BUTTON",
+ "Brings up page to select active legs in a flightplan.",
+ "Shared Cockpit",
+ ),
+ (b"GPS_CLEAR_BUTTON", "Clears entered data on a page", "Shared Cockpit"),
+ (b"GPS_CLEAR_ALL_BUTTON", "Clears all data immediately", "Shared Cockpit"),
+ (
+ b"GPS_CLEAR_BUTTON_DOWN",
+ "Triggers the pressing of the Clear button",
+ "Shared Cockpit",
+ ),
+ (
+ b"GPS_CLEAR_BUTTON_UP",
+ "Triggers the release of the Clear button.",
+ "Shared Cockpit",
+ ),
+ (b"GPS_ENTER_BUTTON", "Approves entered data.", "Shared Cockpit"),
+ (b"GPS_CURSOR_BUTTON", "Selects GPS cursor", "Shared Cockpit"),
+ (b"GPS_GROUP_KNOB_INC", "Increments cursor", "Shared Cockpit"),
+ (b"GPS_GROUP_KNOB_DEC", "Decrements cursor", "Shared Cockpit"),
+ (b"GPS_PAGE_KNOB_INC", "Increments through pages", "Shared Cockpit"),
+ (b"GPS_PAGE_KNOB_DEC", "Decrements through pages", "Shared Cockpit"),
+ (
+ b"DME_SELECT",
+ "Selects one of the two DME systems (1,2),.",
+ "Shared Cockpit",
+ ),
+ (
+ b"RADIO_SELECTED_DME_IDENT_ENABLE",
+ "Turns on the identification sound for the selected DME.",
+ "Shared Cockpit",
+ ),
+ (
+ b"RADIO_SELECTED_DME_IDENT_DISABLE",
+ "Turns off the identification sound for the selected DME.",
+ "Shared Cockpit",
+ ),
+ (
+ b"RADIO_SELECTED_DME_IDENT_SET",
+ "Sets the DME identification sound to the given filename.",
+ "Shared Cockpit",
+ ),
+ (
+ b"RADIO_SELECTED_DME_IDENT_TOGGLE",
+ "Turns on or off the identification sound for the selected DME.",
+ "Shared Cockpit",
+ ),
+ ]
- class __Instruments(EventHelper):
- list = [
- (b'EGT', "Selects EGT bug for +/-", "Shared Cockpit"),
- (b'EGT_INC', "Increments EGT bugs", "Shared Cockpit"),
- (b'EGT_DEC', "Decrements EGT bugs", "Shared Cockpit"),
- (b'EGT_SET', "Sets EGT bugs (0 to 32767),", "Shared Cockpit"),
- (b'BAROMETRIC', "Syncs altimeter setting to sea level pressure, or 29.92 if above 18000 feet", "Shared Cockpit"),
- (b'GYRO_DRIFT_INC', "Increments heading indicator", "Shared Cockpit"),
- (b'GYRO_DRIFT_DEC', "Decrements heading indicator", "Shared Cockpit"),
- (b'KOHLSMAN_INC', "Increments altimeter setting", "Shared Cockpit"),
- (b'KOHLSMAN_DEC', "Decrements altimeter setting", "Shared Cockpit"),
- (b'KOHLSMAN_SET', "Sets altimeter setting (Millibars * 16),", "Shared Cockpit"),
- (b'TRUE_AIRSPEED_CAL_INC', "Increments airspeed indicators true airspeed reference card", "Shared Cockpit"),
- (b'TRUE_AIRSPEED_CAL_DEC', "Decrements airspeed indicators true airspeed reference card", "Shared Cockpit"),
- (b'TRUE_AIRSPEED_CAL_SET', "Sets airspeed indicators true airspeed reference card (degrees, where 0 is standard sea level conditions),", "Shared Cockpit"),
- (b'EGT1_INC', "Increments EGT bug 1", "Shared Cockpit"),
- (b'EGT1_DEC', "Decrements EGT bug 1", "Shared Cockpit"),
- (b'EGT1_SET', "Sets EGT bug 1 (0 to 32767),", "Shared Cockpit"),
- (b'EGT2_INC', "Increments EGT bug 2", "Shared Cockpit"),
- (b'EGT2_DEC', "Decrements EGT bug 2", "Shared Cockpit"),
- (b'EGT2_SET', "Sets EGT bug 2 (0 to 32767),", "Shared Cockpit"),
- (b'EGT3_INC', "Increments EGT bug 3", "Shared Cockpit"),
- (b'EGT3_DEC', "Decrements EGT bug 3", "Shared Cockpit"),
- (b'EGT3_SET', "Sets EGT bug 3 (0 to 32767),", "Shared Cockpit"),
- (b'EGT4_INC', "Increments EGT bug 4", "Shared Cockpit"),
- (b'EGT4_DEC', "Decrements EGT bug 4", "Shared Cockpit"),
- (b'EGT4_SET', "Sets EGT bug 4 (0 to 32767),", "Shared Cockpit"),
- (b'ATTITUDE_BARS_POSITION_UP', "Increments attitude indicator pitch reference bars", "Shared Cockpit"),
- (b'ATTITUDE_BARS_POSITION_DOWN', "Decrements attitude indicator pitch reference bars", "Shared Cockpit"),
- (b'ATTITUDE_CAGE_BUTTON', "Cages attitude indicator at 0 pitch and bank", "Shared Cockpit"),
- (b'RESET_G_FORCE_INDICATOR', "Resets max/min indicated G force to 1.0.", "Shared Cockpit"),
- (b'RESET_MAX_RPM_INDICATOR', "Reset max indicated engine rpm to 0.", "Shared Cockpit"),
- (b'HEADING_GYRO_SET', "Sets heading indicator to 0 drift error.", "Shared Cockpit"),
- (b'GYRO_DRIFT_SET', "Sets heading indicator drift angle (degrees),.", "Shared Cockpit"),
- ]
+ class __Instruments(EventHelper):
+ list = [
+ (b"EGT", "Selects EGT bug for +/-", "Shared Cockpit"),
+ (b"EGT_INC", "Increments EGT bugs", "Shared Cockpit"),
+ (b"EGT_DEC", "Decrements EGT bugs", "Shared Cockpit"),
+ (b"EGT_SET", "Sets EGT bugs (0 to 32767),", "Shared Cockpit"),
+ (
+ b"BAROMETRIC",
+ "Syncs altimeter setting to sea level pressure, or 29.92 if above 18000 feet",
+ "Shared Cockpit",
+ ),
+ (b"GYRO_DRIFT_INC", "Increments heading indicator", "Shared Cockpit"),
+ (b"GYRO_DRIFT_DEC", "Decrements heading indicator", "Shared Cockpit"),
+ (b"KOHLSMAN_INC", "Increments altimeter setting", "Shared Cockpit"),
+ (b"KOHLSMAN_DEC", "Decrements altimeter setting", "Shared Cockpit"),
+ (
+ b"KOHLSMAN_SET",
+ "Sets altimeter setting (Millibars * 16),",
+ "Shared Cockpit",
+ ),
+ (
+ b"TRUE_AIRSPEED_CAL_INC",
+ "Increments airspeed indicators true airspeed reference card",
+ "Shared Cockpit",
+ ),
+ (
+ b"TRUE_AIRSPEED_CAL_DEC",
+ "Decrements airspeed indicators true airspeed reference card",
+ "Shared Cockpit",
+ ),
+ (
+ b"TRUE_AIRSPEED_CAL_SET",
+ "Sets airspeed indicators true airspeed reference card (degrees, where 0 is standard sea level conditions),",
+ "Shared Cockpit",
+ ),
+ (b"EGT1_INC", "Increments EGT bug 1", "Shared Cockpit"),
+ (b"EGT1_DEC", "Decrements EGT bug 1", "Shared Cockpit"),
+ (b"EGT1_SET", "Sets EGT bug 1 (0 to 32767),", "Shared Cockpit"),
+ (b"EGT2_INC", "Increments EGT bug 2", "Shared Cockpit"),
+ (b"EGT2_DEC", "Decrements EGT bug 2", "Shared Cockpit"),
+ (b"EGT2_SET", "Sets EGT bug 2 (0 to 32767),", "Shared Cockpit"),
+ (b"EGT3_INC", "Increments EGT bug 3", "Shared Cockpit"),
+ (b"EGT3_DEC", "Decrements EGT bug 3", "Shared Cockpit"),
+ (b"EGT3_SET", "Sets EGT bug 3 (0 to 32767),", "Shared Cockpit"),
+ (b"EGT4_INC", "Increments EGT bug 4", "Shared Cockpit"),
+ (b"EGT4_DEC", "Decrements EGT bug 4", "Shared Cockpit"),
+ (b"EGT4_SET", "Sets EGT bug 4 (0 to 32767),", "Shared Cockpit"),
+ (
+ b"ATTITUDE_BARS_POSITION_UP",
+ "Increments attitude indicator pitch reference bars",
+ "Shared Cockpit",
+ ),
+ (
+ b"ATTITUDE_BARS_POSITION_DOWN",
+ "Decrements attitude indicator pitch reference bars",
+ "Shared Cockpit",
+ ),
+ (
+ b"ATTITUDE_CAGE_BUTTON",
+ "Cages attitude indicator at 0 pitch and bank",
+ "Shared Cockpit",
+ ),
+ (
+ b"RESET_G_FORCE_INDICATOR",
+ "Resets max/min indicated G force to 1.0.",
+ "Shared Cockpit",
+ ),
+ (
+ b"RESET_MAX_RPM_INDICATOR",
+ "Reset max indicated engine rpm to 0.",
+ "Shared Cockpit",
+ ),
+ (
+ b"HEADING_GYRO_SET",
+ "Sets heading indicator to 0 drift error.",
+ "Shared Cockpit",
+ ),
+ (
+ b"GYRO_DRIFT_SET",
+ "Sets heading indicator drift angle (degrees),.",
+ "Shared Cockpit",
+ ),
+ ]
- class __Lights(EventHelper):
- list = [
- (b'STROBES_TOGGLE', "Toggle strobe lights ", "All aircraft"),
- (b'ALL_LIGHTS_TOGGLE', "Toggle all lights", "Shared Cockpit"),
- (b'PANEL_LIGHTS_TOGGLE', "Toggle panel lights", "All aircraft"),
- (b'LANDING_LIGHTS_TOGGLE', "Toggle landing lights", "All aircraft"),
- (b'LANDING_LIGHT_UP', "Rotate landing light up", "Shared Cockpit"),
- (b'LANDING_LIGHT_DOWN', "Rotate landing light down", "Shared Cockpit"),
- (b'LANDING_LIGHT_LEFT', "Rotate landing light left", "Shared Cockpit"),
- (b'LANDING_LIGHT_RIGHT', "Rotate landing light right", "Shared Cockpit"),
- (b'LANDING_LIGHT_HOME', "Return landing light to default position", "Shared Cockpit"),
- (b'STROBES_ON', "Turn strobe lights on", "All aircraft"),
- (b'STROBES_OFF', "Turn strobe light off", "All aircraft"),
- (b'STROBES_SET', "Set strobe lights on/off (1,0),", "All aircraft"),
- (b'PANEL_LIGHTS_ON', "Turn panel lights on", "All aircraft"),
- (b'PANEL_LIGHTS_OFF', "Turn panel lights off", "All aircraft"),
- (b'PANEL_LIGHTS_SET', "Set panel lights on/off (1,0),", "All aircraft"),
- (b'LANDING_LIGHTS_ON', "Turn landing lights on", "All aircraft"),
- (b'LANDING_LIGHTS_OFF', "Turn landing lights off", "All aircraft"),
- (b'LANDING_LIGHTS_SET', "Set landing lights on/off (1,0),", "All aircraft"),
- (b'TOGGLE_BEACON_LIGHTS', "Toggle beacon lights", "All aircraft"),
- (b'TOGGLE_TAXI_LIGHTS', "Toggle taxi lights", "All aircraft"),
- (b'TOGGLE_LOGO_LIGHTS', "Toggle logo lights", "All aircraft"),
- (b'TOGGLE_RECOGNITION_LIGHTS', "Toggle recognition lights", "All aircraft"),
- (b'TOGGLE_WING_LIGHTS', "Toggle wing lights", "All aircraft"),
- (b'TOGGLE_NAV_LIGHTS', "Toggle navigation lights", "All aircraft"),
- (b'TOGGLE_CABIN_LIGHTS', "Toggle cockpit/cabin lights", "All aircraft"),
- ]
+ class __Lights(EventHelper):
+ list = [
+ (b"STROBES_TOGGLE", "Toggle strobe lights ", "All aircraft"),
+ (b"ALL_LIGHTS_TOGGLE", "Toggle all lights", "Shared Cockpit"),
+ (b"PANEL_LIGHTS_TOGGLE", "Toggle panel lights", "All aircraft"),
+ (b"LANDING_LIGHTS_TOGGLE", "Toggle landing lights", "All aircraft"),
+ (b"LANDING_LIGHT_UP", "Rotate landing light up", "Shared Cockpit"),
+ (b"LANDING_LIGHT_DOWN", "Rotate landing light down", "Shared Cockpit"),
+ (b"LANDING_LIGHT_LEFT", "Rotate landing light left", "Shared Cockpit"),
+ (b"LANDING_LIGHT_RIGHT", "Rotate landing light right", "Shared Cockpit"),
+ (
+ b"LANDING_LIGHT_HOME",
+ "Return landing light to default position",
+ "Shared Cockpit",
+ ),
+ (b"STROBES_ON", "Turn strobe lights on", "All aircraft"),
+ (b"STROBES_OFF", "Turn strobe light off", "All aircraft"),
+ (b"STROBES_SET", "Set strobe lights on/off (1,0),", "All aircraft"),
+ (b"PANEL_LIGHTS_ON", "Turn panel lights on", "All aircraft"),
+ (b"PANEL_LIGHTS_OFF", "Turn panel lights off", "All aircraft"),
+ (b"PANEL_LIGHTS_SET", "Set panel lights on/off (1,0),", "All aircraft"),
+ (b"LANDING_LIGHTS_ON", "Turn landing lights on", "All aircraft"),
+ (b"LANDING_LIGHTS_OFF", "Turn landing lights off", "All aircraft"),
+ (b"LANDING_LIGHTS_SET", "Set landing lights on/off (1,0),", "All aircraft"),
+ (b"TOGGLE_BEACON_LIGHTS", "Toggle beacon lights", "All aircraft"),
+ (b"TOGGLE_TAXI_LIGHTS", "Toggle taxi lights", "All aircraft"),
+ (b"TOGGLE_LOGO_LIGHTS", "Toggle logo lights", "All aircraft"),
+ (b"TOGGLE_RECOGNITION_LIGHTS", "Toggle recognition lights", "All aircraft"),
+ (b"TOGGLE_WING_LIGHTS", "Toggle wing lights", "All aircraft"),
+ (b"TOGGLE_NAV_LIGHTS", "Toggle navigation lights", "All aircraft"),
+ (b"TOGGLE_CABIN_LIGHTS", "Toggle cockpit/cabin lights", "All aircraft"),
+ ]
- class __Failures(EventHelper):
- list = [
- (b'TOGGLE_VACUUM_FAILURE', "Toggle vacuum system failure", "Shared Cockpit"),
- (b'TOGGLE_ELECTRICAL_FAILURE', "Toggle electrical system failure", "Shared Cockpit"),
- (b'TOGGLE_PITOT_BLOCKAGE', "Toggles blocked pitot tube", "Shared Cockpit"),
- (b'TOGGLE_STATIC_PORT_BLOCKAGE', " Toggles blocked static port", "Shared Cockpit"),
- (b'TOGGLE_HYDRAULIC_FAILURE', "Toggles hydraulic system failure", "Shared Cockpit"),
- (b'TOGGLE_TOTAL_BRAKE_FAILURE', "Toggles brake failure (both),", "Shared Cockpit"),
- (b'TOGGLE_LEFT_BRAKE_FAILURE', "Toggles left brake failure", "Shared Cockpit"),
- (b'TOGGLE_RIGHT_BRAKE_FAILURE', "Toggles right brake failure", "Shared Cockpit"),
- (b'TOGGLE_ENGINE1_FAILURE', "Toggle engine 1 failure", "Shared Cockpit"),
- (b'TOGGLE_ENGINE2_FAILURE', "Toggle engine 2 failure", "Shared Cockpit"),
- (b'TOGGLE_ENGINE3_FAILURE', "Toggle engine 3 failure", "Shared Cockpit"),
- (b'TOGGLE_ENGINE4_FAILURE', "Toggle engine 4 failure", "Shared Cockpit"),
- ]
+ class __Failures(EventHelper):
+ list = [
+ (
+ b"TOGGLE_VACUUM_FAILURE",
+ "Toggle vacuum system failure",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_ELECTRICAL_FAILURE",
+ "Toggle electrical system failure",
+ "Shared Cockpit",
+ ),
+ (b"TOGGLE_PITOT_BLOCKAGE", "Toggles blocked pitot tube", "Shared Cockpit"),
+ (
+ b"TOGGLE_STATIC_PORT_BLOCKAGE",
+ " Toggles blocked static port",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_HYDRAULIC_FAILURE",
+ "Toggles hydraulic system failure",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_TOTAL_BRAKE_FAILURE",
+ "Toggles brake failure (both),",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_LEFT_BRAKE_FAILURE",
+ "Toggles left brake failure",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_RIGHT_BRAKE_FAILURE",
+ "Toggles right brake failure",
+ "Shared Cockpit",
+ ),
+ (b"TOGGLE_ENGINE1_FAILURE", "Toggle engine 1 failure", "Shared Cockpit"),
+ (b"TOGGLE_ENGINE2_FAILURE", "Toggle engine 2 failure", "Shared Cockpit"),
+ (b"TOGGLE_ENGINE3_FAILURE", "Toggle engine 3 failure", "Shared Cockpit"),
+ (b"TOGGLE_ENGINE4_FAILURE", "Toggle engine 4 failure", "Shared Cockpit"),
+ ]
- class __Miscellaneous_Systems(EventHelper):
- list = [
- (b'SMOKE_TOGGLE', "Toggle smoke system switch", "All aircraft"),
- (b'GEAR_TOGGLE', "Toggle gear handle", "All aircraft"),
- (b'BRAKES', "Increment brake pressure ", "Shared Cockpit"),
- (b'GEAR_SET', "Sets gear handle position up/down (0,1),", "All aircraft"),
- (b'BRAKES_LEFT', "Increments left brake pressure", "Shared Cockpit"),
- (b'BRAKES_RIGHT', "Increments right brake pressure", "Shared Cockpit"),
- (b'PARKING_BRAKES', "Toggles parking brake on/off", "Shared Cockpit"),
- (b'GEAR_PUMP', "Increments emergency gear extension", "Shared Cockpit"),
- (b'PITOT_HEAT_TOGGLE', "Toggles pitot heat switch", "All aircraft"),
- (b'SMOKE_ON', "Turns smoke system on", "All aircraft"),
- (b'SMOKE_OFF', "Turns smoke system off", "All aircraft"),
- (b'SMOKE_SET', "Sets smoke system on/off (1,0),", "All aircraft"),
- (b'PITOT_HEAT_ON', "Turns pitot heat switch on", "Shared Cockpit"),
- (b'PITOT_HEAT_OFF', "Turns pitot heat switch off", "Shared Cockpit"),
- (b'PITOT_HEAT_SET', "Sets pitot heat switch on/off (1,0),", "Shared Cockpit"),
- (b'GEAR_UP', "Sets gear handle in UP position", "All aircraft"),
- (b'GEAR_DOWN', "Sets gear handle in DOWN position", "All aircraft"),
- (b'TOGGLE_MASTER_BATTERY', "Toggles main battery switch", "All aircraft"),
- (b'TOGGLE_MASTER_ALTERNATOR', "Toggles main alternator/generator switch", "All aircraft"),
- (b'TOGGLE_ELECTRIC_VACUUM_PUMP', "Toggles backup electric vacuum pump", "Shared Cockpit"),
- (b'TOGGLE_ALTERNATE_STATIC', "Toggles alternate static pressure port", "All aircraft"),
- (b'DECREASE_DECISION_HEIGHT', "Decrements decision height reference", "Shared Cockpit"),
- (b'INCREASE_DECISION_HEIGHT', "Increments decision height reference", "Shared Cockpit"),
- (b'TOGGLE_STRUCTURAL_DEICE', "Toggles structural deice switch", "Shared Cockpit"),
- (b'TOGGLE_PROPELLER_DEICE', "Toggles propeller deice switch", "Shared Cockpit"),
- (b'TOGGLE_ALTERNATOR1', "Toggles alternator/generator 1 switch", "All aircraft"),
- (b'TOGGLE_ALTERNATOR2', "Toggles alternator/generator 2 switch", "All aircraft"),
- (b'TOGGLE_ALTERNATOR3', "Toggles alternator/generator 3 switch", "All aircraft"),
- (b'TOGGLE_ALTERNATOR4', "Toggles alternator/generator 4 switch", "All aircraft"),
- (b'TOGGLE_MASTER_BATTERY_ALTERNATOR', "Toggles master battery and alternator switch", "Shared Cockpit"),
- (b'AXIS_LEFT_BRAKE_SET', "Sets left brake position from axis controller (e.g. joystick),. -16383 (0 brakes) to +16383 (max brakes)", "Shared Cockpit"),
- (b'AXIS_RIGHT_BRAKE_SET', "Sets right brake position from axis controller (e.g. joystick),. -16383 (0 brakes) to +16383 (max brakes)", "Shared Cockpit"),
- (b'TOGGLE_AIRCRAFT_EXIT', "Toggles primary door open/close. Follow by KEY_SELECT_2, etc for subsequent doors.", "Shared Cockpit"),
- (b'TOGGLE_WING_FOLD', "Toggles wing folding", "Shared Cockpit"),
- (b'SET_WING_FOLD', '''Sets the wings into the folded position suitable for storage, typically on a carrier. Takes a value:
+ class __Miscellaneous_Systems(EventHelper):
+ list = [
+ (b"SMOKE_TOGGLE", "Toggle smoke system switch", "All aircraft"),
+ (b"GEAR_TOGGLE", "Toggle gear handle", "All aircraft"),
+ (b"BRAKES", "Increment brake pressure ", "Shared Cockpit"),
+ (b"GEAR_SET", "Sets gear handle position up/down (0,1),", "All aircraft"),
+ (b"BRAKES_LEFT", "Increments left brake pressure", "Shared Cockpit"),
+ (b"BRAKES_RIGHT", "Increments right brake pressure", "Shared Cockpit"),
+ (b"PARKING_BRAKES", "Toggles parking brake on/off", "Shared Cockpit"),
+ (b"GEAR_PUMP", "Increments emergency gear extension", "Shared Cockpit"),
+ (b"PITOT_HEAT_TOGGLE", "Toggles pitot heat switch", "All aircraft"),
+ (b"SMOKE_ON", "Turns smoke system on", "All aircraft"),
+ (b"SMOKE_OFF", "Turns smoke system off", "All aircraft"),
+ (b"SMOKE_SET", "Sets smoke system on/off (1,0),", "All aircraft"),
+ (b"PITOT_HEAT_ON", "Turns pitot heat switch on", "Shared Cockpit"),
+ (b"PITOT_HEAT_OFF", "Turns pitot heat switch off", "Shared Cockpit"),
+ (
+ b"PITOT_HEAT_SET",
+ "Sets pitot heat switch on/off (1,0),",
+ "Shared Cockpit",
+ ),
+ (b"GEAR_UP", "Sets gear handle in UP position", "All aircraft"),
+ (b"GEAR_DOWN", "Sets gear handle in DOWN position", "All aircraft"),
+ (b"TOGGLE_MASTER_BATTERY", "Toggles main battery switch", "All aircraft"),
+ (
+ b"TOGGLE_MASTER_ALTERNATOR",
+ "Toggles main alternator/generator switch",
+ "All aircraft",
+ ),
+ (
+ b"TOGGLE_ELECTRIC_VACUUM_PUMP",
+ "Toggles backup electric vacuum pump",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_ALTERNATE_STATIC",
+ "Toggles alternate static pressure port",
+ "All aircraft",
+ ),
+ (
+ b"DECREASE_DECISION_HEIGHT",
+ "Decrements decision height reference",
+ "Shared Cockpit",
+ ),
+ (
+ b"INCREASE_DECISION_HEIGHT",
+ "Increments decision height reference",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_STRUCTURAL_DEICE",
+ "Toggles structural deice switch",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_PROPELLER_DEICE",
+ "Toggles propeller deice switch",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_ALTERNATOR1",
+ "Toggles alternator/generator 1 switch",
+ "All aircraft",
+ ),
+ (
+ b"TOGGLE_ALTERNATOR2",
+ "Toggles alternator/generator 2 switch",
+ "All aircraft",
+ ),
+ (
+ b"TOGGLE_ALTERNATOR3",
+ "Toggles alternator/generator 3 switch",
+ "All aircraft",
+ ),
+ (
+ b"TOGGLE_ALTERNATOR4",
+ "Toggles alternator/generator 4 switch",
+ "All aircraft",
+ ),
+ (
+ b"TOGGLE_MASTER_BATTERY_ALTERNATOR",
+ "Toggles master battery and alternator switch",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_LEFT_BRAKE_SET",
+ "Sets left brake position from axis controller (e.g. joystick),. -16383 (0 brakes) to +16383 (max brakes)",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_RIGHT_BRAKE_SET",
+ "Sets right brake position from axis controller (e.g. joystick),. -16383 (0 brakes) to +16383 (max brakes)",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_AIRCRAFT_EXIT",
+ "Toggles primary door open/close. Follow by KEY_SELECT_2, etc for subsequent doors.",
+ "Shared Cockpit",
+ ),
+ (b"TOGGLE_WING_FOLD", "Toggles wing folding", "Shared Cockpit"),
+ (
+ b"SET_WING_FOLD",
+ """Sets the wings into the folded position suitable for storage, typically on a carrier. Takes a value:
1 - fold wings,
- 0 - unfold wings''', "Shared Cockpit"),
- (b'TOGGLE_TAIL_HOOK_HANDLE', "Toggles tail hook", "Shared Cockpit"),
- (b'SET_TAIL_HOOK_HANDLE', '''Sets the tail hook handle. Takes a value:
+ 0 - unfold wings""",
+ "Shared Cockpit",
+ ),
+ (b"TOGGLE_TAIL_HOOK_HANDLE", "Toggles tail hook", "Shared Cockpit"),
+ (
+ b"SET_TAIL_HOOK_HANDLE",
+ """Sets the tail hook handle. Takes a value:
1 - set tail hook,
- 0 - retract tail hook''', "Shared Cockpit"),
- (b'TOGGLE_WATER_RUDDER', "Toggles water rudders", "Shared Cockpit"),
- (b'TOGGLE_PUSHBACK', "Toggles pushback.", "Shared Cockpit"),
- (b'KEY_TUG_HEADING', "Triggers tug and sets the desired heading. The units are a 32 bit integer (0 to 4294967295), which represent 0 to 360 degrees. To set a 45 degree angle, for example, set the value to 4294967295 / 8.", "Shared Cockpit"),
- (b'KEY_TUG_SPEED', "Triggers tug, and sets desired speed, in feet per second. The speed can be both positive (forward movement), and negative (backward movement).", "Shared Cockpit"),
- (b'TUG_DISABLE', "Disables tug", "Shared Cockpit"),
- (b'TOGGLE_MASTER_IGNITION_SWITCH', "Toggles master ignition switch", "Shared Cockpit"),
- (b'TOGGLE_TAILWHEEL_LOCK', "Toggles tail wheel lock", "Shared Cockpit"),
- (b'ADD_FUEL_QUANTITY', "Adds fuel to the aircraft, 25% of capacity by default. 0 to 65535 (max fuel), can be passed.", "Shared Cockpit"),
- (b'TOW_PLANE_RELEASE', "Release a towed aircraft, usually a glider.", "Shared Cockpit"),
- (b'TOW_PLANE_REQUEST', "Request a tow plane. The user aircraft must be tow-able, stationary, on the ground and not already attached for this to succeed.", "Shared Cockpit"),
- (b'RELEASE_DROPPABLE_OBJECTS', "Release one droppable object. Multiple key events will release multiple objects.", "Shared Cockpit"),
- (b'RETRACT_FLOAT_SWITCH_DEC', "If the plane has retractable floats, moves the retract position from Extend to Neutral, or Neutral to Retract.", "Shared Cockpit"),
- (b'RETRACT_FLOAT_SWITCH_INC', "If the plane has retractable floats, moves the retract position from Retract to Neutral, or Neutral to Extend.", "Shared Cockpit"),
- (b'TOGGLE_WATER_BALLAST_VALVE', "Turn the water ballast valve on or off.", "Shared Cockpit"),
- (b'TOGGLE_VARIOMETER_SWITCH', "Turn the variometer on or off.", "Shared Cockpit"),
- (b'TOGGLE_TURN_INDICATOR_SWITCH', "Turn the turn indicator on or off.", "Shared Cockpit"),
- (b'APU_STARTER', "Start up the auxiliary power unit (APU),.", "Shared Cockpit"),
- (b'APU_OFF_SWITCH', "Turn the APU off.", "Shared Cockpit"),
- (b'APU_GENERATOR_SWITCH_TOGGLE', "Turn the auxiliary generator on or off.", "Shared Cockpit"),
- (b'APU_GENERATOR_SWITCH_SET', "Set the auxiliary generator switch (0,1),.", "Shared Cockpit"),
- (b'EXTINGUISH_ENGINE_FIRE', "Takes a two digit argument. The first digit represents the fire extinguisher index, and the second represents the engine index. For example, 11 would represent using bottle 1 on engine 1. 21 would represent using bottle 2 on engine 1. Typical entries for a twin engine aircraft would be 11 and 22.", "Shared Cockpit"),
- (b'HYDRAULIC_SWITCH_TOGGLE', "Turn the hydraulic switch on or off.", "Shared Cockpit"),
- (b'BLEED_AIR_SOURCE_CONTROL_INC', "Increases the bleed air source control.", "Shared Cockpit"),
- (b'BLEED_AIR_SOURCE_CONTROL_DEC', "Decreases the bleed air source control.", "Shared Cockpit"),
- (b'BLEED_AIR_SOURCE_CONTROL_SET', '''Set to one of:
+ 0 - retract tail hook""",
+ "Shared Cockpit",
+ ),
+ (b"TOGGLE_WATER_RUDDER", "Toggles water rudders", "Shared Cockpit"),
+ (b"TOGGLE_PUSHBACK", "Toggles pushback.", "Shared Cockpit"),
+ (
+ b"KEY_TUG_HEADING",
+ "Triggers tug and sets the desired heading. The units are a 32 bit integer (0 to 4294967295), which represent 0 to 360 degrees. To set a 45 degree angle, for example, set the value to 4294967295 / 8.",
+ "Shared Cockpit",
+ ),
+ (
+ b"KEY_TUG_SPEED",
+ "Triggers tug, and sets desired speed, in feet per second. The speed can be both positive (forward movement), and negative (backward movement).",
+ "Shared Cockpit",
+ ),
+ (b"TUG_DISABLE", "Disables tug", "Shared Cockpit"),
+ (
+ b"TOGGLE_MASTER_IGNITION_SWITCH",
+ "Toggles master ignition switch",
+ "Shared Cockpit",
+ ),
+ (b"TOGGLE_TAILWHEEL_LOCK", "Toggles tail wheel lock", "Shared Cockpit"),
+ (
+ b"ADD_FUEL_QUANTITY",
+ "Adds fuel to the aircraft, 25% of capacity by default. 0 to 65535 (max fuel), can be passed.",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOW_PLANE_RELEASE",
+ "Release a towed aircraft, usually a glider.",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOW_PLANE_REQUEST",
+ "Request a tow plane. The user aircraft must be tow-able, stationary, on the ground and not already attached for this to succeed.",
+ "Shared Cockpit",
+ ),
+ (
+ b"RELEASE_DROPPABLE_OBJECTS",
+ "Release one droppable object. Multiple key events will release multiple objects.",
+ "Shared Cockpit",
+ ),
+ (
+ b"RETRACT_FLOAT_SWITCH_DEC",
+ "If the plane has retractable floats, moves the retract position from Extend to Neutral, or Neutral to Retract.",
+ "Shared Cockpit",
+ ),
+ (
+ b"RETRACT_FLOAT_SWITCH_INC",
+ "If the plane has retractable floats, moves the retract position from Retract to Neutral, or Neutral to Extend.",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_WATER_BALLAST_VALVE",
+ "Turn the water ballast valve on or off.",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_VARIOMETER_SWITCH",
+ "Turn the variometer on or off.",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_TURN_INDICATOR_SWITCH",
+ "Turn the turn indicator on or off.",
+ "Shared Cockpit",
+ ),
+ (
+ b"APU_STARTER",
+ "Start up the auxiliary power unit (APU),.",
+ "Shared Cockpit",
+ ),
+ (b"APU_OFF_SWITCH", "Turn the APU off.", "Shared Cockpit"),
+ (
+ b"APU_GENERATOR_SWITCH_TOGGLE",
+ "Turn the auxiliary generator on or off.",
+ "Shared Cockpit",
+ ),
+ (
+ b"APU_GENERATOR_SWITCH_SET",
+ "Set the auxiliary generator switch (0,1),.",
+ "Shared Cockpit",
+ ),
+ (
+ b"EXTINGUISH_ENGINE_FIRE",
+ "Takes a two digit argument. The first digit represents the fire extinguisher index, and the second represents the engine index. For example, 11 would represent using bottle 1 on engine 1. 21 would represent using bottle 2 on engine 1. Typical entries for a twin engine aircraft would be 11 and 22.",
+ "Shared Cockpit",
+ ),
+ (
+ b"HYDRAULIC_SWITCH_TOGGLE",
+ "Turn the hydraulic switch on or off.",
+ "Shared Cockpit",
+ ),
+ (
+ b"BLEED_AIR_SOURCE_CONTROL_INC",
+ "Increases the bleed air source control.",
+ "Shared Cockpit",
+ ),
+ (
+ b"BLEED_AIR_SOURCE_CONTROL_DEC",
+ "Decreases the bleed air source control.",
+ "Shared Cockpit",
+ ),
+ (
+ b"BLEED_AIR_SOURCE_CONTROL_SET",
+ """Set to one of:
0: auto
1: off
2: apu
- 3: engines''', "Shared Cockpit"),
- (b'TURBINE_IGNITION_SWITCH_TOGGLE', "Turn the turbine ignition switch on or off.", "Shared Cockpit"),
- (b'CABIN_NO_SMOKING_ALERT_SWITCH_TOGGLE', "Turn the \"No smoking\" alert on or off.", "Shared Cockpit"),
- (b'CABIN_SEATBELTS_ALERT_SWITCH_TOGGLE', "Turn the \"Fasten seatbelts\" alert on or off.", "Shared Cockpit"),
- (b'ANTISKID_BRAKES_TOGGLE', "Turn the anti-skid braking system on or off.", "Shared Cockpit"),
- (b'GPWS_SWITCH_TOGGLE', "Turn the g round proximity warning system (GPWS), on or off.", "Shared Cockpit"),
- (b'MANUAL_FUEL_PRESSURE_PUMP', "Activate the manual fuel pressure pump.", "Shared Cockpit"),
- ]
+ 3: engines""",
+ "Shared Cockpit",
+ ),
+ (
+ b"TURBINE_IGNITION_SWITCH_TOGGLE",
+ "Turn the turbine ignition switch on or off.",
+ "Shared Cockpit",
+ ),
+ (
+ b"CABIN_NO_SMOKING_ALERT_SWITCH_TOGGLE",
+ 'Turn the "No smoking" alert on or off.',
+ "Shared Cockpit",
+ ),
+ (
+ b"CABIN_SEATBELTS_ALERT_SWITCH_TOGGLE",
+ 'Turn the "Fasten seatbelts" alert on or off.',
+ "Shared Cockpit",
+ ),
+ (
+ b"ANTISKID_BRAKES_TOGGLE",
+ "Turn the anti-skid braking system on or off.",
+ "Shared Cockpit",
+ ),
+ (
+ b"GPWS_SWITCH_TOGGLE",
+ "Turn the g round proximity warning system (GPWS), on or off.",
+ "Shared Cockpit",
+ ),
+ (
+ b"MANUAL_FUEL_PRESSURE_PUMP",
+ "Activate the manual fuel pressure pump.",
+ "Shared Cockpit",
+ ),
+ ]
- class __Nose_wheel_steering(EventHelper):
- list = [
- (b'STEERING_INC', "Increments the nose wheel steering position by 5 percent.", "Shared Cockpit"),
- (b'STEERING_DEC', "Decrements the nose wheel steering position by 5 percent.", "Shared Cockpit"),
- (b'STEERING_SET', "Sets the value of the nose wheel steering position. Zero is straight ahead (-16383, +16383),.", "Shared Cockpit"),
- ]
+ class __Nose_wheel_steering(EventHelper):
+ list = [
+ (
+ b"STEERING_INC",
+ "Increments the nose wheel steering position by 5 percent.",
+ "Shared Cockpit",
+ ),
+ (
+ b"STEERING_DEC",
+ "Decrements the nose wheel steering position by 5 percent.",
+ "Shared Cockpit",
+ ),
+ (
+ b"STEERING_SET",
+ "Sets the value of the nose wheel steering position. Zero is straight ahead (-16383, +16383),.",
+ "Shared Cockpit",
+ ),
+ ]
- class __Cabin_pressurization(EventHelper):
- list = [
- (b'KEY_PRESSURIZATION_PRESSURE_ALT_INC', "Increases the altitude that the cabin is pressurized to.", "Shared Cockpit"),
- (b'KEY_PRESSURIZATION_PRESSURE_ALT_DEC', "Decreases the altitude that the cabin is pressurized to.", "Shared Cockpit"),
- (b'PRESSURIZATION_CLIMB_RATE_INC', "Sets the rate at which cabin pressurization is increased.", "Shared Cockpit"),
- (b'PRESSURIZATION_CLIMB_RATE_DEC', "Sets the rate at which cabin pressurization is decreased.", "Shared Cockpit"),
- (b'PRESSURIZATION_PRESSURE_DUMP_SWTICH', '''Sets the cabin pressure to the outside air pressure.''', "Shared Cockpit"),
- ]
+ class __Cabin_pressurization(EventHelper):
+ list = [
+ (
+ b"KEY_PRESSURIZATION_PRESSURE_ALT_INC",
+ "Increases the altitude that the cabin is pressurized to.",
+ "Shared Cockpit",
+ ),
+ (
+ b"KEY_PRESSURIZATION_PRESSURE_ALT_DEC",
+ "Decreases the altitude that the cabin is pressurized to.",
+ "Shared Cockpit",
+ ),
+ (
+ b"PRESSURIZATION_CLIMB_RATE_INC",
+ "Sets the rate at which cabin pressurization is increased.",
+ "Shared Cockpit",
+ ),
+ (
+ b"PRESSURIZATION_CLIMB_RATE_DEC",
+ "Sets the rate at which cabin pressurization is decreased.",
+ "Shared Cockpit",
+ ),
+ (
+ b"PRESSURIZATION_PRESSURE_DUMP_SWTICH",
+ """Sets the cabin pressure to the outside air pressure.""",
+ "Shared Cockpit",
+ ),
+ ]
- class __Catapult_Launches(EventHelper):
- list = [
- (b'TAKEOFF_ASSIST_ARM_TOGGLE', "Deploy or remove the assist arm. Refer to the document Notes on Aircraft Systems.", "Shared Cockpit"),
- (b'TAKEOFF_ASSIST_ARM_SET', '''Value:
+ class __Catapult_Launches(EventHelper):
+ list = [
+ (
+ b"TAKEOFF_ASSIST_ARM_TOGGLE",
+ "Deploy or remove the assist arm. Refer to the document Notes on Aircraft Systems.",
+ "Shared Cockpit",
+ ),
+ (
+ b"TAKEOFF_ASSIST_ARM_SET",
+ """Value:
TRUE request set
- FALSE request unset''', "Shared Cockpit"),
- (b'TAKEOFF_ASSIST_FIRE', "If everything is set up correctly. Launch from the catapult.", "Shared Cockpit"),
- (b'TOGGLE_LAUNCH_BAR_SWITCH', "Toggle the request for the launch bar to be installed or removed.", "Shared Cockpit"),
- (b'SET_LAUNCH_BAR_SWITCH', '''Value:
+ FALSE request unset""",
+ "Shared Cockpit",
+ ),
+ (
+ b"TAKEOFF_ASSIST_FIRE",
+ "If everything is set up correctly. Launch from the catapult.",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_LAUNCH_BAR_SWITCH",
+ "Toggle the request for the launch bar to be installed or removed.",
+ "Shared Cockpit",
+ ),
+ (
+ b"SET_LAUNCH_BAR_SWITCH",
+ """Value:
TRUE request set
- FALSE request unset''', "Shared Cockpit"),
- ]
+ FALSE request unset""",
+ "Shared Cockpit",
+ ),
+ ]
- class __Helicopter_Specific_Systems(EventHelper):
- list = [
- (b'ROTOR_BRAKE', " Triggers rotor braking input", "Shared Cockpit"),
- (b'ROTOR_CLUTCH_SWITCH_TOGGLE', "Toggles on electric rotor clutch switch", "Shared Cockpit"),
- (b'ROTOR_CLUTCH_SWITCH_SET', "Sets electric rotor clutch switch on/off (1,0),", "Shared Cockpit"),
- (b'ROTOR_GOV_SWITCH_TOGGLE', "Toggles the electric rotor governor switch", "Shared Cockpit"),
- (b'ROTOR_GOV_SWITCH_SET', "Sets the electric rotor governor switch on/off (1,0),", "Shared Cockpit"),
- (b'ROTOR_LATERAL_TRIM_INC', "Increments the lateral (right), rotor trim", "Shared Cockpit"),
- (b'ROTOR_LATERAL_TRIM_DEC', "Decrements the lateral (right), rotor trim", "Shared Cockpit"),
- (b'ROTOR_LATERAL_TRIM_SET', "Sets the lateral (right), rotor trim (0 to 16383)", "Shared Cockpit"),
- ]
+ class __Helicopter_Specific_Systems(EventHelper):
+ list = [
+ (b"ROTOR_BRAKE", " Triggers rotor braking input", "Shared Cockpit"),
+ (
+ b"ROTOR_CLUTCH_SWITCH_TOGGLE",
+ "Toggles on electric rotor clutch switch",
+ "Shared Cockpit",
+ ),
+ (
+ b"ROTOR_CLUTCH_SWITCH_SET",
+ "Sets electric rotor clutch switch on/off (1,0),",
+ "Shared Cockpit",
+ ),
+ (
+ b"ROTOR_GOV_SWITCH_TOGGLE",
+ "Toggles the electric rotor governor switch",
+ "Shared Cockpit",
+ ),
+ (
+ b"ROTOR_GOV_SWITCH_SET",
+ "Sets the electric rotor governor switch on/off (1,0),",
+ "Shared Cockpit",
+ ),
+ (
+ b"ROTOR_LATERAL_TRIM_INC",
+ "Increments the lateral (right), rotor trim",
+ "Shared Cockpit",
+ ),
+ (
+ b"ROTOR_LATERAL_TRIM_DEC",
+ "Decrements the lateral (right), rotor trim",
+ "Shared Cockpit",
+ ),
+ (
+ b"ROTOR_LATERAL_TRIM_SET",
+ "Sets the lateral (right), rotor trim (0 to 16383)",
+ "Shared Cockpit",
+ ),
+ ]
- class __Slings_and_Hoists(EventHelper):
- list = [
- (b'SLING_PICKUP_RELEASE', "Toggle between pickup and release mode. Hold mode is automatic and cannot be selected. Refer to the document Notes on Aircraft Systems.", "Shared Cockpit"),
- (b'HOIST_SWITCH_EXTEND', "The rate at which a hoist cable extends is set in the Aircraft Configuration File.", "Shared Cockpit"),
- (b'HOIST_SWITCH_RETRACT', "The rate at which a hoist cable retracts is set in the Aircraft Configuration File.", "Shared Cockpit"),
- (b'HOIST_SWITCH_SET', '''The data value should be set to one of:
+ class __Slings_and_Hoists(EventHelper):
+ list = [
+ (
+ b"SLING_PICKUP_RELEASE",
+ "Toggle between pickup and release mode. Hold mode is automatic and cannot be selected. Refer to the document Notes on Aircraft Systems.",
+ "Shared Cockpit",
+ ),
+ (
+ b"HOIST_SWITCH_EXTEND",
+ "The rate at which a hoist cable extends is set in the Aircraft Configuration File.",
+ "Shared Cockpit",
+ ),
+ (
+ b"HOIST_SWITCH_RETRACT",
+ "The rate at which a hoist cable retracts is set in the Aircraft Configuration File.",
+ "Shared Cockpit",
+ ),
+ (
+ b"HOIST_SWITCH_SET",
+ """The data value should be set to one of:
<0 up
=0 off
- >0 down''', "Shared Cockpit"),
- (b'HOIST_DEPLOY_TOGGLE', "Toggles the hoist arm switch, extend or retract.", "Shared Cockpit"),
- (b'HOIST_DEPLOY_SET', '''The data value should be set to:
+ >0 down""",
+ "Shared Cockpit",
+ ),
+ (
+ b"HOIST_DEPLOY_TOGGLE",
+ "Toggles the hoist arm switch, extend or retract.",
+ "Shared Cockpit",
+ ),
+ (
+ b"HOIST_DEPLOY_SET",
+ """The data value should be set to:
0 - set hoist switch to retract the arm
- 1 - set hoist switch to extend the arm''', "Shared Cockpit"),
- ]
+ 1 - set hoist switch to extend the arm""",
+ "Shared Cockpit",
+ ),
+ ]
- class __Slew_System(EventHelper):
- list = [
- (b'SLEW_TOGGLE', "Toggles slew on/off", "Shared Cockpit (Pilot only),"),
- (b'SLEW_OFF', "Turns slew off", "Shared Cockpit (Pilot only),"),
- (b'SLEW_ON', "Turns slew on", "Shared Cockpit (Pilot only),"),
- (b'SLEW_SET', "Sets slew on/off (1,0),", "Shared Cockpit (Pilot only)"),
- (b'SLEW_RESET', "Stop slew and reset pitch, bank, and heading all to zero.", "Shared Cockpit (Pilot only),"),
- (b'SLEW_ALTIT_UP_FAST', "Slew upward fast", "Shared Cockpit (Pilot only),"),
- (b'SLEW_ALTIT_UP_SLOW', "Slew upward slow", "Shared Cockpit (Pilot only),"),
- (b'SLEW_ALTIT_FREEZE', "Stop vertical slew", "Shared Cockpit (Pilot only),"),
- (b'SLEW_ALTIT_DN_SLOW', "Slew downward slow", "Shared Cockpit (Pilot only),"),
- (b'SLEW_ALTIT_DN_FAST', "Slew downward fast", "Shared Cockpit (Pilot only),"),
- (b'SLEW_ALTIT_PLUS', "Increase upward slew", "Shared Cockpit (Pilot only),"),
- (b'SLEW_ALTIT_MINUS', "Decrease upward slew ", "Shared Cockpit (Pilot only),"),
- (b'SLEW_PITCH_DN_FAST', "Slew pitch downward fast", "Shared Cockpit (Pilot only),"),
- (b'SLEW_PITCH_DN_SLOW', "Slew pitch downward slow", "Shared Cockpit (Pilot only),"),
- (b'SLEW_PITCH_FREEZE', "Stop pitch slew", "Shared Cockpit (Pilot only),"),
- (b'SLEW_PITCH_UP_SLOW', "Slew pitch up slow", "Shared Cockpit (Pilot only),"),
- (b'SLEW_PITCH_UP_FAST', "Slew pitch upward fast", "Shared Cockpit (Pilot only),"),
- (b'SLEW_PITCH_PLUS', "Increase pitch up slew", "Shared Cockpit (Pilot only),"),
- (b'SLEW_PITCH_MINUS', "Decrease pitch up slew", "Shared Cockpit (Pilot only),"),
- (b'SLEW_BANK_MINUS', "Increase left bank slew", "Shared Cockpit (Pilot only),"),
- (b'SLEW_AHEAD_PLUS', "Increase forward slew", "Shared Cockpit (Pilot only),"),
- (b'SLEW_BANK_PLUS', "Increase right bank slew", "Shared Cockpit (Pilot only),"),
- (b'SLEW_LEFT', "Slew to the left", "Shared Cockpit (Pilot only),"),
- (b'SLEW_FREEZE', "Stop all slew", "Shared Cockpit (Pilot only),"),
- (b'SLEW_RIGHT', "Slew to the right", "Shared Cockpit (Pilot only),"),
- (b'SLEW_HEADING_MINUS', "Increase slew heading to the left", "Shared Cockpit (Pilot only),"),
- (b'SLEW_AHEAD_MINUS', "Decrease forward slew", "Shared Cockpit (Pilot only),"),
- (b'SLEW_HEADING_PLUS', "Increase slew heading to the right", "Shared Cockpit (Pilot only),"),
- (b'AXIS_SLEW_AHEAD_SET', "Sets forward slew (+/- 16383),", "Shared Cockpit (Pilot only)"),
- (b'AXIS_SLEW_SIDEWAYS_SET', "Sets sideways slew (+/- 16383),", "Shared Cockpit (Pilot only)"),
- (b'AXIS_SLEW_HEADING_SET', "Sets heading slew (+/- 16383),", "Shared Cockpit (Pilot only)"),
- (b'AXIS_SLEW_ALT_SET', "Sets vertical slew (+/- 16383),", "Shared Cockpit (Pilot only)"),
- (b'AXIS_SLEW_BANK_SET', "Sets roll slew (+/- 16383),", "Shared Cockpit (Pilot only)"),
- (b'AXIS_SLEW_PITCH_SET', "Sets pitch slew (+/- 16383),", "Shared Cockpit (Pilot only)"),
- ]
+ class __Slew_System(EventHelper):
+ list = [
+ (b"SLEW_TOGGLE", "Toggles slew on/off", "Shared Cockpit (Pilot only),"),
+ (b"SLEW_OFF", "Turns slew off", "Shared Cockpit (Pilot only),"),
+ (b"SLEW_ON", "Turns slew on", "Shared Cockpit (Pilot only),"),
+ (b"SLEW_SET", "Sets slew on/off (1,0),", "Shared Cockpit (Pilot only)"),
+ (
+ b"SLEW_RESET",
+ "Stop slew and reset pitch, bank, and heading all to zero.",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (b"SLEW_ALTIT_UP_FAST", "Slew upward fast", "Shared Cockpit (Pilot only),"),
+ (b"SLEW_ALTIT_UP_SLOW", "Slew upward slow", "Shared Cockpit (Pilot only),"),
+ (
+ b"SLEW_ALTIT_FREEZE",
+ "Stop vertical slew",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_ALTIT_DN_SLOW",
+ "Slew downward slow",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_ALTIT_DN_FAST",
+ "Slew downward fast",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_ALTIT_PLUS",
+ "Increase upward slew",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_ALTIT_MINUS",
+ "Decrease upward slew ",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_PITCH_DN_FAST",
+ "Slew pitch downward fast",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_PITCH_DN_SLOW",
+ "Slew pitch downward slow",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (b"SLEW_PITCH_FREEZE", "Stop pitch slew", "Shared Cockpit (Pilot only),"),
+ (
+ b"SLEW_PITCH_UP_SLOW",
+ "Slew pitch up slow",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_PITCH_UP_FAST",
+ "Slew pitch upward fast",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_PITCH_PLUS",
+ "Increase pitch up slew",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_PITCH_MINUS",
+ "Decrease pitch up slew",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_BANK_MINUS",
+ "Increase left bank slew",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_AHEAD_PLUS",
+ "Increase forward slew",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_BANK_PLUS",
+ "Increase right bank slew",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (b"SLEW_LEFT", "Slew to the left", "Shared Cockpit (Pilot only),"),
+ (b"SLEW_FREEZE", "Stop all slew", "Shared Cockpit (Pilot only),"),
+ (b"SLEW_RIGHT", "Slew to the right", "Shared Cockpit (Pilot only),"),
+ (
+ b"SLEW_HEADING_MINUS",
+ "Increase slew heading to the left",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_AHEAD_MINUS",
+ "Decrease forward slew",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"SLEW_HEADING_PLUS",
+ "Increase slew heading to the right",
+ "Shared Cockpit (Pilot only),",
+ ),
+ (
+ b"AXIS_SLEW_AHEAD_SET",
+ "Sets forward slew (+/- 16383),",
+ "Shared Cockpit (Pilot only)",
+ ),
+ (
+ b"AXIS_SLEW_SIDEWAYS_SET",
+ "Sets sideways slew (+/- 16383),",
+ "Shared Cockpit (Pilot only)",
+ ),
+ (
+ b"AXIS_SLEW_HEADING_SET",
+ "Sets heading slew (+/- 16383),",
+ "Shared Cockpit (Pilot only)",
+ ),
+ (
+ b"AXIS_SLEW_ALT_SET",
+ "Sets vertical slew (+/- 16383),",
+ "Shared Cockpit (Pilot only)",
+ ),
+ (
+ b"AXIS_SLEW_BANK_SET",
+ "Sets roll slew (+/- 16383),",
+ "Shared Cockpit (Pilot only)",
+ ),
+ (
+ b"AXIS_SLEW_PITCH_SET",
+ "Sets pitch slew (+/- 16383),",
+ "Shared Cockpit (Pilot only)",
+ ),
+ ]
- class __View_System(EventHelper):
- list = [
- (b'VIEW_MODE', "Selects next view", "Shared Cockpit"),
- (b'VIEW_WINDOW_TO_FRONT', "Sets active window to front", "Shared Cockpit"),
- (b'VIEW_RESET', "Resets the view to the default", "Shared Cockpit"),
- (b'VIEW_ALWAYS_PAN_UP', " ", "Shared Cockpit"),
- (b'VIEW_ALWAYS_PAN_DOWN', " ", "Shared Cockpit"),
- (b'NEXT_SUB_VIEW', " ", "Shared Cockpit"),
- (b'PREV_SUB_VIEW', " ", "Shared Cockpit"),
- (b'VIEW_TRACK_PAN_TOGGLE', " ", "Shared Cockpit"),
- (b'VIEW_PREVIOUS_TOGGLE', " ", "Shared Cockpit"),
- (b'VIEW_CAMERA_SELECT_START', " ", "Shared Cockpit"),
- (b'PANEL_HUD_NEXT', " ", "Shared Cockpit"),
- (b'PANEL_HUD_PREVIOUS', " ", "Shared Cockpit"),
- (b'ZOOM_IN', "Zooms view in", "Shared Cockpit"),
- (b'ZOOM_OUT', "Zooms view out", "Shared Cockpit"),
- (b'MAP_ZOOM_FINE_IN', "Fine zoom in map view", "Shared Cockpit"),
- (b'PAN_LEFT', "Pans view left", "Shared Cockpit"),
- (b'PAN_RIGHT', "Pans view right", "Shared Cockpit"),
- (b'MAP_ZOOM_FINE_OUT', "Fine zoom out in map view", "Shared Cockpit"),
- (b'VIEW_FORWARD', "Sets view direction forward", "Shared Cockpit"),
- (b'VIEW_FORWARD_RIGHT', "Sets view direction forward and right", "Shared Cockpit"),
- (b'VIEW_RIGHT', "Sets view direction to the right", "Shared Cockpit"),
- (b'VIEW_REAR_RIGHT', "Sets view direction to the rear and right", "Shared Cockpit"),
- (b'VIEW_REAR', "Sets view direction to the rear", "Shared Cockpit"),
- (b'VIEW_REAR_LEFT', "Sets view direction to the rear and left", "Shared Cockpit"),
- (b'VIEW_LEFT', "Sets view direction to the left", "Shared Cockpit"),
- (b'VIEW_FORWARD_LEFT', "Sets view direction forward and left", "Shared Cockpit"),
- (b'VIEW_DOWN', "Sets view direction down", "Shared Cockpit"),
- (b'ZOOM_MINUS', "Decreases zoom", "Shared Cockpit"),
- (b'ZOOM_PLUS', "Increase zoom", "Shared Cockpit"),
- (b'PAN_UP', "Pan view up", "Shared Cockpit"),
- (b'PAN_DOWN', "Pan view down", "Shared Cockpit"),
- (b'VIEW_MODE_REV', "Reverse view cycle", "Shared Cockpit"),
- (b'ZOOM_IN_FINE', "Zoom in fine", "Shared Cockpit"),
- (b'ZOOM_OUT_FINE', "Zoom out fine", "Shared Cockpit"),
- (b'CLOSE_VIEW', "Close current view", "Shared Cockpit"),
- (b'NEW_VIEW', "Open new view", "Shared Cockpit"),
- (b'NEXT_VIEW', "Select next view", "Shared Cockpit"),
- (b'PREV_VIEW', "Select previous view", "Shared Cockpit"),
- (b'PAN_LEFT_UP', "Pan view left", "Shared Cockpit"),
- (b'PAN_LEFT_DOWN', "Pan view left and down", "Shared Cockpit"),
- (b'PAN_RIGHT_UP', "Pan view right and up", "Shared Cockpit"),
- (b'PAN_RIGHT_DOWN', "Pan view right and down", "Shared Cockpit"),
- (b'PAN_TILT_LEFT', "Tilt view left", "Shared Cockpit"),
- (b'PAN_TILT_RIGHT', "Tilt view right", "Shared Cockpit"),
- (b'PAN_RESET', "Reset view to forward", "Shared Cockpit"),
- (b'VIEW_FORWARD_UP', "Sets view forward and up", "Shared Cockpit"),
- (b'VIEW_FORWARD_RIGHT_UP', "Sets view forward, right, and up", "Shared Cockpit"),
- (b'VIEW_RIGHT_UP', "Sets view right and up", "Shared Cockpit"),
- (b'VIEW_REAR_RIGHT_UP', "Sets view rear, right, and up", "Shared Cockpit"),
- (b'VIEW_REAR_UP', "Sets view rear and up", "Shared Cockpit"),
- (b'VIEW_REAR_LEFT_UP', "Sets view rear left and up", "Shared Cockpit"),
- (b'VIEW_LEFT_UP', "Sets view left and up", "Shared Cockpit"),
- (b'VIEW_FORWARD_LEFT_UP', "Sets view forward left and up", "Shared Cockpit"),
- (b'VIEW_UP', "Sets view up", "Shared Cockpit"),
- (b'VIEW_RESET', "Reset view forward", "Shared Cockpit"),
- (b'PAN_RESET_COCKPIT', "Reset panning to forward, if in cockpit view", "Shared Cockpit"),
- (b'KEY_CHASE_VIEW_NEXT', "Cycle view to next target", "Shared Cockpit"),
- (b'KEY_CHASE_VIEW_PREV', "Cycle view to previous target", "Shared Cockpit"),
- (b'CHASE_VIEW_TOGGLE', "Toggles chase view on/off", "Shared Cockpit"),
- (b'EYEPOINT_UP', "Move eyepoint up", "Shared Cockpit"),
- (b'EYEPOINT_DOWN', "Move eyepoint down", "Shared Cockpit"),
- (b'EYEPOINT_RIGHT', "Move eyepoint right", "Shared Cockpit"),
- (b'EYEPOINT_LEFT', "Move eyepoint left", "Shared Cockpit"),
- (b'EYEPOINT_FORWARD', "Move eyepoint forward", "Shared Cockpit"),
- (b'EYEPOINT_BACK', "Move eyepoint backward", "Shared Cockpit"),
- (b'EYEPOINT_RESET', "Move eyepoint to default position", "Shared Cockpit"),
- (b'NEW_MAP', "Opens new map view", "Shared Cockpit"),
- (b'VIEW_COCKPIT_FORWARD', "Switch immediately to the forward view, in 2D mode.", "Shared Cockpit"),
- (b'VIEW_VIRTUAL_COCKPIT_FORWARD', "Switch immediately to the forward view, in virtual cockpit mode.", "Shared Cockpit"),
- (b'VIEW_PANEL_ALPHA_SET', "Sets the alpha-blending value for the panel. Takes a parameter in the range 0 to 255. The alpha-blending can be changed from the keyboard using Ctrl-Shift-T, and the plus and minus keys.", "Shared Cockpit"),
- (b'VIEW_PANEL_ALPHA_SELECT', "Sets the mode to change the alpha-blending, so the keys KEY_PLUS and KEY_MINUS increment and decrement the value.", "Shared Cockpit"),
- (b'VIEW_PANEL_ALPHA_INC', "Increment alpha-blending for the panel.", "Shared Cockpit"),
- (b'VIEW_PANEL_ALPHA_DEC', "Decrement alpha-blending for the panel.", "Shared Cockpit"),
- (b'VIEW_LINKING_SET', "Links all the views from one camera together, so that panning the view will change the view of all the linked cameras.", "Shared Cockpit"),
- (b'VIEW_LINKING_TOGGLE', "Turns view linking on or off.", "Shared Cockpit"),
- (b'VIEW_CHASE_DISTANCE_ADD', "Increments the distance of the view camera from the chase object (such as in Spot Plane view, or viewing an AI controlled aircraft),.", "Shared Cockpit"),
- (b'VIEW_CHASE_DISTANCE_SUB', "Decrements the distance of the view camera from the chase object.", "Shared Cockpit"),
- ]
+ class __View_System(EventHelper):
+ list = [
+ (b"VIEW_MODE", "Selects next view", "Shared Cockpit"),
+ (b"VIEW_WINDOW_TO_FRONT", "Sets active window to front", "Shared Cockpit"),
+ (b"VIEW_RESET", "Resets the view to the default", "Shared Cockpit"),
+ (b"VIEW_ALWAYS_PAN_UP", " ", "Shared Cockpit"),
+ (b"VIEW_ALWAYS_PAN_DOWN", " ", "Shared Cockpit"),
+ (b"NEXT_SUB_VIEW", " ", "Shared Cockpit"),
+ (b"PREV_SUB_VIEW", " ", "Shared Cockpit"),
+ (b"VIEW_TRACK_PAN_TOGGLE", " ", "Shared Cockpit"),
+ (b"VIEW_PREVIOUS_TOGGLE", " ", "Shared Cockpit"),
+ (b"VIEW_CAMERA_SELECT_START", " ", "Shared Cockpit"),
+ (b"PANEL_HUD_NEXT", " ", "Shared Cockpit"),
+ (b"PANEL_HUD_PREVIOUS", " ", "Shared Cockpit"),
+ (b"ZOOM_IN", "Zooms view in", "Shared Cockpit"),
+ (b"ZOOM_OUT", "Zooms view out", "Shared Cockpit"),
+ (b"MAP_ZOOM_FINE_IN", "Fine zoom in map view", "Shared Cockpit"),
+ (b"PAN_LEFT", "Pans view left", "Shared Cockpit"),
+ (b"PAN_RIGHT", "Pans view right", "Shared Cockpit"),
+ (b"MAP_ZOOM_FINE_OUT", "Fine zoom out in map view", "Shared Cockpit"),
+ (b"VIEW_FORWARD", "Sets view direction forward", "Shared Cockpit"),
+ (
+ b"VIEW_FORWARD_RIGHT",
+ "Sets view direction forward and right",
+ "Shared Cockpit",
+ ),
+ (b"VIEW_RIGHT", "Sets view direction to the right", "Shared Cockpit"),
+ (
+ b"VIEW_REAR_RIGHT",
+ "Sets view direction to the rear and right",
+ "Shared Cockpit",
+ ),
+ (b"VIEW_REAR", "Sets view direction to the rear", "Shared Cockpit"),
+ (
+ b"VIEW_REAR_LEFT",
+ "Sets view direction to the rear and left",
+ "Shared Cockpit",
+ ),
+ (b"VIEW_LEFT", "Sets view direction to the left", "Shared Cockpit"),
+ (
+ b"VIEW_FORWARD_LEFT",
+ "Sets view direction forward and left",
+ "Shared Cockpit",
+ ),
+ (b"VIEW_DOWN", "Sets view direction down", "Shared Cockpit"),
+ (b"ZOOM_MINUS", "Decreases zoom", "Shared Cockpit"),
+ (b"ZOOM_PLUS", "Increase zoom", "Shared Cockpit"),
+ (b"PAN_UP", "Pan view up", "Shared Cockpit"),
+ (b"PAN_DOWN", "Pan view down", "Shared Cockpit"),
+ (b"VIEW_MODE_REV", "Reverse view cycle", "Shared Cockpit"),
+ (b"ZOOM_IN_FINE", "Zoom in fine", "Shared Cockpit"),
+ (b"ZOOM_OUT_FINE", "Zoom out fine", "Shared Cockpit"),
+ (b"CLOSE_VIEW", "Close current view", "Shared Cockpit"),
+ (b"NEW_VIEW", "Open new view", "Shared Cockpit"),
+ (b"NEXT_VIEW", "Select next view", "Shared Cockpit"),
+ (b"PREV_VIEW", "Select previous view", "Shared Cockpit"),
+ (b"PAN_LEFT_UP", "Pan view left", "Shared Cockpit"),
+ (b"PAN_LEFT_DOWN", "Pan view left and down", "Shared Cockpit"),
+ (b"PAN_RIGHT_UP", "Pan view right and up", "Shared Cockpit"),
+ (b"PAN_RIGHT_DOWN", "Pan view right and down", "Shared Cockpit"),
+ (b"PAN_TILT_LEFT", "Tilt view left", "Shared Cockpit"),
+ (b"PAN_TILT_RIGHT", "Tilt view right", "Shared Cockpit"),
+ (b"PAN_RESET", "Reset view to forward", "Shared Cockpit"),
+ (b"VIEW_FORWARD_UP", "Sets view forward and up", "Shared Cockpit"),
+ (
+ b"VIEW_FORWARD_RIGHT_UP",
+ "Sets view forward, right, and up",
+ "Shared Cockpit",
+ ),
+ (b"VIEW_RIGHT_UP", "Sets view right and up", "Shared Cockpit"),
+ (b"VIEW_REAR_RIGHT_UP", "Sets view rear, right, and up", "Shared Cockpit"),
+ (b"VIEW_REAR_UP", "Sets view rear and up", "Shared Cockpit"),
+ (b"VIEW_REAR_LEFT_UP", "Sets view rear left and up", "Shared Cockpit"),
+ (b"VIEW_LEFT_UP", "Sets view left and up", "Shared Cockpit"),
+ (
+ b"VIEW_FORWARD_LEFT_UP",
+ "Sets view forward left and up",
+ "Shared Cockpit",
+ ),
+ (b"VIEW_UP", "Sets view up", "Shared Cockpit"),
+ (b"VIEW_RESET", "Reset view forward", "Shared Cockpit"),
+ (
+ b"PAN_RESET_COCKPIT",
+ "Reset panning to forward, if in cockpit view",
+ "Shared Cockpit",
+ ),
+ (b"KEY_CHASE_VIEW_NEXT", "Cycle view to next target", "Shared Cockpit"),
+ (b"KEY_CHASE_VIEW_PREV", "Cycle view to previous target", "Shared Cockpit"),
+ (b"CHASE_VIEW_TOGGLE", "Toggles chase view on/off", "Shared Cockpit"),
+ (b"EYEPOINT_UP", "Move eyepoint up", "Shared Cockpit"),
+ (b"EYEPOINT_DOWN", "Move eyepoint down", "Shared Cockpit"),
+ (b"EYEPOINT_RIGHT", "Move eyepoint right", "Shared Cockpit"),
+ (b"EYEPOINT_LEFT", "Move eyepoint left", "Shared Cockpit"),
+ (b"EYEPOINT_FORWARD", "Move eyepoint forward", "Shared Cockpit"),
+ (b"EYEPOINT_BACK", "Move eyepoint backward", "Shared Cockpit"),
+ (b"EYEPOINT_RESET", "Move eyepoint to default position", "Shared Cockpit"),
+ (b"NEW_MAP", "Opens new map view", "Shared Cockpit"),
+ (
+ b"VIEW_COCKPIT_FORWARD",
+ "Switch immediately to the forward view, in 2D mode.",
+ "Shared Cockpit",
+ ),
+ (
+ b"VIEW_VIRTUAL_COCKPIT_FORWARD",
+ "Switch immediately to the forward view, in virtual cockpit mode.",
+ "Shared Cockpit",
+ ),
+ (
+ b"VIEW_PANEL_ALPHA_SET",
+ "Sets the alpha-blending value for the panel. Takes a parameter in the range 0 to 255. The alpha-blending can be changed from the keyboard using Ctrl-Shift-T, and the plus and minus keys.",
+ "Shared Cockpit",
+ ),
+ (
+ b"VIEW_PANEL_ALPHA_SELECT",
+ "Sets the mode to change the alpha-blending, so the keys KEY_PLUS and KEY_MINUS increment and decrement the value.",
+ "Shared Cockpit",
+ ),
+ (
+ b"VIEW_PANEL_ALPHA_INC",
+ "Increment alpha-blending for the panel.",
+ "Shared Cockpit",
+ ),
+ (
+ b"VIEW_PANEL_ALPHA_DEC",
+ "Decrement alpha-blending for the panel.",
+ "Shared Cockpit",
+ ),
+ (
+ b"VIEW_LINKING_SET",
+ "Links all the views from one camera together, so that panning the view will change the view of all the linked cameras.",
+ "Shared Cockpit",
+ ),
+ (b"VIEW_LINKING_TOGGLE", "Turns view linking on or off.", "Shared Cockpit"),
+ (
+ b"VIEW_CHASE_DISTANCE_ADD",
+ "Increments the distance of the view camera from the chase object (such as in Spot Plane view, or viewing an AI controlled aircraft),.",
+ "Shared Cockpit",
+ ),
+ (
+ b"VIEW_CHASE_DISTANCE_SUB",
+ "Decrements the distance of the view camera from the chase object.",
+ "Shared Cockpit",
+ ),
+ ]
- class __Miscellaneous_Events(EventHelper):
- list = [
- (b'PAUSE_TOGGLE', "Toggles pause on/off", "Disabled"),
- (b'PAUSE_ON', "Turns pause on", "Disabled"),
- (b'PAUSE_OFF', "Turns pause off", "Disabled"),
- (b'PAUSE_SET', "Sets pause on/off (1,0),", "Disabled"),
- (b'DEMO_STOP', "Stops demo system playback", "Shared Cockpit"),
- (b'SELECT_1', "Sets \"selected\" index (for other events), to 1", "Shared Cockpit"),
- (b'SELECT_2', "Sets \"selected\" index (for other events), to 2", "Shared Cockpit"),
- (b'SELECT_3', "Sets \"selected\" index (for other events), to 3", "Shared Cockpit"),
- (b'SELECT_4', "Sets \"selected\" index (for other events), to 4", "Shared Cockpit"),
- (b'MINUS', "Used in conjunction with \"selected\" parameters to decrease their value (e.g., radio frequency),", "Shared Cockpit"),
- (b'PLUS', "Used in conjunction with \"selected\" parameters to increase their value (e.g., radio frequency),", "Shared Cockpit"),
- (b'ZOOM_1X', "Sets zoom level to 1", "Shared Cockpit"),
- (b'SOUND_TOGGLE', "Toggles sound on/off", "Shared Cockpit"),
- (b'SIM_RATE', "Selects simulation rate (use KEY_MINUS, KEY_PLUS to change),", "Shared Cockpit"),
- (b'JOYSTICK_CALIBRATE', "Toggles joystick on/off", "Shared Cockpit"),
- (b'SITUATION_SAVE', "Saves flight situation", "Shared Cockpit"),
- (b'SITUATION_RESET', "Resets flight situation", "Shared Cockpit"),
- (b'SOUND_SET', "Sets sound on/off (1,0),", "Shared Cockpit"),
- (b'EXIT', "Quit ESP with a message", "Shared Cockpit"),
- (b'ABORT', "Quit ESP without a message", "Shared Cockpit"),
- (b'READOUTS_SLEW', "Cycle through information readouts while in slew", "Shared Cockpit"),
- (b'READOUTS_FLIGHT', "Cycle through information readouts", "Shared Cockpit"),
- (b'MINUS_SHIFT', "Used with other events", "Shared Cockpit"),
- (b'PLUS_SHIFT', "Used with other events", "Shared Cockpit"),
- (b'SIM_RATE_INCR', "Increase sim rate", "Shared Cockpit"),
- (b'SIM_RATE_DECR', "Decrease sim rate", "Shared Cockpit"),
- (b'KNEEBOARD_VIEW', "Toggles kneeboard", "Shared Cockpit"),
- (b'PANEL_1', "Toggles panel 1", "Shared Cockpit"),
- (b'PANEL_2', "Toggles panel 2", "Shared Cockpit"),
- (b'PANEL_3', "Toggles panel 3", "Shared Cockpit"),
- (b'PANEL_4', "Toggles panel 4", "Shared Cockpit"),
- (b'PANEL_5', "Toggles panel 5", "Shared Cockpit"),
- (b'PANEL_6', "Toggles panel 6", "Shared Cockpit"),
- (b'PANEL_7', "Toggles panel 7", "Shared Cockpit"),
- (b'PANEL_8', "Toggles panel 8", "Shared Cockpit"),
- (b'PANEL_9', "Toggles panel 9", "Shared Cockpit"),
- (b'SOUND_ON', "Turns sound on", "Shared Cockpit"),
- (b'SOUND_OFF', "Turns sound off", "Shared Cockpit"),
- (b'INVOKE_HELP', "Brings up Help system", "Shared Cockpit"),
- (b'TOGGLE_AIRCRAFT_LABELS', "Toggles aircraft labels", "Shared Cockpit"),
- (b'FLIGHT_MAP', "Brings up flight map", "Shared Cockpit"),
- (b'RELOAD_PANELS', "Reload panel data", "Shared Cockpit"),
- (b'PANEL_ID_TOGGLE', "Toggles indexed panel (1 to 9),", "Shared Cockpit"),
- (b'PANEL_ID_OPEN', "Opens indexed panel (1 to 9),", "Shared Cockpit"),
- (b'PANEL_ID_CLOSE', "Closes indexed panel (1 to 9),", "Shared Cockpit"),
- (b'RELOAD_USER_AIRCRAFT', "Reloads the user aircraft data (from cache if same type loaded as an AI, otherwise from disk),", "Shared Cockpit"),
- (b'SIM_RESET', "Resets aircraft state", "Shared Cockpit"),
- (b'VIRTUAL_COPILOT_TOGGLE', "Turns Flying Tips on/off", "Shared Cockpit"),
- (b'VIRTUAL_COPILOT_SET', "Sets Flying Tips on/off (1,0),", "Shared Cockpit"),
- (b'VIRTUAL_COPILOT_ACTION', "Triggers action noted in Flying Tips", "Shared Cockpit"),
- (b'REFRESH_SCENERY', "Reloads scenery", "Shared Cockpit"),
- (b'CLOCK_HOURS_DEC', "Decrements time by hours", "Shared Cockpit"),
- (b'CLOCK_HOURS_INC', "Increments time by hours", "Shared Cockpit"),
- (b'CLOCK_MINUTES_DEC', "Decrements time by minutes", "Shared Cockpit"),
- (b'CLOCK_MINUTES_INC', "Increments time by minutes", "Shared Cockpit"),
- (b'CLOCK_SECONDS_ZERO', "Zeros seconds", "Shared Cockpit"),
- (b'CLOCK_HOURS_SET', "Sets hour of day", "Shared Cockpit"),
- (b'CLOCK_MINUTES_SET', "Sets minutes of the hour", "Shared Cockpit"),
- (b'ZULU_HOURS_SET', "Sets hours, zulu time", "Shared Cockpit"),
- (b'ZULU_MINUTES_SET', "Sets minutes, in zulu time", "Shared Cockpit"),
- (b'ZULU_DAY_SET', "Sets day, in zulu time", "Shared Cockpit"),
- (b'ZULU_YEAR_SET', "Sets year, in zulu time", "Shared Cockpit"),
- (b'GAUGE_KEYSTROKE', "Enables a keystroke to be sent to a gauge that is in focus. The keystrokes can only be in the range 0 to 9, A to Z, and the four keys: plus, minus, comma and period. This is typically used to allow some keyboard entry to a complex device such as a GPS to enter such things as ICAO codes using the keyboard, rather than turning dials.", "Shared Cockpit"),
- (b'SIMUI_WINDOW_HIDESHOW', "Display the ATC window.", "Shared Cockpit"),
- (b'VIEW_WINDOW_TITLES_TOGGLE', "Turn window titles on or off.", "Shared Cockpit"),
- (b'AXIS_PAN_PITCH', "Sets the pitch of the axis. Requires an angle.", "Shared Cockpit"),
- (b'AXIS_PAN_HEADING', "Sets the heading of the axis. Requires an angle.", "Shared Cockpit"),
- (b'AXIS_PAN_TILT', "Sets the tilt of the axis. Requires an angle.", "Shared Cockpit"),
- (b'VIEW_AXIS_INDICATOR_CYCLE', "Step through the view axes.", "Shared Cockpit"),
- (b'VIEW_MAP_ORIENTATION_CYCLE', "Step through the map orientations.", "Shared Cockpit"),
- (b'TOGGLE_JETWAY', "Requests a jetway, which will only be answered if the aircraft is at a parking spot.", "Shared Cockpit"),
- (b'VIDEO_RECORD_TOGGLE', '''Turn on or off the video recording feature. This records uncompressed AVI format files to:
- My Documents\\My Videos\\''', "Shared Cockpit"),
- (b'TOGGLE_AIRPORT_NAME_DISPLAY', "Turn on or off the airport name.", "Shared Cockpit"),
- (b'CAPTURE_SCREENSHOT', '''Capture the current view as a screenshot. Which will be saved to a bmp file in:
- My Documents\\My Pictures\\''', "Shared Cockpit"),
- (b'MOUSE_LOOK_TOGGLE', "Switch Mouse Look mode on or off. Mouse Look mode enables a user to control their view using the mouse, and holding down the space bar.", "Shared Cockpit"),
- (b'YAXIS_INVERT_TOGGLE', "Switch inversion of Y axis controls on or off.", "Shared Cockpit"),
- (b'AUTORUDDER_TOGGLE', "Turn the automatic rudder control feature on or off.", "Shared Cockpit"),
- ]
+ class __Miscellaneous_Events(EventHelper):
+ list = [
+ (b"PAUSE_TOGGLE", "Toggles pause on/off", "Disabled"),
+ (b"PAUSE_ON", "Turns pause on", "Disabled"),
+ (b"PAUSE_OFF", "Turns pause off", "Disabled"),
+ (b"PAUSE_SET", "Sets pause on/off (1,0),", "Disabled"),
+ (b"DEMO_STOP", "Stops demo system playback", "Shared Cockpit"),
+ (
+ b"SELECT_1",
+ 'Sets "selected" index (for other events), to 1',
+ "Shared Cockpit",
+ ),
+ (
+ b"SELECT_2",
+ 'Sets "selected" index (for other events), to 2',
+ "Shared Cockpit",
+ ),
+ (
+ b"SELECT_3",
+ 'Sets "selected" index (for other events), to 3',
+ "Shared Cockpit",
+ ),
+ (
+ b"SELECT_4",
+ 'Sets "selected" index (for other events), to 4',
+ "Shared Cockpit",
+ ),
+ (
+ b"MINUS",
+ 'Used in conjunction with "selected" parameters to decrease their value (e.g., radio frequency),',
+ "Shared Cockpit",
+ ),
+ (
+ b"PLUS",
+ 'Used in conjunction with "selected" parameters to increase their value (e.g., radio frequency),',
+ "Shared Cockpit",
+ ),
+ (b"ZOOM_1X", "Sets zoom level to 1", "Shared Cockpit"),
+ (b"SOUND_TOGGLE", "Toggles sound on/off", "Shared Cockpit"),
+ (
+ b"SIM_RATE",
+ "Selects simulation rate (use KEY_MINUS, KEY_PLUS to change),",
+ "Shared Cockpit",
+ ),
+ (b"JOYSTICK_CALIBRATE", "Toggles joystick on/off", "Shared Cockpit"),
+ (b"SITUATION_SAVE", "Saves flight situation", "Shared Cockpit"),
+ (b"SITUATION_RESET", "Resets flight situation", "Shared Cockpit"),
+ (b"SOUND_SET", "Sets sound on/off (1,0),", "Shared Cockpit"),
+ (b"EXIT", "Quit ESP with a message", "Shared Cockpit"),
+ (b"ABORT", "Quit ESP without a message", "Shared Cockpit"),
+ (
+ b"READOUTS_SLEW",
+ "Cycle through information readouts while in slew",
+ "Shared Cockpit",
+ ),
+ (
+ b"READOUTS_FLIGHT",
+ "Cycle through information readouts",
+ "Shared Cockpit",
+ ),
+ (b"MINUS_SHIFT", "Used with other events", "Shared Cockpit"),
+ (b"PLUS_SHIFT", "Used with other events", "Shared Cockpit"),
+ (b"SIM_RATE_INCR", "Increase sim rate", "Shared Cockpit"),
+ (b"SIM_RATE_DECR", "Decrease sim rate", "Shared Cockpit"),
+ (b"KNEEBOARD_VIEW", "Toggles kneeboard", "Shared Cockpit"),
+ (b"PANEL_1", "Toggles panel 1", "Shared Cockpit"),
+ (b"PANEL_2", "Toggles panel 2", "Shared Cockpit"),
+ (b"PANEL_3", "Toggles panel 3", "Shared Cockpit"),
+ (b"PANEL_4", "Toggles panel 4", "Shared Cockpit"),
+ (b"PANEL_5", "Toggles panel 5", "Shared Cockpit"),
+ (b"PANEL_6", "Toggles panel 6", "Shared Cockpit"),
+ (b"PANEL_7", "Toggles panel 7", "Shared Cockpit"),
+ (b"PANEL_8", "Toggles panel 8", "Shared Cockpit"),
+ (b"PANEL_9", "Toggles panel 9", "Shared Cockpit"),
+ (b"SOUND_ON", "Turns sound on", "Shared Cockpit"),
+ (b"SOUND_OFF", "Turns sound off", "Shared Cockpit"),
+ (b"INVOKE_HELP", "Brings up Help system", "Shared Cockpit"),
+ (b"TOGGLE_AIRCRAFT_LABELS", "Toggles aircraft labels", "Shared Cockpit"),
+ (b"FLIGHT_MAP", "Brings up flight map", "Shared Cockpit"),
+ (b"RELOAD_PANELS", "Reload panel data", "Shared Cockpit"),
+ (b"PANEL_ID_TOGGLE", "Toggles indexed panel (1 to 9),", "Shared Cockpit"),
+ (b"PANEL_ID_OPEN", "Opens indexed panel (1 to 9),", "Shared Cockpit"),
+ (b"PANEL_ID_CLOSE", "Closes indexed panel (1 to 9),", "Shared Cockpit"),
+ (
+ b"RELOAD_USER_AIRCRAFT",
+ "Reloads the user aircraft data (from cache if same type loaded as an AI, otherwise from disk),",
+ "Shared Cockpit",
+ ),
+ (b"SIM_RESET", "Resets aircraft state", "Shared Cockpit"),
+ (b"VIRTUAL_COPILOT_TOGGLE", "Turns Flying Tips on/off", "Shared Cockpit"),
+ (
+ b"VIRTUAL_COPILOT_SET",
+ "Sets Flying Tips on/off (1,0),",
+ "Shared Cockpit",
+ ),
+ (
+ b"VIRTUAL_COPILOT_ACTION",
+ "Triggers action noted in Flying Tips",
+ "Shared Cockpit",
+ ),
+ (b"REFRESH_SCENERY", "Reloads scenery", "Shared Cockpit"),
+ (b"CLOCK_HOURS_DEC", "Decrements time by hours", "Shared Cockpit"),
+ (b"CLOCK_HOURS_INC", "Increments time by hours", "Shared Cockpit"),
+ (b"CLOCK_MINUTES_DEC", "Decrements time by minutes", "Shared Cockpit"),
+ (b"CLOCK_MINUTES_INC", "Increments time by minutes", "Shared Cockpit"),
+ (b"CLOCK_SECONDS_ZERO", "Zeros seconds", "Shared Cockpit"),
+ (b"CLOCK_HOURS_SET", "Sets hour of day", "Shared Cockpit"),
+ (b"CLOCK_MINUTES_SET", "Sets minutes of the hour", "Shared Cockpit"),
+ (b"ZULU_HOURS_SET", "Sets hours, zulu time", "Shared Cockpit"),
+ (b"ZULU_MINUTES_SET", "Sets minutes, in zulu time", "Shared Cockpit"),
+ (b"ZULU_DAY_SET", "Sets day, in zulu time", "Shared Cockpit"),
+ (b"ZULU_YEAR_SET", "Sets year, in zulu time", "Shared Cockpit"),
+ (
+ b"GAUGE_KEYSTROKE",
+ "Enables a keystroke to be sent to a gauge that is in focus. The keystrokes can only be in the range 0 to 9, A to Z, and the four keys: plus, minus, comma and period. This is typically used to allow some keyboard entry to a complex device such as a GPS to enter such things as ICAO codes using the keyboard, rather than turning dials.",
+ "Shared Cockpit",
+ ),
+ (b"SIMUI_WINDOW_HIDESHOW", "Display the ATC window.", "Shared Cockpit"),
+ (
+ b"VIEW_WINDOW_TITLES_TOGGLE",
+ "Turn window titles on or off.",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_PAN_PITCH",
+ "Sets the pitch of the axis. Requires an angle.",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_PAN_HEADING",
+ "Sets the heading of the axis. Requires an angle.",
+ "Shared Cockpit",
+ ),
+ (
+ b"AXIS_PAN_TILT",
+ "Sets the tilt of the axis. Requires an angle.",
+ "Shared Cockpit",
+ ),
+ (
+ b"VIEW_AXIS_INDICATOR_CYCLE",
+ "Step through the view axes.",
+ "Shared Cockpit",
+ ),
+ (
+ b"VIEW_MAP_ORIENTATION_CYCLE",
+ "Step through the map orientations.",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_JETWAY",
+ "Requests a jetway, which will only be answered if the aircraft is at a parking spot.",
+ "Shared Cockpit",
+ ),
+ (
+ b"VIDEO_RECORD_TOGGLE",
+ """Turn on or off the video recording feature. This records uncompressed AVI format files to:
+ My Documents\\My Videos\\""",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_AIRPORT_NAME_DISPLAY",
+ "Turn on or off the airport name.",
+ "Shared Cockpit",
+ ),
+ (
+ b"CAPTURE_SCREENSHOT",
+ """Capture the current view as a screenshot. Which will be saved to a bmp file in:
+ My Documents\\My Pictures\\""",
+ "Shared Cockpit",
+ ),
+ (
+ b"MOUSE_LOOK_TOGGLE",
+ "Switch Mouse Look mode on or off. Mouse Look mode enables a user to control their view using the mouse, and holding down the space bar.",
+ "Shared Cockpit",
+ ),
+ (
+ b"YAXIS_INVERT_TOGGLE",
+ "Switch inversion of Y axis controls on or off.",
+ "Shared Cockpit",
+ ),
+ (
+ b"AUTORUDDER_TOGGLE",
+ "Turn the automatic rudder control feature on or off.",
+ "Shared Cockpit",
+ ),
+ ]
- class __Freezing_position(EventHelper):
- list = [
- (b'FREEZE_LATITUDE_LONGITUDE_TOGGLE', '''Turns the freezing of the lat/lon position of the aircraft (either user or AI controlled), on or off. If this key event is set, it means that the latitude and longitude of the aircraft are not being controlled by ESP, so enabling, for example, a SimConnect client to control the position of the aircraft. This can also apply to altitude and attitude. Refer to the simulation variables:
+ class __Freezing_position(EventHelper):
+ list = [
+ (
+ b"FREEZE_LATITUDE_LONGITUDE_TOGGLE",
+ """Turns the freezing of the lat/lon position of the aircraft (either user or AI controlled), on or off. If this key event is set, it means that the latitude and longitude of the aircraft are not being controlled by ESP, so enabling, for example, a SimConnect client to control the position of the aircraft. This can also apply to altitude and attitude. Refer to the simulation variables:
IS LATITUDE LONGITUDE FREEZE ON,
IS ALTITUDE FREEZE ON, and
IS ATTITUDE FREEZE ON
- Refer also to the SimConnect_AIReleaseControl function. ''', "Shared Cockpit"),
- (b'FREEZE_LATITUDE_LONGITUDE_SET', "Freezes the lat/lon position of the aircraft.", "Shared Cockpit"),
- (b'FREEZE_ALTITUDE_TOGGLE', "Turns the freezing of the altitude of the aircraft on or off.", "Shared Cockpit"),
- (b'FREEZE_ALTITUDE_SET', "Freezes the altitude of the aircraft..", "Shared Cockpit"),
- (b'FREEZE_ATTITUDE_TOGGLE', "Turns the freezing of the attitude (pitch, bank and heading), of the aircraft on or off.", "Shared Cockpit"),
- (b'FREEZE_ATTITUDE_SET', "Freezes the attitude (pitch, bank and heading), of the aircraft.", "Shared Cockpit"),
- ]
+ Refer also to the SimConnect_AIReleaseControl function. """,
+ "Shared Cockpit",
+ ),
+ (
+ b"FREEZE_LATITUDE_LONGITUDE_SET",
+ "Freezes the lat/lon position of the aircraft.",
+ "Shared Cockpit",
+ ),
+ (
+ b"FREEZE_ALTITUDE_TOGGLE",
+ "Turns the freezing of the altitude of the aircraft on or off.",
+ "Shared Cockpit",
+ ),
+ (
+ b"FREEZE_ALTITUDE_SET",
+ "Freezes the altitude of the aircraft..",
+ "Shared Cockpit",
+ ),
+ (
+ b"FREEZE_ATTITUDE_TOGGLE",
+ "Turns the freezing of the attitude (pitch, bank and heading), of the aircraft on or off.",
+ "Shared Cockpit",
+ ),
+ (
+ b"FREEZE_ATTITUDE_SET",
+ "Freezes the attitude (pitch, bank and heading), of the aircraft.",
+ "Shared Cockpit",
+ ),
+ ]
- class __Mission_Keys(EventHelper):
- list = [
- (b'POINT_OF_INTEREST_TOGGLE_POINTER', "Turn the point-of-interest indicator (often a light beam), on or off. Refer to the Missions system documentation.", "Shared Cockpit"),
- (b'POINT_OF_INTEREST_CYCLE_PREVIOUS', "Change the current point-of-interest to the previous point-of-interest.", "Shared Cockpit"),
- (b'POINT_OF_INTEREST_CYCLE_NEXT', "Change the current point-of-interest to the next point-of-interest.", "Shared Cockpit"),
- ]
+ class __Mission_Keys(EventHelper):
+ list = [
+ (
+ b"POINT_OF_INTEREST_TOGGLE_POINTER",
+ "Turn the point-of-interest indicator (often a light beam), on or off. Refer to the Missions system documentation.",
+ "Shared Cockpit",
+ ),
+ (
+ b"POINT_OF_INTEREST_CYCLE_PREVIOUS",
+ "Change the current point-of-interest to the previous point-of-interest.",
+ "Shared Cockpit",
+ ),
+ (
+ b"POINT_OF_INTEREST_CYCLE_NEXT",
+ "Change the current point-of-interest to the next point-of-interest.",
+ "Shared Cockpit",
+ ),
+ ]
- class __ATC(EventHelper):
- list = [
- (b'ATC', "Activates ATC window", "Shared Cockpit"),
- (b'ATC_MENU_1', "Selects ATC option 1", "Shared Cockpit"),
- (b'ATC_MENU_2', "Selects ATC option 2", "Shared Cockpit"),
- (b'ATC_MENU_3', "Selects ATC option 3", "Shared Cockpit"),
- (b'ATC_MENU_4', "Selects ATC option 4", "Shared Cockpit"),
- (b'ATC_MENU_5', "Selects ATC option 5", "Shared Cockpit"),
- (b'ATC_MENU_6', "Selects ATC option 6", "Shared Cockpit"),
- (b'ATC_MENU_7', "Selects ATC option 7", "Shared Cockpit"),
- (b'ATC_MENU_8', "Selects ATC option 8", "Shared Cockpit"),
- (b'ATC_MENU_9', "Selects ATC option 9", "Shared Cockpit"),
- (b'ATC_MENU_0', "Selects ATC option 10", "Shared Cockpit"),
- ]
+ class __ATC(EventHelper):
+ list = [
+ (b"ATC", "Activates ATC window", "Shared Cockpit"),
+ (b"ATC_MENU_1", "Selects ATC option 1", "Shared Cockpit"),
+ (b"ATC_MENU_2", "Selects ATC option 2", "Shared Cockpit"),
+ (b"ATC_MENU_3", "Selects ATC option 3", "Shared Cockpit"),
+ (b"ATC_MENU_4", "Selects ATC option 4", "Shared Cockpit"),
+ (b"ATC_MENU_5", "Selects ATC option 5", "Shared Cockpit"),
+ (b"ATC_MENU_6", "Selects ATC option 6", "Shared Cockpit"),
+ (b"ATC_MENU_7", "Selects ATC option 7", "Shared Cockpit"),
+ (b"ATC_MENU_8", "Selects ATC option 8", "Shared Cockpit"),
+ (b"ATC_MENU_9", "Selects ATC option 9", "Shared Cockpit"),
+ (b"ATC_MENU_0", "Selects ATC option 10", "Shared Cockpit"),
+ ]
- class __Multiplayer(EventHelper):
- list = [
- (b'MP_TRANSFER_CONTROL', "Toggle to the next player to track", "-"),
- (b'MP_PLAYER_CYCLE', "Cycle through the current user aircraft.", "Shared Cockpit"),
- (b'MP_PLAYER_FOLLOW', "Set the view to follow the selected user aircraft.", "Shared Cockpit"),
- (b'MP_CHAT', "Toggles chat window visible/invisible", "Shared Cockpit"),
- (b'MP_ACTIVATE_CHAT', "Activates chat window", "Shared Cockpit"),
- (b'MP_VOICE_CAPTURE_START', "Start capturing audio from the users computer and transmitting it to all other players in the multiplayer session who are turned to the same radio frequency.", "Shared Cockpit"),
- (b'MP_VOICE_CAPTURE_STOP', "Stop capturing radio audio.", "Shared Cockpit"),
- (b'MP_BROADCAST_VOICE_CAPTURE_START', "Start capturing audio from the users computer and transmitting it to all other players in the multiplayer session.", "Shared Cockpit"),
- (b'MP_BROADCAST_VOICE_CAPTURE_STOP', "Stop capturing broadcast audio.", "Shared Cockpit"),
- (b'TOGGLE_RACERESULTS_WINDOW', "Show or hide multi-player race results.", "Disabled"),
- ]
+ class __Multiplayer(EventHelper):
+ list = [
+ (b"MP_TRANSFER_CONTROL", "Toggle to the next player to track", "-"),
+ (
+ b"MP_PLAYER_CYCLE",
+ "Cycle through the current user aircraft.",
+ "Shared Cockpit",
+ ),
+ (
+ b"MP_PLAYER_FOLLOW",
+ "Set the view to follow the selected user aircraft.",
+ "Shared Cockpit",
+ ),
+ (b"MP_CHAT", "Toggles chat window visible/invisible", "Shared Cockpit"),
+ (b"MP_ACTIVATE_CHAT", "Activates chat window", "Shared Cockpit"),
+ (
+ b"MP_VOICE_CAPTURE_START",
+ "Start capturing audio from the users computer and transmitting it to all other players in the multiplayer session who are turned to the same radio frequency.",
+ "Shared Cockpit",
+ ),
+ (b"MP_VOICE_CAPTURE_STOP", "Stop capturing radio audio.", "Shared Cockpit"),
+ (
+ b"MP_BROADCAST_VOICE_CAPTURE_START",
+ "Start capturing audio from the users computer and transmitting it to all other players in the multiplayer session.",
+ "Shared Cockpit",
+ ),
+ (
+ b"MP_BROADCAST_VOICE_CAPTURE_STOP",
+ "Stop capturing broadcast audio.",
+ "Shared Cockpit",
+ ),
+ (
+ b"TOGGLE_RACERESULTS_WINDOW",
+ "Show or hide multi-player race results.",
+ "Disabled",
+ ),
+ ]
- class __G1000_PFD(EventHelper):
- list = [
- (b'G1000_PFD_FLIGHTPLAN_BUTTON', "The primary flight display (PFD) should display its current flight plan.", "Shared Cockpit"),
- (b'G1000_PFD_PROCEDURE_BUTTON', "Turn to the Procedure page.", "Shared Cockpit"),
- (b'G1000_PFD_ZOOMIN_BUTTON', "Zoom in on the current map.", "Shared Cockpit"),
- (b'G1000_PFD_ZOOMOUT_BUTTON', "Zoom out on the current map.", "Shared Cockpit"),
- (b'G1000_PFD_DIRECTTO_BUTTON', "Turn to the Direct To page.", "Shared Cockpit"),
- (b'G1000_PFD_MENU_BUTTON', "If a segmented flight plan is highlighted, activates the associated menu.", "Shared Cockpit"),
- (b'G1000_PFD_CLEAR_BUTTON', "Clears the current input.", "Shared Cockpit"),
- (b'G1000_PFD_ENTER_BUTTON', "Enters the current input.", "Shared Cockpit"),
- (b'G1000_PFD_CURSOR_BUTTON', "Turns on or off a screen cursor.", "Shared Cockpit"),
- (b'G1000_PFD_GROUP_KNOB_INC', "Step up through the page groups.", "Shared Cockpit"),
- (b'G1000_PFD_GROUP_KNOB_DEC', "Step down through the page groups.", "Shared Cockpit"),
- (b'G1000_PFD_PAGE_KNOB_INC', "Step up through the individual pages.", "Shared Cockpit"),
- (b'G1000_PFD_PAGE_KNOB_DEC', "Step down through the individual pages.", "Shared Cockpit"),
- ]
- # G1000_PFD_SOFTKEY1, G1000_PFD_SOFTKEY12 Initiate the action for the icon displayed in the softkey position. Shared Cockpit
+ class __G1000_PFD(EventHelper):
+ list = [
+ (
+ b"G1000_PFD_FLIGHTPLAN_BUTTON",
+ "The primary flight display (PFD) should display its current flight plan.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_PFD_PROCEDURE_BUTTON",
+ "Turn to the Procedure page.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_PFD_ZOOMIN_BUTTON",
+ "Zoom in on the current map.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_PFD_ZOOMOUT_BUTTON",
+ "Zoom out on the current map.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_PFD_DIRECTTO_BUTTON",
+ "Turn to the Direct To page.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_PFD_MENU_BUTTON",
+ "If a segmented flight plan is highlighted, activates the associated menu.",
+ "Shared Cockpit",
+ ),
+ (b"G1000_PFD_CLEAR_BUTTON", "Clears the current input.", "Shared Cockpit"),
+ (b"G1000_PFD_ENTER_BUTTON", "Enters the current input.", "Shared Cockpit"),
+ (
+ b"G1000_PFD_CURSOR_BUTTON",
+ "Turns on or off a screen cursor.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_PFD_GROUP_KNOB_INC",
+ "Step up through the page groups.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_PFD_GROUP_KNOB_DEC",
+ "Step down through the page groups.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_PFD_PAGE_KNOB_INC",
+ "Step up through the individual pages.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_PFD_PAGE_KNOB_DEC",
+ "Step down through the individual pages.",
+ "Shared Cockpit",
+ ),
+ ]
+ # G1000_PFD_SOFTKEY1, G1000_PFD_SOFTKEY12 Initiate the action for the icon displayed in the softkey position. Shared Cockpit
- class __G1000_MFD(EventHelper):
- list = [
- (b'G1000_MFD_FLIGHTPLAN_BUTTON', "The multifunction display (MFD) should display its current flight plan.", "Shared Cockpit"),
- (b'G1000_MFD_PROCEDURE_BUTTON', "Turn to the Procedure page.", "Shared Cockpit"),
- (b'G1000_MFD_ZOOMIN_BUTTON', "Zoom in on the current map.", "Shared Cockpit"),
- (b'G1000_MFD_ZOOMOUT_BUTTON', "Zoom out on the current map.", "Shared Cockpit"),
- (b'G1000_MFD_DIRECTTO_BUTTON', "Turn to the Direct To page.", "Shared Cockpit"),
- (b'G1000_MFD_MENU_BUTTON', "If a segmented flight plan is highlighted, activates the associated menu.", "Shared Cockpit"),
- (b'G1000_MFD_CLEAR_BUTTON', "Clears the current input.", "Shared Cockpit"),
- (b'G1000_MFD_ENTER_BUTTON', "Enters the current input.", "Shared Cockpit"),
- (b'G1000_MFD_CURSOR_BUTTON', "Turns on or off a screen cursor.", "Shared Cockpit"),
- (b'G1000_MFD_GROUP_KNOB_INC', "Step up through the page groups.", "Shared Cockpit"),
- (b'G1000_MFD_GROUP_KNOB_DEC', "Step down through the page groups.", "Shared Cockpit"),
- (b'G1000_MFD_PAGE_KNOB_INC', "Step up through the individual pages.", "Shared Cockpit"),
- (b'G1000_MFD_PAGE_KNOB_DEC', "Step down through the individual pages.", "Shared Cockpit"),
- ]
- # G1000_MFD_SOFTKEY1, G1000_MFD_SOFTKEY12 Initiate the action for the icon displayed in the softkey position. Shared Cockpit
+ class __G1000_MFD(EventHelper):
+ list = [
+ (
+ b"G1000_MFD_FLIGHTPLAN_BUTTON",
+ "The multifunction display (MFD) should display its current flight plan.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_MFD_PROCEDURE_BUTTON",
+ "Turn to the Procedure page.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_MFD_ZOOMIN_BUTTON",
+ "Zoom in on the current map.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_MFD_ZOOMOUT_BUTTON",
+ "Zoom out on the current map.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_MFD_DIRECTTO_BUTTON",
+ "Turn to the Direct To page.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_MFD_MENU_BUTTON",
+ "If a segmented flight plan is highlighted, activates the associated menu.",
+ "Shared Cockpit",
+ ),
+ (b"G1000_MFD_CLEAR_BUTTON", "Clears the current input.", "Shared Cockpit"),
+ (b"G1000_MFD_ENTER_BUTTON", "Enters the current input.", "Shared Cockpit"),
+ (
+ b"G1000_MFD_CURSOR_BUTTON",
+ "Turns on or off a screen cursor.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_MFD_GROUP_KNOB_INC",
+ "Step up through the page groups.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_MFD_GROUP_KNOB_DEC",
+ "Step down through the page groups.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_MFD_PAGE_KNOB_INC",
+ "Step up through the individual pages.",
+ "Shared Cockpit",
+ ),
+ (
+ b"G1000_MFD_PAGE_KNOB_DEC",
+ "Step down through the individual pages.",
+ "Shared Cockpit",
+ ),
+ ]
+ # G1000_MFD_SOFTKEY1, G1000_MFD_SOFTKEY12 Initiate the action for the icon displayed in the softkey position. Shared Cockpit
diff --git a/action_plugins/map_to_simconnect/SimConnect/FacilitiesList.py b/action_plugins/map_to_simconnect/SimConnect/FacilitiesList.py
index 85fb4bae..9d0cd5e9 100644
--- a/action_plugins/map_to_simconnect/SimConnect/FacilitiesList.py
+++ b/action_plugins/map_to_simconnect/SimConnect/FacilitiesList.py
@@ -4,109 +4,128 @@
class Facilitie(object):
- def __init__(self):
- pass
+ def __init__(self):
+ pass
class FacilitiesHelper:
- def __init__(self, _sm, _parent):
- self.sm = _sm
- self.parent = _parent
- self.REQUEST_ID = _sm.new_request_id()
- self.item = None
- self.sm.Facilities.append(self)
-
- def subscribe(self, _cbfunc):
- if self.item < SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_COUNT:
- self.cb = _cbfunc
- hr = self.sm.dll.SubscribeToFacilities(
- self.sm.hSimConnect,
- SIMCONNECT_FACILITY_LIST_TYPE(self.item),
- self.REQUEST_ID.value
- )
-
- def unsubscribe(self):
- self.cb = None
- hr = self.sm.dll.UnsubscribeToFacilities(
- self.sm.hSimConnect,
- SIMCONNECT_FACILITY_LIST_TYPE(self.item)
- )
-
- def get(self):
- # Get the current cached list of airports, waypoints, etc, as the item indicates
- if self.item < SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_COUNT:
- hr = self.sm.dll.RequestFacilitiesList(
- self.sm.hSimConnect,
- SIMCONNECT_FACILITY_LIST_TYPE(self.item),
- self.REQUEST_ID.value
- )
- # self.sm.run()
-
-
-class FacilitiesRequests():
- def __init__(self, _sm):
- self.sm = _sm
- self.list = []
- self.Airports = self.__FACILITY_AIRPORT(_sm, self)
- self.list.append(self.Airports)
- self.Waypoints = self.__FACILITY_WAYPOINT(_sm, self)
- self.list.append(self.Waypoints)
- self.NDBs = self.__FACILITY_NDB(_sm, self)
- self.list.append(self.NDBs)
- self.VORs = self.__FACILITY_VOR(_sm, self)
- self.list.append(self.VORs)
-
- def dump(self, pList):
- pList = cast(pList, POINTER(SIMCONNECT_RECV_FACILITIES_LIST))
- List = pList.contents
- print("RequestID: %d dwArraySize: %d dwEntryNumber: %d dwOutOf: %d" % (
- List.dwRequestID, List.dwArraySize, List.dwEntryNumber, List.dwOutOf)
- )
-
- # Dump various facility elements
- class __FACILITY_AIRPORT(FacilitiesHelper):
- def __init__(self, _sm, _parent):
- super().__init__(_sm, _parent)
- self.item = SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT
-
- def dump(self, pFac):
- pFac = cast(pFac, POINTER(SIMCONNECT_DATA_FACILITY_AIRPORT))
- Fac = pFac.contents
- print("Icao: %s Latitude: %lg Longitude: %lg Altitude: %lg" % (
- Fac.Icao.decode(), Fac.Latitude, Fac.Longitude, Fac.Altitude)
- )
-
- class __FACILITY_WAYPOINT(FacilitiesHelper):
- def __init__(self, _sm, _parent):
- super().__init__(_sm, _parent)
- self.item = SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_WAYPOINT
-
- def dump(self, pFac):
- pFac = cast(pFac, POINTER(SIMCONNECT_DATA_FACILITY_WAYPOINT))
- Fac = pFac.contents
- self.parent.Airports.dump(pFac)
- print("\tfMagVar: %g" % (Fac.fMagVar))
-
- class __FACILITY_NDB(FacilitiesHelper):
- def __init__(self, _sm, _parent):
- super().__init__(_sm, _parent)
- self.item = SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_NDB
-
- def dump(self, pFac):
- pFac = cast(pFac, POINTER(SIMCONNECT_DATA_FACILITY_NDB))
- Fac = pFac.contents
- self.parent.Waypoints.dump(pFac)
- print("\t\tfFrequency: %d" % (Fac.fFrequency))
-
- class __FACILITY_VOR(FacilitiesHelper):
- def __init__(self, _sm, _parent):
- super().__init__(_sm, _parent)
- self.item = SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_VOR
-
- def dump(self, pFac):
- pFac = cast(pFac, POINTER(SIMCONNECT_DATA_FACILITY_VOR))
- Fac = pFac.contents
- self.parent.NDBs.dump(pFac)
- print("\t\t\tFlags: %x fLocalizer: %f GlideLat: %lg GlideLon: %lg GlideAlt: %lg fGlideSlopeAngle: %f" % (
- Fac.Flags, Fac.fLocalizer, Fac.GlideLat, Fac.GlideLon, Fac.GlideAlt, Fac.fGlideSlopeAngle)
- )
+ def __init__(self, _sm, _parent):
+ self.sm = _sm
+ self.parent = _parent
+ self.REQUEST_ID = _sm.new_request_id()
+ self.item = None
+ self.sm.Facilities.append(self)
+
+ def subscribe(self, _cbfunc):
+ if (
+ self.item
+ < SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_COUNT
+ ):
+ self.cb = _cbfunc
+ self.sm.dll.SubscribeToFacilities(
+ self.sm.hSimConnect,
+ SIMCONNECT_FACILITY_LIST_TYPE(self.item),
+ self.REQUEST_ID.value,
+ )
+
+ def unsubscribe(self):
+ self.cb = None
+ self.sm.dll.UnsubscribeToFacilities(
+ self.sm.hSimConnect, SIMCONNECT_FACILITY_LIST_TYPE(self.item)
+ )
+
+ def get(self):
+ # Get the current cached list of airports, waypoints, etc, as the item indicates
+ if (
+ self.item
+ < SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_COUNT
+ ):
+ self.sm.dll.RequestFacilitiesList(
+ self.sm.hSimConnect,
+ SIMCONNECT_FACILITY_LIST_TYPE(self.item),
+ self.REQUEST_ID.value,
+ )
+ # self.sm.run()
+
+
+class FacilitiesRequests:
+ def __init__(self, _sm):
+ self.sm = _sm
+ self.list = []
+ self.Airports = self.__FACILITY_AIRPORT(_sm, self)
+ self.list.append(self.Airports)
+ self.Waypoints = self.__FACILITY_WAYPOINT(_sm, self)
+ self.list.append(self.Waypoints)
+ self.NDBs = self.__FACILITY_NDB(_sm, self)
+ self.list.append(self.NDBs)
+ self.VORs = self.__FACILITY_VOR(_sm, self)
+ self.list.append(self.VORs)
+
+ def dump(self, pList):
+ pList = cast(pList, POINTER(SIMCONNECT_RECV_FACILITIES_LIST))
+ List = pList.contents
+ print(
+ "RequestID: %d dwArraySize: %d dwEntryNumber: %d dwOutOf: %d"
+ % (List.dwRequestID, List.dwArraySize, List.dwEntryNumber, List.dwOutOf)
+ )
+
+ # Dump various facility elements
+ class __FACILITY_AIRPORT(FacilitiesHelper):
+ def __init__(self, _sm, _parent):
+ super().__init__(_sm, _parent)
+ self.item = (
+ SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT
+ )
+
+ def dump(self, pFac):
+ pFac = cast(pFac, POINTER(SIMCONNECT_DATA_FACILITY_AIRPORT))
+ Fac = pFac.contents
+ print(
+ "Icao: %s Latitude: %lg Longitude: %lg Altitude: %lg"
+ % (Fac.Icao.decode(), Fac.Latitude, Fac.Longitude, Fac.Altitude)
+ )
+
+ class __FACILITY_WAYPOINT(FacilitiesHelper):
+ def __init__(self, _sm, _parent):
+ super().__init__(_sm, _parent)
+ self.item = (
+ SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_WAYPOINT
+ )
+
+ def dump(self, pFac):
+ pFac = cast(pFac, POINTER(SIMCONNECT_DATA_FACILITY_WAYPOINT))
+ Fac = pFac.contents
+ self.parent.Airports.dump(pFac)
+ print("\tfMagVar: %g" % (Fac.fMagVar))
+
+ class __FACILITY_NDB(FacilitiesHelper):
+ def __init__(self, _sm, _parent):
+ super().__init__(_sm, _parent)
+ self.item = SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_NDB
+
+ def dump(self, pFac):
+ pFac = cast(pFac, POINTER(SIMCONNECT_DATA_FACILITY_NDB))
+ Fac = pFac.contents
+ self.parent.Waypoints.dump(pFac)
+ print("\t\tfFrequency: %d" % (Fac.fFrequency))
+
+ class __FACILITY_VOR(FacilitiesHelper):
+ def __init__(self, _sm, _parent):
+ super().__init__(_sm, _parent)
+ self.item = SIMCONNECT_FACILITY_LIST_TYPE.SIMCONNECT_FACILITY_LIST_TYPE_VOR
+
+ def dump(self, pFac):
+ pFac = cast(pFac, POINTER(SIMCONNECT_DATA_FACILITY_VOR))
+ Fac = pFac.contents
+ self.parent.NDBs.dump(pFac)
+ print(
+ "\t\t\tFlags: %x fLocalizer: %f GlideLat: %lg GlideLon: %lg GlideAlt: %lg fGlideSlopeAngle: %f"
+ % (
+ Fac.Flags,
+ Fac.fLocalizer,
+ Fac.GlideLat,
+ Fac.GlideLon,
+ Fac.GlideAlt,
+ Fac.fGlideSlopeAngle,
+ )
+ )
diff --git a/action_plugins/map_to_simconnect/SimConnect/RequestList.py b/action_plugins/map_to_simconnect/SimConnect/RequestList.py
index 111fc7dd..b5d50cf7 100644
--- a/action_plugins/map_to_simconnect/SimConnect/RequestList.py
+++ b/action_plugins/map_to_simconnect/SimConnect/RequestList.py
@@ -4,1033 +4,4766 @@
from gremlin.singleton_decorator import SingletonDecorator
import logging
+
syslog = logging.getLogger("system")
+
@SingletonDecorator
-class AircraftRequests():
- def find(self, key):
- index = None
- if ':' in key:
- (keyname, index) = key.split(":", 1)
- key = f"{keyname}:index"
+class AircraftRequests:
+ def find(self, key):
+ index = None
+ if ":" in key:
+ (keyname, index) = key.split(":", 1)
+ key = f"{keyname}:index"
+
+ for item in self.list:
+ if key in item.list:
+ request = getattr(item, key)
+ if index is not None:
+ request.setIndex(index)
+ return request
+ return None
+
+ def request(self, key):
+ """gets the request object for this aircraft request"""
+ _request = self.find(key)
+ if _request is not None:
+ _request._ensure_def()
+ return _request
+ return None
- for item in self.list:
- if key in item.list:
- request = getattr(item, key)
- if index is not None:
- request.setIndex(index)
- return request
- return None
-
- def request(self, key):
- ''' gets the request object for this aircraft request '''
- _request = self.find(key)
- if _request is not None:
- _request._ensure_def()
- return _request
- return None
+ def get(self, key):
+ """gets the value of the current request"""
+ request = self.request(key)
+ if request is None:
+ return None
+ self.sm.get_data(request)
+ return request.value
- def get(self, key):
- ''' gets the value of the current request '''
- request = self.request(key)
- if request is None:
- return None
- self.sm.get_data(request)
- return request.value
+ def set(self, key, _value):
+ """sets the value of the current request"""
+ request = self.request(key)
+ if request is None:
+ syslog.warning(f"Simconnect: Request: key {key} not found in request list")
+ return False
- def set(self, key, _value):
- ''' sets the value of the current request '''
- request = self.request(key)
- if request is None:
- syslog.warning(f"Simconnect: Request: key {key} not found in request list")
- return False
-
- self.sm.set_data(request)
-
- return True
+ self.sm.set_data(request)
- def __init__(self, sm : SimConnect, time=10, attempts=10, on_change = False):
- self.sm = sm
- self.list = []
- self.EngineData = self.__AircraftEngineData(sm, time, attempts, on_change)
- self.list.append(self.EngineData)
- self.FuelTankSelection = self.__FuelTankSelection(sm, time, attempts, on_change)
- self.list.append(self.FuelTankSelection)
- self.FuelData = self.__AircraftFuelData(sm, time, attempts, on_change)
- self.list.append(self.FuelData)
- self.LightsData = self.__AircraftLightsData(sm, time, attempts, on_change)
- self.list.append(self.LightsData)
- self.PositionandSpeedData = self.__AircraftPositionandSpeedData(sm, time, attempts, on_change)
- self.list.append(self.PositionandSpeedData)
- self.FlightInstrumentationData = self.__AircraftFlightInstrumentationData(sm, time, attempts, on_change)
- self.list.append(self.FlightInstrumentationData)
- self.AvionicsData = self.__AircraftAvionicsData(sm, time, attempts, on_change)
- self.list.append(self.AvionicsData)
- self.ControlsData = self.__AircraftControlsData(sm, time, attempts, on_change)
- self.list.append(self.ControlsData)
- self.AutopilotData = self.__AircraftAutopilotData(sm, time, attempts, on_change)
- self.list.append(self.AutopilotData)
- self.LandingGearData = self.__AircraftLandingGearData(sm, time, attempts, on_change)
- self.list.append(self.LandingGearData)
- self.AircraftEnvironmentData = self.__AircraftEnvironmentData(sm, time, attempts, on_change)
- self.list.append(self.AircraftEnvironmentData)
- self.HelicopterSpecificData = self.__HelicopterSpecificData(sm, time, attempts, on_change)
- self.list.append(self.HelicopterSpecificData)
- self.MiscellaneousSystemsData = self.__AircraftMiscellaneousSystemsData(sm, time, attempts, on_change)
- self.list.append(self.MiscellaneousSystemsData)
- self.MiscellaneousData = self.__AircraftMiscellaneousData(sm, time, attempts, on_change)
- self.list.append(self.MiscellaneousData)
- self.StringData = self.__AircraftStringData(sm, time, attempts, on_change)
- self.list.append(self.StringData)
- self.AIControlledAircraft = self.__AIControlledAircraft(sm, time, attempts, on_change)
- self.list.append(self.AIControlledAircraft)
- self.CarrierOperations = self.__CarrierOperations(sm, time, attempts, on_change)
- self.list.append(self.CarrierOperations)
- self.Racing = self.__Racing(sm, time, attempts, on_change)
- self.list.append(self.Racing)
- self.EnvironmentData = self.__EnvironmentData(sm, time, attempts, on_change)
- self.list.append(self.EnvironmentData)
- self.SlingsandHoists = self.__SlingsandHoists(sm, time, attempts, on_change)
- self.list.append(self.SlingsandHoists)
+ return True
+ def __init__(self, sm: SimConnect, time=10, attempts=10, on_change=False):
+ self.sm = sm
+ self.list = []
+ self.EngineData = self.__AircraftEngineData(sm, time, attempts, on_change)
+ self.list.append(self.EngineData)
+ self.FuelTankSelection = self.__FuelTankSelection(sm, time, attempts, on_change)
+ self.list.append(self.FuelTankSelection)
+ self.FuelData = self.__AircraftFuelData(sm, time, attempts, on_change)
+ self.list.append(self.FuelData)
+ self.LightsData = self.__AircraftLightsData(sm, time, attempts, on_change)
+ self.list.append(self.LightsData)
+ self.PositionandSpeedData = self.__AircraftPositionandSpeedData(
+ sm, time, attempts, on_change
+ )
+ self.list.append(self.PositionandSpeedData)
+ self.FlightInstrumentationData = self.__AircraftFlightInstrumentationData(
+ sm, time, attempts, on_change
+ )
+ self.list.append(self.FlightInstrumentationData)
+ self.AvionicsData = self.__AircraftAvionicsData(sm, time, attempts, on_change)
+ self.list.append(self.AvionicsData)
+ self.ControlsData = self.__AircraftControlsData(sm, time, attempts, on_change)
+ self.list.append(self.ControlsData)
+ self.AutopilotData = self.__AircraftAutopilotData(sm, time, attempts, on_change)
+ self.list.append(self.AutopilotData)
+ self.LandingGearData = self.__AircraftLandingGearData(
+ sm, time, attempts, on_change
+ )
+ self.list.append(self.LandingGearData)
+ self.AircraftEnvironmentData = self.__AircraftEnvironmentData(
+ sm, time, attempts, on_change
+ )
+ self.list.append(self.AircraftEnvironmentData)
+ self.HelicopterSpecificData = self.__HelicopterSpecificData(
+ sm, time, attempts, on_change
+ )
+ self.list.append(self.HelicopterSpecificData)
+ self.MiscellaneousSystemsData = self.__AircraftMiscellaneousSystemsData(
+ sm, time, attempts, on_change
+ )
+ self.list.append(self.MiscellaneousSystemsData)
+ self.MiscellaneousData = self.__AircraftMiscellaneousData(
+ sm, time, attempts, on_change
+ )
+ self.list.append(self.MiscellaneousData)
+ self.StringData = self.__AircraftStringData(sm, time, attempts, on_change)
+ self.list.append(self.StringData)
+ self.AIControlledAircraft = self.__AIControlledAircraft(
+ sm, time, attempts, on_change
+ )
+ self.list.append(self.AIControlledAircraft)
+ self.CarrierOperations = self.__CarrierOperations(sm, time, attempts, on_change)
+ self.list.append(self.CarrierOperations)
+ self.Racing = self.__Racing(sm, time, attempts, on_change)
+ self.list.append(self.Racing)
+ self.EnvironmentData = self.__EnvironmentData(sm, time, attempts, on_change)
+ self.list.append(self.EnvironmentData)
+ self.SlingsandHoists = self.__SlingsandHoists(sm, time, attempts, on_change)
+ self.list.append(self.SlingsandHoists)
- class __AircraftEngineData(RequestHelper):
- list = {
- "NUMBER_OF_ENGINES": ["Number of engines (minimum 0, maximum 4)", b'NUMBER OF ENGINES', b'Number', 'N'],
- "ENGINE_CONTROL_SELECT": ["Selected engines (combination of bit flags); 1 = Engine 1; 2 = Engine 2; 4 = Engine 3; 8 = Engine 4", b'ENGINE CONTROL SELECT', b'Mask', 'Y'],
- "THROTTLE_LOWER_LIMIT": ["Percent throttle defining lower limit (negative for reverse thrust equipped airplanes)", b'THROTTLE LOWER LIMIT', b'Percent', 'N'],
- "ENGINE_TYPE": ["Engine type:; 0 = Piston; 1 = Jet; 2 = None; 3 = Helo(Bell) turbine; 4 = Unsupported; 5 = Turboprop", b'ENGINE TYPE', b'Enum', 'N'],
- "MASTER_IGNITION_SWITCH": ["Aircraft master ignition switch (grounds all engines magnetos)", b'MASTER IGNITION SWITCH', b'Bool', 'N'],
- "GENERAL_ENG_COMBUSTION:index": ["Combustion flag", b'GENERAL ENG COMBUSTION:index', b'Bool', 'Y'],
- "GENERAL_ENG_MASTER_ALTERNATOR:index": ["Alternator (generator) switch", b'GENERAL ENG MASTER ALTERNATOR:index', b'Bool', 'N'],
- "GENERAL_ENG_FUEL_PUMP_SWITCH:index": ["Fuel pump switch", b'GENERAL ENG FUEL PUMP SWITCH:index', b'Bool', 'N'],
- "GENERAL_ENG_FUEL_PUMP_ON:index": ["Fuel pump on/off", b'GENERAL ENG FUEL PUMP ON:index', b'Bool', 'N'],
- "GENERAL_ENG_RPM:index": ["Engine rpm", b'GENERAL ENG RPM:index', b'Rpm', 'N'],
- "GENERAL_ENG_PCT_MAX_RPM:index": ["Percent of max rated rpm", b'GENERAL ENG PCT MAX RPM:index', b'Percent', 'N'],
- "GENERAL_ENG_MAX_REACHED_RPM:index": ["Maximum attained rpm", b'GENERAL ENG MAX REACHED RPM:index', b'Rpm', 'N'],
- "GENERAL_ENG_THROTTLE_LEVER_POSITION:index": ["Percent of max throttle position", b'GENERAL ENG THROTTLE LEVER POSITION:index', b'Percent', 'Y'],
- "GENERAL_ENG_MIXTURE_LEVER_POSITION:index": ["Percent of max mixture lever position", b'GENERAL ENG MIXTURE LEVER POSITION:index', b'Percent', 'Y'],
- "GENERAL_ENG_PROPELLER_LEVER_POSITION:index": ["Percent of max prop lever position", b'GENERAL ENG PROPELLER LEVER POSITION:index', b'Percent', 'Y'],
- "GENERAL_ENG_STARTER:index": ["Engine starter on/off", b'GENERAL ENG STARTER:index', b'Bool', 'N'],
- "GENERAL_ENG_EXHAUST_GAS_TEMPERATURE:index": ["Engine exhaust gas temperature.", b'GENERAL ENG EXHAUST GAS TEMPERATURE:index', b'Rankine', 'Y'],
- "GENERAL_ENG_OIL_PRESSURE:index": ["Engine oil pressure", b'GENERAL ENG OIL PRESSURE:index', b'Psf', 'Y'],
- "GENERAL_ENG_OIL_LEAKED_PERCENT:index": ["Percent of max oil capacity leaked", b'GENERAL ENG OIL LEAKED PERCENT:index', b'Percent', 'N'],
- "GENERAL_ENG_COMBUSTION_SOUND_PERCENT:index": ["Percent of maximum engine sound", b'GENERAL ENG COMBUSTION SOUND PERCENT:index', b'Percent', 'N'],
- "GENERAL_ENG_DAMAGE_PERCENT:index": ["Percent of total engine damage", b'GENERAL ENG DAMAGE PERCENT:index', b'Percent', 'N'],
- "GENERAL_ENG_OIL_TEMPERATURE:index": ["Engine oil temperature", b'GENERAL ENG OIL TEMPERATURE:index', b'Rankine', 'Y'],
- "GENERAL_ENG_FAILED:index": ["Fail flag", b'GENERAL ENG FAILED:index', b'Bool', 'N'],
- "GENERAL_ENG_GENERATOR_SWITCH:index": ["Alternator (generator) switch", b'GENERAL ENG GENERATOR SWITCH:index', b'Bool', 'N'],
- "GENERAL_ENG_GENERATOR_ACTIVE:index": ["Alternator (generator) on/off", b'GENERAL ENG GENERATOR ACTIVE:index', b'Bool', 'Y'],
- "GENERAL_ENG_ANTI_ICE_POSITION:index": ["Engine anti-ice switch", b'GENERAL ENG ANTI ICE POSITION:index', b'Bool', 'N'],
- "GENERAL_ENG_FUEL_VALVE:index": ["Fuel valve state", b'GENERAL ENG FUEL VALVE:index', b'Bool', 'N'],
- "GENERAL_ENG_FUEL_PRESSURE:index": ["Engine fuel pressure", b'GENERAL ENG FUEL PRESSURE:index', b'Psi', 'Y'],
- "GENERAL_ENG_ELAPSED_TIME:index": ["Total engine elapsed time", b'GENERAL ENG ELAPSED TIME:index', b'Hours', 'N'],
- "RECIP_ENG_COWL_FLAP_POSITION:index": ["Percent cowl flap opened", b'RECIP ENG COWL FLAP POSITION:index', b'Percent', 'Y'],
- "RECIP_ENG_PRIMER:index": ["Engine primer position", b'RECIP ENG PRIMER:index', b'Bool', 'Y'],
- "RECIP_ENG_MANIFOLD_PRESSURE:index": ["Engine manifold pressure", b'RECIP ENG MANIFOLD PRESSURE:index', b'Psi', 'Y'],
- "RECIP_ENG_ALTERNATE_AIR_POSITION:index": ["Alternate air control", b'RECIP ENG ALTERNATE AIR POSITION:index', b'Position', 'Y'],
- "RECIP_ENG_COOLANT_RESERVOIR_PERCENT:index": ["Percent coolant available", b'RECIP ENG COOLANT RESERVOIR PERCENT:index', b'Percent', 'Y'],
- "RECIP_ENG_LEFT_MAGNETO:index": ["Left magneto state", b'RECIP ENG LEFT MAGNETO:index', b'Bool', 'Y'],
- "RECIP_ENG_RIGHT_MAGNETO:index": ["Right magneto state", b'RECIP ENG RIGHT MAGNETO:index', b'Bool', 'Y'],
- "RECIP_ENG_BRAKE_POWER:index": ["Brake power produced by engine", b'RECIP ENG BRAKE POWER:index', b'Foot pounds per second', 'Y'],
- "RECIP_ENG_STARTER_TORQUE:index": ["Torque produced by engine", b'RECIP ENG STARTER TORQUE:index', b'Foot pound', 'Y'],
- "RECIP_ENG_TURBOCHARGER_FAILED:index": ["Turbo failed state", b'RECIP ENG TURBOCHARGER FAILED:index', b'Bool', 'Y'],
- "RECIP_ENG_EMERGENCY_BOOST_ACTIVE:index": ["War emergency power active", b'RECIP ENG EMERGENCY BOOST ACTIVE:index', b'Bool', 'Y'],
- "RECIP_ENG_EMERGENCY_BOOST_ELAPSED_TIME:index": ["Elapsed time war emergency power active", b'RECIP ENG EMERGENCY BOOST ELAPSED TIME:index', b'Hours', 'Y'],
- "RECIP_ENG_WASTEGATE_POSITION:index": ["Percent turbo wastegate closed", b'RECIP ENG WASTEGATE POSITION:index', b'Percent', 'Y'],
- "RECIP_ENG_TURBINE_INLET_TEMPERATURE:index": ["Engine turbine inlet temperature", b'RECIP ENG TURBINE INLET TEMPERATURE:index', b'Celsius', 'Y'],
- "RECIP_ENG_CYLINDER_HEAD_TEMPERATURE:index": ["Engine cylinder head temperature", b'RECIP ENG CYLINDER HEAD TEMPERATURE:index', b'Celsius', 'Y'],
- "RECIP_ENG_RADIATOR_TEMPERATURE:index": ["Engine radiator temperature", b'RECIP ENG RADIATOR TEMPERATURE:index', b'Celsius', 'Y'],
- "RECIP_ENG_FUEL_AVAILABLE:index": ["True if fuel is available", b'RECIP ENG FUEL AVAILABLE:index', b'Bool', 'Y'],
- "RECIP_ENG_FUEL_FLOW:index": ["Engine fuel flow", b'RECIP ENG FUEL FLOW:index', b'Pounds per hour', 'Y'],
- "RECIP_ENG_FUEL_TANK_SELECTOR:index": ["Fuel tank selected for engine. See fuel tank list.", b'RECIP ENG FUEL TANK SELECTOR:index', b'Enum', 'N'],
- "ENGINE_TYPE": ["Engine type:; 0 = Piston; 1 = Jet; 2 = None; 3 = Helo(Bell) turbine; 4 = Unsupported; 5 = Turboprop", b'ENGINE TYPE', b'Enum', 'N'],
- "RECIP_ENG_FUEL_NUMBER_TANKS_USED:index": ["Number of tanks currently being used", b'RECIP ENG FUEL NUMBER TANKS USED:index', b'Number', 'N'],
- "RECIP_CARBURETOR_TEMPERATURE:index": ["Carburetor temperature", b'RECIP CARBURETOR TEMPERATURE:index', b'Celsius', 'Y'],
- "RECIP_MIXTURE_RATIO:index": ["Fuel / Air mixture ratio", b'RECIP MIXTURE RATIO:index', b'Ratio', 'Y'],
- "TURB_ENG_N1:index": ["Turbine engine N1", b'TURB ENG N1:index', b'Percent', 'Y'],
- "TURB_ENG_N2:index": ["Turbine engine N2", b'TURB ENG N2:index', b'Percent', 'Y'],
- "TURB_ENG_CORRECTED_N1:index": ["Turbine engine corrected N1", b'TURB ENG CORRECTED N1:index', b'Percent', 'Y'],
- "TURB_ENG_CORRECTED_N2:index": ["Turbine engine corrected N2", b'TURB ENG CORRECTED N2:index', b'Percent', 'Y'],
- "TURB_ENG_CORRECTED_FF:index": ["Corrected fuel flow", b'TURB ENG CORRECTED FF:index', b'Pounds per hour', 'Y'],
- "TURB_ENG_MAX_TORQUE_PERCENT:index": ["Percent of max rated torque", b'TURB ENG MAX TORQUE PERCENT:index', b'Percent', 'Y'],
- "TURB_ENG_PRESSURE_RATIO:index": ["Engine pressure ratio", b'TURB ENG PRESSURE RATIO:index', b'Ratio', 'Y'],
- "TURB_ENG_ITT:index": ["Engine ITT", b'TURB ENG ITT:index', b'Rankine', 'Y'],
- "TURB_ENG_AFTERBURNER:index": ["Afterburner state", b'TURB ENG AFTERBURNER:index', b'Bool', 'N'],
- "TURB_ENG_JET_THRUST:index": ["Engine jet thrust", b'TURB ENG JET THRUST:index', b'Pounds', 'N'],
- "TURB_ENG_BLEED_AIR:index": ["Bleed air pressure", b'TURB ENG BLEED AIR:index', b'Psi', 'N'],
- "TURB_ENG_TANK_SELECTOR:index": ["Fuel tank selected for engine. See fuel tank list.", b'TURB ENG TANK SELECTOR:index', b'Enum', 'N'],
- "ENGINE_TYPE": ["Engine type:; 0 = Piston; 1 = Jet; 2 = None; 3 = Helo(Bell) turbine; 4 = Unsupported; 5 = Turboprop", b'ENGINE TYPE', b'Enum', 'N'],
- "TURB_ENG_NUM_TANKS_USED:index": ["Number of tanks currently being used", b'TURB ENG NUM TANKS USED:index', b'Number', 'N'],
- "TURB_ENG_FUEL_FLOW_PPH:index": ["Engine fuel flow", b'TURB ENG FUEL FLOW PPH:index', b'Pounds per hour', 'N'],
- "TURB_ENG_FUEL_AVAILABLE:index": ["True if fuel is available", b'TURB ENG FUEL AVAILABLE:index', b'Bool', 'N'],
- "TURB_ENG_REVERSE_NOZZLE_PERCENT:index": ["Percent thrust reverser nozzles deployed", b'TURB ENG REVERSE NOZZLE PERCENT:index', b'Percent', 'N'],
- "TURB_ENG_VIBRATION:index": ["Engine vibration value", b'TURB ENG VIBRATION:index', b'Number', 'N'],
- "ENG_FAILED:index": ["Failure flag", b'ENG FAILED:index', b'Number', 'N'],
- "ENG_RPM_ANIMATION_PERCENT:index": ["Percent max rated rpm used for visual animation", b'ENG RPM ANIMATION PERCENT:index', b'Percent', 'N'],
- "ENG_ON_FIRE:index": ["On fire state", b'ENG ON FIRE:index', b'Bool', 'Y'],
- "ENG_FUEL_FLOW_BUG_POSITION:index": ["Fuel flow reference", b'ENG FUEL FLOW BUG POSITION:index', b'Pounds per hour', 'N'],
- "PROP_RPM:index": ["Propeller rpm", b'PROP RPM:index', b'Rpm', 'Y'],
- "PROP_MAX_RPM_PERCENT:index": ["Percent of max rated rpm", b'PROP MAX RPM PERCENT:index', b'Percent', 'N'],
- "PROP_THRUST:index": ["Propeller thrust", b'PROP THRUST:index', b'Pounds', 'N'],
- "PROP_BETA:index": ["Prop blade pitch angle", b'PROP BETA:index', b'Radians', 'N'],
- "PROP_FEATHERING_INHIBIT:index": ["Feathering inhibit flag", b'PROP FEATHERING INHIBIT:index', b'Bool', 'N'],
- "PROP_FEATHERED:index": ["Feathered state", b'PROP FEATHERED:index', b'Bool', 'N'],
- "PROP_SYNC_DELTA_LEVER:index": ["Corrected prop correction input on slaved engine", b'PROP SYNC DELTA LEVER:index', b'Position', 'N'],
- "PROP_AUTO_FEATHER_ARMED:index": ["Auto-feather armed state", b'PROP AUTO FEATHER ARMED:index', b'Bool', 'N'],
- "PROP_FEATHER_SWITCH:index": ["Prop feather switch", b'PROP FEATHER SWITCH:index', b'Bool', 'N'],
- "PANEL_AUTO_FEATHER_SWITCH:index": ["Auto-feather arming switch", b'PANEL AUTO FEATHER SWITCH:index', b'Bool', 'N'],
- "PROP_SYNC_ACTIVE:index": ["True if prop sync is active", b'PROP SYNC ACTIVE:index', b'Bool', 'N'],
- "PROP_DEICE_SWITCH:index": ["True if prop deice switch on", b'PROP DEICE SWITCH:index', b'Bool', 'N'],
- "ENG_COMBUSTION": ["True if the engine is running", b'ENG COMBUSTION', b'Bool', 'N'],
- "ENG_N1_RPM:index": ["Engine N1 rpm", b'ENG N1 RPM:index', b'Rpm (0 to 16384 = 0 to 100%)', 'N'],
- "ENG_N2_RPM:index": ["Engine N2 rpm", b'ENG N2 RPM:index', b'Rpm(0 to 16384 = 0 to 100%)', 'N'],
- "ENG_FUEL_FLOW_GPH:index": ["Engine fuel flow", b'ENG FUEL FLOW GPH:index', b'Gallons per hour', 'N'],
- "ENG_FUEL_FLOW_PPH:index": ["Engine fuel flow", b'ENG FUEL FLOW PPH:index', b'Pounds per hour', 'N'],
- "ENG_TORQUE:index": ["Torque", b'ENG TORQUE:index', b'Foot pounds', 'N'],
- "ENG_ANTI_ICE:index": ["Anti-ice switch", b'ENG ANTI ICE:index', b'Bool', 'N'],
- "ENG_PRESSURE_RATIO:index": ["Engine pressure ratio", b'ENG PRESSURE RATIO:index', b'Ratio (0-16384)', 'N'],
- "ENG_EXHAUST_GAS_TEMPERATURE:index": ["Exhaust gas temperature", b'ENG EXHAUST GAS TEMPERATURE:index', b'Rankine', 'N'],
- "ENG_EXHAUST_GAS_TEMPERATURE_GES:index": ["Governed engine setting", b'ENG EXHAUST GAS TEMPERATURE GES:index', b'Percent over 100', 'N'],
- "ENG_CYLINDER_HEAD_TEMPERATURE:index": ["Engine cylinder head temperature", b'ENG CYLINDER HEAD TEMPERATURE:index', b'Rankine', 'N'],
- "ENG_OIL_TEMPERATURE:index": ["Engine oil temperature", b'ENG OIL TEMPERATURE:index', b'Rankine', 'N'],
- "ENG_OIL_PRESSURE:index": ["Engine oil pressure", b'ENG OIL PRESSURE:index', b'foot pounds', 'N'],
- "ENG_OIL_QUANTITY:index": ["Engine oil quantitiy as a percentage of full capacity", b'ENG OIL QUANTITY:index', b'Percent over 100', 'N'],
- "ENG_HYDRAULIC_PRESSURE:index": ["Engine hydraulic pressure", b'ENG HYDRAULIC PRESSURE:index', b'foot pounds', 'N'],
- "ENG_HYDRAULIC_QUANTITY:index": ["Engine hydraulic fluid quantity, as a percentage of total capacity", b'ENG HYDRAULIC QUANTITY:index', b'Percent over 100', 'N'],
- "ENG_MANIFOLD_PRESSURE:index": ["Engine manifold pressure.", b'ENG MANIFOLD PRESSURE:index', b'inHG.', 'N'],
- "ENG_VIBRATION:index": ["Engine vibration", b'ENG VIBRATION:index', b'Number', 'N'],
- "ENG_RPM_SCALER:index": ["Obsolete", b'ENG RPM SCALER:index', b'Scalar', 'N'],
- "ENG_MAX_RPM": ["Maximum rpm", b'ENG MAX RPM', b'Rpm', 'N'],
- "GENERAL_ENG_STARTER_ACTIVE": ["True if engine starter is active", b'GENERAL ENG STARTER ACTIVE', b'Bool', 'N'],
- "GENERAL_ENG_FUEL_USED_SINCE_START": ["Fuel used since the engines were last started", b'GENERAL ENG FUEL USED SINCE START', b'Pounds', 'N'],
- "TURB_ENG_PRIMARY_NOZZLE_PERCENT:index": ["Percent thrust of primary nozzle", b'TURB ENG PRIMARY NOZZLE PERCENT:index', b'Percent over 100', 'N'],
- "TURB_ENG_IGNITION_SWITCH": ["True if the turbine engine ignition switch is on", b'TURB ENG IGNITION SWITCH', b'Bool', 'N'],
- "TURB_ENG_MASTER_STARTER_SWITCH": ["True if the turbine engine master starter switch is on", b'TURB ENG MASTER STARTER SWITCH', b'Bool', 'N'],
- "TURB_ENG_AFTERBURNER_STAGE_ACTIVE": ["The stage of the afterburner, or 0 if the afterburner is not active.", b'TURB ENG AFTERBURNER STAGE ACTIVE', b'Number', 'N'],
- }
+ class __AircraftEngineData(RequestHelper):
+ list = {
+ "NUMBER_OF_ENGINES": [
+ "Number of engines (minimum 0, maximum 4)",
+ b"NUMBER OF ENGINES",
+ b"Number",
+ "N",
+ ],
+ "ENGINE_CONTROL_SELECT": [
+ "Selected engines (combination of bit flags); 1 = Engine 1; 2 = Engine 2; 4 = Engine 3; 8 = Engine 4",
+ b"ENGINE CONTROL SELECT",
+ b"Mask",
+ "Y",
+ ],
+ "THROTTLE_LOWER_LIMIT": [
+ "Percent throttle defining lower limit (negative for reverse thrust equipped airplanes)",
+ b"THROTTLE LOWER LIMIT",
+ b"Percent",
+ "N",
+ ],
+ "ENGINE_TYPE": [
+ "Engine type:; 0 = Piston; 1 = Jet; 2 = None; 3 = Helo(Bell) turbine; 4 = Unsupported; 5 = Turboprop",
+ b"ENGINE TYPE",
+ b"Enum",
+ "N",
+ ],
+ "MASTER_IGNITION_SWITCH": [
+ "Aircraft master ignition switch (grounds all engines magnetos)",
+ b"MASTER IGNITION SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "GENERAL_ENG_COMBUSTION:index": [
+ "Combustion flag",
+ b"GENERAL ENG COMBUSTION:index",
+ b"Bool",
+ "Y",
+ ],
+ "GENERAL_ENG_MASTER_ALTERNATOR:index": [
+ "Alternator (generator) switch",
+ b"GENERAL ENG MASTER ALTERNATOR:index",
+ b"Bool",
+ "N",
+ ],
+ "GENERAL_ENG_FUEL_PUMP_SWITCH:index": [
+ "Fuel pump switch",
+ b"GENERAL ENG FUEL PUMP SWITCH:index",
+ b"Bool",
+ "N",
+ ],
+ "GENERAL_ENG_FUEL_PUMP_ON:index": [
+ "Fuel pump on/off",
+ b"GENERAL ENG FUEL PUMP ON:index",
+ b"Bool",
+ "N",
+ ],
+ "GENERAL_ENG_RPM:index": [
+ "Engine rpm",
+ b"GENERAL ENG RPM:index",
+ b"Rpm",
+ "N",
+ ],
+ "GENERAL_ENG_PCT_MAX_RPM:index": [
+ "Percent of max rated rpm",
+ b"GENERAL ENG PCT MAX RPM:index",
+ b"Percent",
+ "N",
+ ],
+ "GENERAL_ENG_MAX_REACHED_RPM:index": [
+ "Maximum attained rpm",
+ b"GENERAL ENG MAX REACHED RPM:index",
+ b"Rpm",
+ "N",
+ ],
+ "GENERAL_ENG_THROTTLE_LEVER_POSITION:index": [
+ "Percent of max throttle position",
+ b"GENERAL ENG THROTTLE LEVER POSITION:index",
+ b"Percent",
+ "Y",
+ ],
+ "GENERAL_ENG_MIXTURE_LEVER_POSITION:index": [
+ "Percent of max mixture lever position",
+ b"GENERAL ENG MIXTURE LEVER POSITION:index",
+ b"Percent",
+ "Y",
+ ],
+ "GENERAL_ENG_PROPELLER_LEVER_POSITION:index": [
+ "Percent of max prop lever position",
+ b"GENERAL ENG PROPELLER LEVER POSITION:index",
+ b"Percent",
+ "Y",
+ ],
+ "GENERAL_ENG_STARTER:index": [
+ "Engine starter on/off",
+ b"GENERAL ENG STARTER:index",
+ b"Bool",
+ "N",
+ ],
+ "GENERAL_ENG_EXHAUST_GAS_TEMPERATURE:index": [
+ "Engine exhaust gas temperature.",
+ b"GENERAL ENG EXHAUST GAS TEMPERATURE:index",
+ b"Rankine",
+ "Y",
+ ],
+ "GENERAL_ENG_OIL_PRESSURE:index": [
+ "Engine oil pressure",
+ b"GENERAL ENG OIL PRESSURE:index",
+ b"Psf",
+ "Y",
+ ],
+ "GENERAL_ENG_OIL_LEAKED_PERCENT:index": [
+ "Percent of max oil capacity leaked",
+ b"GENERAL ENG OIL LEAKED PERCENT:index",
+ b"Percent",
+ "N",
+ ],
+ "GENERAL_ENG_COMBUSTION_SOUND_PERCENT:index": [
+ "Percent of maximum engine sound",
+ b"GENERAL ENG COMBUSTION SOUND PERCENT:index",
+ b"Percent",
+ "N",
+ ],
+ "GENERAL_ENG_DAMAGE_PERCENT:index": [
+ "Percent of total engine damage",
+ b"GENERAL ENG DAMAGE PERCENT:index",
+ b"Percent",
+ "N",
+ ],
+ "GENERAL_ENG_OIL_TEMPERATURE:index": [
+ "Engine oil temperature",
+ b"GENERAL ENG OIL TEMPERATURE:index",
+ b"Rankine",
+ "Y",
+ ],
+ "GENERAL_ENG_FAILED:index": [
+ "Fail flag",
+ b"GENERAL ENG FAILED:index",
+ b"Bool",
+ "N",
+ ],
+ "GENERAL_ENG_GENERATOR_SWITCH:index": [
+ "Alternator (generator) switch",
+ b"GENERAL ENG GENERATOR SWITCH:index",
+ b"Bool",
+ "N",
+ ],
+ "GENERAL_ENG_GENERATOR_ACTIVE:index": [
+ "Alternator (generator) on/off",
+ b"GENERAL ENG GENERATOR ACTIVE:index",
+ b"Bool",
+ "Y",
+ ],
+ "GENERAL_ENG_ANTI_ICE_POSITION:index": [
+ "Engine anti-ice switch",
+ b"GENERAL ENG ANTI ICE POSITION:index",
+ b"Bool",
+ "N",
+ ],
+ "GENERAL_ENG_FUEL_VALVE:index": [
+ "Fuel valve state",
+ b"GENERAL ENG FUEL VALVE:index",
+ b"Bool",
+ "N",
+ ],
+ "GENERAL_ENG_FUEL_PRESSURE:index": [
+ "Engine fuel pressure",
+ b"GENERAL ENG FUEL PRESSURE:index",
+ b"Psi",
+ "Y",
+ ],
+ "GENERAL_ENG_ELAPSED_TIME:index": [
+ "Total engine elapsed time",
+ b"GENERAL ENG ELAPSED TIME:index",
+ b"Hours",
+ "N",
+ ],
+ "RECIP_ENG_COWL_FLAP_POSITION:index": [
+ "Percent cowl flap opened",
+ b"RECIP ENG COWL FLAP POSITION:index",
+ b"Percent",
+ "Y",
+ ],
+ "RECIP_ENG_PRIMER:index": [
+ "Engine primer position",
+ b"RECIP ENG PRIMER:index",
+ b"Bool",
+ "Y",
+ ],
+ "RECIP_ENG_MANIFOLD_PRESSURE:index": [
+ "Engine manifold pressure",
+ b"RECIP ENG MANIFOLD PRESSURE:index",
+ b"Psi",
+ "Y",
+ ],
+ "RECIP_ENG_ALTERNATE_AIR_POSITION:index": [
+ "Alternate air control",
+ b"RECIP ENG ALTERNATE AIR POSITION:index",
+ b"Position",
+ "Y",
+ ],
+ "RECIP_ENG_COOLANT_RESERVOIR_PERCENT:index": [
+ "Percent coolant available",
+ b"RECIP ENG COOLANT RESERVOIR PERCENT:index",
+ b"Percent",
+ "Y",
+ ],
+ "RECIP_ENG_LEFT_MAGNETO:index": [
+ "Left magneto state",
+ b"RECIP ENG LEFT MAGNETO:index",
+ b"Bool",
+ "Y",
+ ],
+ "RECIP_ENG_RIGHT_MAGNETO:index": [
+ "Right magneto state",
+ b"RECIP ENG RIGHT MAGNETO:index",
+ b"Bool",
+ "Y",
+ ],
+ "RECIP_ENG_BRAKE_POWER:index": [
+ "Brake power produced by engine",
+ b"RECIP ENG BRAKE POWER:index",
+ b"Foot pounds per second",
+ "Y",
+ ],
+ "RECIP_ENG_STARTER_TORQUE:index": [
+ "Torque produced by engine",
+ b"RECIP ENG STARTER TORQUE:index",
+ b"Foot pound",
+ "Y",
+ ],
+ "RECIP_ENG_TURBOCHARGER_FAILED:index": [
+ "Turbo failed state",
+ b"RECIP ENG TURBOCHARGER FAILED:index",
+ b"Bool",
+ "Y",
+ ],
+ "RECIP_ENG_EMERGENCY_BOOST_ACTIVE:index": [
+ "War emergency power active",
+ b"RECIP ENG EMERGENCY BOOST ACTIVE:index",
+ b"Bool",
+ "Y",
+ ],
+ "RECIP_ENG_EMERGENCY_BOOST_ELAPSED_TIME:index": [
+ "Elapsed time war emergency power active",
+ b"RECIP ENG EMERGENCY BOOST ELAPSED TIME:index",
+ b"Hours",
+ "Y",
+ ],
+ "RECIP_ENG_WASTEGATE_POSITION:index": [
+ "Percent turbo wastegate closed",
+ b"RECIP ENG WASTEGATE POSITION:index",
+ b"Percent",
+ "Y",
+ ],
+ "RECIP_ENG_TURBINE_INLET_TEMPERATURE:index": [
+ "Engine turbine inlet temperature",
+ b"RECIP ENG TURBINE INLET TEMPERATURE:index",
+ b"Celsius",
+ "Y",
+ ],
+ "RECIP_ENG_CYLINDER_HEAD_TEMPERATURE:index": [
+ "Engine cylinder head temperature",
+ b"RECIP ENG CYLINDER HEAD TEMPERATURE:index",
+ b"Celsius",
+ "Y",
+ ],
+ "RECIP_ENG_RADIATOR_TEMPERATURE:index": [
+ "Engine radiator temperature",
+ b"RECIP ENG RADIATOR TEMPERATURE:index",
+ b"Celsius",
+ "Y",
+ ],
+ "RECIP_ENG_FUEL_AVAILABLE:index": [
+ "True if fuel is available",
+ b"RECIP ENG FUEL AVAILABLE:index",
+ b"Bool",
+ "Y",
+ ],
+ "RECIP_ENG_FUEL_FLOW:index": [
+ "Engine fuel flow",
+ b"RECIP ENG FUEL FLOW:index",
+ b"Pounds per hour",
+ "Y",
+ ],
+ "RECIP_ENG_FUEL_TANK_SELECTOR:index": [
+ "Fuel tank selected for engine. See fuel tank list.",
+ b"RECIP ENG FUEL TANK SELECTOR:index",
+ b"Enum",
+ "N",
+ ],
+ "RECIP_ENG_FUEL_NUMBER_TANKS_USED:index": [
+ "Number of tanks currently being used",
+ b"RECIP ENG FUEL NUMBER TANKS USED:index",
+ b"Number",
+ "N",
+ ],
+ "RECIP_CARBURETOR_TEMPERATURE:index": [
+ "Carburetor temperature",
+ b"RECIP CARBURETOR TEMPERATURE:index",
+ b"Celsius",
+ "Y",
+ ],
+ "RECIP_MIXTURE_RATIO:index": [
+ "Fuel / Air mixture ratio",
+ b"RECIP MIXTURE RATIO:index",
+ b"Ratio",
+ "Y",
+ ],
+ "TURB_ENG_N1:index": [
+ "Turbine engine N1",
+ b"TURB ENG N1:index",
+ b"Percent",
+ "Y",
+ ],
+ "TURB_ENG_N2:index": [
+ "Turbine engine N2",
+ b"TURB ENG N2:index",
+ b"Percent",
+ "Y",
+ ],
+ "TURB_ENG_CORRECTED_N1:index": [
+ "Turbine engine corrected N1",
+ b"TURB ENG CORRECTED N1:index",
+ b"Percent",
+ "Y",
+ ],
+ "TURB_ENG_CORRECTED_N2:index": [
+ "Turbine engine corrected N2",
+ b"TURB ENG CORRECTED N2:index",
+ b"Percent",
+ "Y",
+ ],
+ "TURB_ENG_CORRECTED_FF:index": [
+ "Corrected fuel flow",
+ b"TURB ENG CORRECTED FF:index",
+ b"Pounds per hour",
+ "Y",
+ ],
+ "TURB_ENG_MAX_TORQUE_PERCENT:index": [
+ "Percent of max rated torque",
+ b"TURB ENG MAX TORQUE PERCENT:index",
+ b"Percent",
+ "Y",
+ ],
+ "TURB_ENG_PRESSURE_RATIO:index": [
+ "Engine pressure ratio",
+ b"TURB ENG PRESSURE RATIO:index",
+ b"Ratio",
+ "Y",
+ ],
+ "TURB_ENG_ITT:index": [
+ "Engine ITT",
+ b"TURB ENG ITT:index",
+ b"Rankine",
+ "Y",
+ ],
+ "TURB_ENG_AFTERBURNER:index": [
+ "Afterburner state",
+ b"TURB ENG AFTERBURNER:index",
+ b"Bool",
+ "N",
+ ],
+ "TURB_ENG_JET_THRUST:index": [
+ "Engine jet thrust",
+ b"TURB ENG JET THRUST:index",
+ b"Pounds",
+ "N",
+ ],
+ "TURB_ENG_BLEED_AIR:index": [
+ "Bleed air pressure",
+ b"TURB ENG BLEED AIR:index",
+ b"Psi",
+ "N",
+ ],
+ "TURB_ENG_TANK_SELECTOR:index": [
+ "Fuel tank selected for engine. See fuel tank list.",
+ b"TURB ENG TANK SELECTOR:index",
+ b"Enum",
+ "N",
+ ],
+ "TURB_ENG_NUM_TANKS_USED:index": [
+ "Number of tanks currently being used",
+ b"TURB ENG NUM TANKS USED:index",
+ b"Number",
+ "N",
+ ],
+ "TURB_ENG_FUEL_FLOW_PPH:index": [
+ "Engine fuel flow",
+ b"TURB ENG FUEL FLOW PPH:index",
+ b"Pounds per hour",
+ "N",
+ ],
+ "TURB_ENG_FUEL_AVAILABLE:index": [
+ "True if fuel is available",
+ b"TURB ENG FUEL AVAILABLE:index",
+ b"Bool",
+ "N",
+ ],
+ "TURB_ENG_REVERSE_NOZZLE_PERCENT:index": [
+ "Percent thrust reverser nozzles deployed",
+ b"TURB ENG REVERSE NOZZLE PERCENT:index",
+ b"Percent",
+ "N",
+ ],
+ "TURB_ENG_VIBRATION:index": [
+ "Engine vibration value",
+ b"TURB ENG VIBRATION:index",
+ b"Number",
+ "N",
+ ],
+ "ENG_FAILED:index": ["Failure flag", b"ENG FAILED:index", b"Number", "N"],
+ "ENG_RPM_ANIMATION_PERCENT:index": [
+ "Percent max rated rpm used for visual animation",
+ b"ENG RPM ANIMATION PERCENT:index",
+ b"Percent",
+ "N",
+ ],
+ "ENG_ON_FIRE:index": ["On fire state", b"ENG ON FIRE:index", b"Bool", "Y"],
+ "ENG_FUEL_FLOW_BUG_POSITION:index": [
+ "Fuel flow reference",
+ b"ENG FUEL FLOW BUG POSITION:index",
+ b"Pounds per hour",
+ "N",
+ ],
+ "PROP_RPM:index": ["Propeller rpm", b"PROP RPM:index", b"Rpm", "Y"],
+ "PROP_MAX_RPM_PERCENT:index": [
+ "Percent of max rated rpm",
+ b"PROP MAX RPM PERCENT:index",
+ b"Percent",
+ "N",
+ ],
+ "PROP_THRUST:index": [
+ "Propeller thrust",
+ b"PROP THRUST:index",
+ b"Pounds",
+ "N",
+ ],
+ "PROP_BETA:index": [
+ "Prop blade pitch angle",
+ b"PROP BETA:index",
+ b"Radians",
+ "N",
+ ],
+ "PROP_FEATHERING_INHIBIT:index": [
+ "Feathering inhibit flag",
+ b"PROP FEATHERING INHIBIT:index",
+ b"Bool",
+ "N",
+ ],
+ "PROP_FEATHERED:index": [
+ "Feathered state",
+ b"PROP FEATHERED:index",
+ b"Bool",
+ "N",
+ ],
+ "PROP_SYNC_DELTA_LEVER:index": [
+ "Corrected prop correction input on slaved engine",
+ b"PROP SYNC DELTA LEVER:index",
+ b"Position",
+ "N",
+ ],
+ "PROP_AUTO_FEATHER_ARMED:index": [
+ "Auto-feather armed state",
+ b"PROP AUTO FEATHER ARMED:index",
+ b"Bool",
+ "N",
+ ],
+ "PROP_FEATHER_SWITCH:index": [
+ "Prop feather switch",
+ b"PROP FEATHER SWITCH:index",
+ b"Bool",
+ "N",
+ ],
+ "PANEL_AUTO_FEATHER_SWITCH:index": [
+ "Auto-feather arming switch",
+ b"PANEL AUTO FEATHER SWITCH:index",
+ b"Bool",
+ "N",
+ ],
+ "PROP_SYNC_ACTIVE:index": [
+ "True if prop sync is active",
+ b"PROP SYNC ACTIVE:index",
+ b"Bool",
+ "N",
+ ],
+ "PROP_DEICE_SWITCH:index": [
+ "True if prop deice switch on",
+ b"PROP DEICE SWITCH:index",
+ b"Bool",
+ "N",
+ ],
+ "ENG_COMBUSTION": [
+ "True if the engine is running",
+ b"ENG COMBUSTION",
+ b"Bool",
+ "N",
+ ],
+ "ENG_N1_RPM:index": [
+ "Engine N1 rpm",
+ b"ENG N1 RPM:index",
+ b"Rpm (0 to 16384 = 0 to 100%)",
+ "N",
+ ],
+ "ENG_N2_RPM:index": [
+ "Engine N2 rpm",
+ b"ENG N2 RPM:index",
+ b"Rpm(0 to 16384 = 0 to 100%)",
+ "N",
+ ],
+ "ENG_FUEL_FLOW_GPH:index": [
+ "Engine fuel flow",
+ b"ENG FUEL FLOW GPH:index",
+ b"Gallons per hour",
+ "N",
+ ],
+ "ENG_FUEL_FLOW_PPH:index": [
+ "Engine fuel flow",
+ b"ENG FUEL FLOW PPH:index",
+ b"Pounds per hour",
+ "N",
+ ],
+ "ENG_TORQUE:index": ["Torque", b"ENG TORQUE:index", b"Foot pounds", "N"],
+ "ENG_ANTI_ICE:index": [
+ "Anti-ice switch",
+ b"ENG ANTI ICE:index",
+ b"Bool",
+ "N",
+ ],
+ "ENG_PRESSURE_RATIO:index": [
+ "Engine pressure ratio",
+ b"ENG PRESSURE RATIO:index",
+ b"Ratio (0-16384)",
+ "N",
+ ],
+ "ENG_EXHAUST_GAS_TEMPERATURE:index": [
+ "Exhaust gas temperature",
+ b"ENG EXHAUST GAS TEMPERATURE:index",
+ b"Rankine",
+ "N",
+ ],
+ "ENG_EXHAUST_GAS_TEMPERATURE_GES:index": [
+ "Governed engine setting",
+ b"ENG EXHAUST GAS TEMPERATURE GES:index",
+ b"Percent over 100",
+ "N",
+ ],
+ "ENG_CYLINDER_HEAD_TEMPERATURE:index": [
+ "Engine cylinder head temperature",
+ b"ENG CYLINDER HEAD TEMPERATURE:index",
+ b"Rankine",
+ "N",
+ ],
+ "ENG_OIL_TEMPERATURE:index": [
+ "Engine oil temperature",
+ b"ENG OIL TEMPERATURE:index",
+ b"Rankine",
+ "N",
+ ],
+ "ENG_OIL_PRESSURE:index": [
+ "Engine oil pressure",
+ b"ENG OIL PRESSURE:index",
+ b"foot pounds",
+ "N",
+ ],
+ "ENG_OIL_QUANTITY:index": [
+ "Engine oil quantitiy as a percentage of full capacity",
+ b"ENG OIL QUANTITY:index",
+ b"Percent over 100",
+ "N",
+ ],
+ "ENG_HYDRAULIC_PRESSURE:index": [
+ "Engine hydraulic pressure",
+ b"ENG HYDRAULIC PRESSURE:index",
+ b"foot pounds",
+ "N",
+ ],
+ "ENG_HYDRAULIC_QUANTITY:index": [
+ "Engine hydraulic fluid quantity, as a percentage of total capacity",
+ b"ENG HYDRAULIC QUANTITY:index",
+ b"Percent over 100",
+ "N",
+ ],
+ "ENG_MANIFOLD_PRESSURE:index": [
+ "Engine manifold pressure.",
+ b"ENG MANIFOLD PRESSURE:index",
+ b"inHG.",
+ "N",
+ ],
+ "ENG_VIBRATION:index": [
+ "Engine vibration",
+ b"ENG VIBRATION:index",
+ b"Number",
+ "N",
+ ],
+ "ENG_RPM_SCALER:index": [
+ "Obsolete",
+ b"ENG RPM SCALER:index",
+ b"Scalar",
+ "N",
+ ],
+ "ENG_MAX_RPM": ["Maximum rpm", b"ENG MAX RPM", b"Rpm", "N"],
+ "GENERAL_ENG_STARTER_ACTIVE": [
+ "True if engine starter is active",
+ b"GENERAL ENG STARTER ACTIVE",
+ b"Bool",
+ "N",
+ ],
+ "GENERAL_ENG_FUEL_USED_SINCE_START": [
+ "Fuel used since the engines were last started",
+ b"GENERAL ENG FUEL USED SINCE START",
+ b"Pounds",
+ "N",
+ ],
+ "TURB_ENG_PRIMARY_NOZZLE_PERCENT:index": [
+ "Percent thrust of primary nozzle",
+ b"TURB ENG PRIMARY NOZZLE PERCENT:index",
+ b"Percent over 100",
+ "N",
+ ],
+ "TURB_ENG_IGNITION_SWITCH": [
+ "True if the turbine engine ignition switch is on",
+ b"TURB ENG IGNITION SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "TURB_ENG_MASTER_STARTER_SWITCH": [
+ "True if the turbine engine master starter switch is on",
+ b"TURB ENG MASTER STARTER SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "TURB_ENG_AFTERBURNER_STAGE_ACTIVE": [
+ "The stage of the afterburner, or 0 if the afterburner is not active.",
+ b"TURB ENG AFTERBURNER STAGE ACTIVE",
+ b"Number",
+ "N",
+ ],
+ }
- class __FuelTankSelection(RequestHelper):
- list = {
- }
+ class __FuelTankSelection(RequestHelper):
+ list = {}
- class __AircraftFuelData(RequestHelper):
- list = {
- "FUEL_TANK_CENTER_LEVEL": ["Percent of maximum capacity", b'FUEL TANK CENTER LEVEL', b'Percent Over 100', 'Y'],
- "FUEL_TANK_CENTER2_LEVEL": ["Percent of maximum capacity", b'FUEL TANK CENTER2 LEVEL', b'Percent Over 100', 'Y'],
- "FUEL_TANK_CENTER3_LEVEL": ["Percent of maximum capacity", b'FUEL TANK CENTER3 LEVEL', b'Percent Over 100', 'Y'],
- "FUEL_TANK_LEFT_MAIN_LEVEL": ["Percent of maximum capacity", b'FUEL TANK LEFT MAIN LEVEL', b'Percent Over 100', 'Y'],
- "FUEL_TANK_LEFT_AUX_LEVEL": ["Percent of maximum capacity", b'FUEL TANK LEFT AUX LEVEL', b'Percent Over 100', 'Y'],
- "FUEL_TANK_LEFT_TIP_LEVEL": ["Percent of maximum capacity", b'FUEL TANK LEFT TIP LEVEL', b'Percent Over 100', 'Y'],
- "FUEL_TANK_RIGHT_MAIN_LEVEL": ["Percent of maximum capacity", b'FUEL TANK RIGHT MAIN LEVEL', b'Percent Over 100', 'Y'],
- "FUEL_TANK_RIGHT_AUX_LEVEL": ["Percent of maximum capacity", b'FUEL TANK RIGHT AUX LEVEL', b'Percent Over 100', 'Y'],
- "FUEL_TANK_RIGHT_TIP_LEVEL": ["Percent of maximum capacity", b'FUEL TANK RIGHT TIP LEVEL', b'Percent Over 100', 'Y'],
- "FUEL_TANK_EXTERNAL1_LEVEL": ["Percent of maximum capacity", b'FUEL TANK EXTERNAL1 LEVEL', b'Percent Over 100', 'Y'],
- "FUEL_TANK_EXTERNAL2_LEVEL": ["Percent of maximum capacity", b'FUEL TANK EXTERNAL2 LEVEL', b'Percent Over 100', 'Y'],
- "FUEL_TANK_CENTER_CAPACITY": ["Maximum capacity in volume", b'FUEL TANK CENTER CAPACITY', b'Gallons', 'N'],
- "FUEL_TANK_CENTER2_CAPACITY": ["Maximum capacity in volume", b'FUEL TANK CENTER2 CAPACITY', b'Gallons', 'N'],
- "FUEL_TANK_CENTER3_CAPACITY": ["Maximum capacity in volume", b'FUEL TANK CENTER3 CAPACITY', b'Gallons', 'N'],
- "FUEL_TANK_LEFT_MAIN_CAPACITY": ["Maximum capacity in volume", b'FUEL TANK LEFT MAIN CAPACITY', b'Gallons', 'N'],
- "FUEL_TANK_LEFT_AUX_CAPACITY": ["Maximum capacity in volume", b'FUEL TANK LEFT AUX CAPACITY', b'Gallons', 'N'],
- "FUEL_TANK_LEFT_TIP_CAPACITY": ["Maximum capacity in volume", b'FUEL TANK LEFT TIP CAPACITY', b'Gallons', 'N'],
- "FUEL_TANK_RIGHT_MAIN_CAPACITY": ["Maximum capacity in volume", b'FUEL TANK RIGHT MAIN CAPACITY', b'Gallons', 'N'],
- "FUEL_TANK_RIGHT_AUX_CAPACITY": ["Maximum capacity in volume", b'FUEL TANK RIGHT AUX CAPACITY', b'Gallons', 'N'],
- "FUEL_TANK_RIGHT_TIP_CAPACITY": ["Maximum capacity in volume", b'FUEL TANK RIGHT TIP CAPACITY', b'Gallons', 'N'],
- "FUEL_TANK_EXTERNAL1_CAPACITY": ["Maximum capacity in volume", b'FUEL TANK EXTERNAL1 CAPACITY', b'Gallons', 'N'],
- "FUEL_TANK_EXTERNAL2_CAPACITY": ["Maximum capacity in volume", b'FUEL TANK EXTERNAL2 CAPACITY', b'Gallons', 'N'],
- "FUEL_LEFT_CAPACITY": ["Maximum capacity in volume", b'FUEL LEFT CAPACITY', b'Gallons', 'N'],
- "FUEL_RIGHT_CAPACITY": ["Maximum capacity in volume", b'FUEL RIGHT CAPACITY', b'Gallons', 'N'],
- "FUEL_TANK_CENTER_QUANTITY": ["Current quantity in volume", b'FUEL TANK CENTER QUANTITY', b'Gallons', 'Y'],
- "FUEL_TANK_CENTER2_QUANTITY": ["Current quantity in volume", b'FUEL TANK CENTER2 QUANTITY', b'Gallons', 'Y'],
- "FUEL_TANK_CENTER3_QUANTITY": ["Current quantity in volume", b'FUEL TANK CENTER3 QUANTITY', b'Gallons', 'Y'],
- "FUEL_TANK_LEFT_MAIN_QUANTITY": ["Current quantity in volume", b'FUEL TANK LEFT MAIN QUANTITY', b'Gallons', 'Y'],
- "FUEL_TANK_LEFT_AUX_QUANTITY": ["Current quantity in volume", b'FUEL TANK LEFT AUX QUANTITY', b'Gallons', 'Y'],
- "FUEL_TANK_LEFT_TIP_QUANTITY": ["Current quantity in volume", b'FUEL TANK LEFT TIP QUANTITY', b'Gallons', 'Y'],
- "FUEL_TANK_RIGHT_MAIN_QUANTITY": ["Current quantity in volume", b'FUEL TANK RIGHT MAIN QUANTITY', b'Gallons', 'Y'],
- "FUEL_TANK_RIGHT_AUX_QUANTITY": ["Current quantity in volume", b'FUEL TANK RIGHT AUX QUANTITY', b'Gallons', 'Y'],
- "FUEL_TANK_RIGHT_TIP_QUANTITY": ["Current quantity in volume", b'FUEL TANK RIGHT TIP QUANTITY', b'Gallons', 'Y'],
- "FUEL_TANK_EXTERNAL1_QUANTITY": ["Current quantity in volume", b'FUEL TANK EXTERNAL1 QUANTITY', b'Gallons', 'Y'],
- "FUEL_TANK_EXTERNAL2_QUANTITY": ["Current quantity in volume", b'FUEL TANK EXTERNAL2 QUANTITY', b'Gallons', 'Y'],
- "FUEL_LEFT_QUANTITY": ["Current quantity in volume", b'FUEL LEFT QUANTITY', b'Gallons', 'N'],
- "FUEL_RIGHT_QUANTITY": ["Current quantity in volume", b'FUEL RIGHT QUANTITY', b'Gallons', 'N'],
- "FUEL_TOTAL_QUANTITY": ["Current quantity in volume", b'FUEL TOTAL QUANTITY', b'Gallons', 'N'],
- "FUEL_WEIGHT_PER_GALLON": ["Fuel weight per gallon", b'FUEL WEIGHT PER GALLON', b'Pounds', 'N'],
- "FUEL_TANK_SELECTOR:index": ["Which tank is selected. See fuel tank list.", b'FUEL TANK SELECTOR:index', b'Enum', 'N'],
- "FUEL_CROSS_FEED": ["Cross feed valve:; 0 = Closed; 1 = Open", b'FUEL CROSS FEED', b'Enum', 'N'],
- "FUEL_TOTAL_CAPACITY": ["Total capacity of the aircraft", b'FUEL TOTAL CAPACITY', b'Gallons', 'N'],
- "FUEL_SELECTED_QUANTITY_PERCENT": ["Percent or capacity for selected tank", b'FUEL SELECTED QUANTITY PERCENT', b'Percent Over 100', 'N'],
- "FUEL_SELECTED_QUANTITY": ["Quantity of selected tank", b'FUEL SELECTED QUANTITY', b'Gallons', 'N'],
- "FUEL_TOTAL_QUANTITY_WEIGHT": ["Current total fuel weight of the aircraft", b'FUEL TOTAL QUANTITY WEIGHT', b'Pounds', 'N'],
- "NUM_FUEL_SELECTORS": ["Number of selectors on the aircraft", b'NUM FUEL SELECTORS', b'Number', 'N'],
- "UNLIMITED_FUEL": ["Unlimited fuel flag", b'UNLIMITED FUEL', b'Bool', 'N'],
- "ESTIMATED_FUEL_FLOW": ["Estimated fuel flow at cruise", b'ESTIMATED FUEL FLOW', b'Pounds per hour', 'N'],
- }
+ class __AircraftFuelData(RequestHelper):
+ list = {
+ "FUEL_TANK_CENTER_LEVEL": [
+ "Percent of maximum capacity",
+ b"FUEL TANK CENTER LEVEL",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "FUEL_TANK_CENTER2_LEVEL": [
+ "Percent of maximum capacity",
+ b"FUEL TANK CENTER2 LEVEL",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "FUEL_TANK_CENTER3_LEVEL": [
+ "Percent of maximum capacity",
+ b"FUEL TANK CENTER3 LEVEL",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "FUEL_TANK_LEFT_MAIN_LEVEL": [
+ "Percent of maximum capacity",
+ b"FUEL TANK LEFT MAIN LEVEL",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "FUEL_TANK_LEFT_AUX_LEVEL": [
+ "Percent of maximum capacity",
+ b"FUEL TANK LEFT AUX LEVEL",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "FUEL_TANK_LEFT_TIP_LEVEL": [
+ "Percent of maximum capacity",
+ b"FUEL TANK LEFT TIP LEVEL",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "FUEL_TANK_RIGHT_MAIN_LEVEL": [
+ "Percent of maximum capacity",
+ b"FUEL TANK RIGHT MAIN LEVEL",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "FUEL_TANK_RIGHT_AUX_LEVEL": [
+ "Percent of maximum capacity",
+ b"FUEL TANK RIGHT AUX LEVEL",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "FUEL_TANK_RIGHT_TIP_LEVEL": [
+ "Percent of maximum capacity",
+ b"FUEL TANK RIGHT TIP LEVEL",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "FUEL_TANK_EXTERNAL1_LEVEL": [
+ "Percent of maximum capacity",
+ b"FUEL TANK EXTERNAL1 LEVEL",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "FUEL_TANK_EXTERNAL2_LEVEL": [
+ "Percent of maximum capacity",
+ b"FUEL TANK EXTERNAL2 LEVEL",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "FUEL_TANK_CENTER_CAPACITY": [
+ "Maximum capacity in volume",
+ b"FUEL TANK CENTER CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_TANK_CENTER2_CAPACITY": [
+ "Maximum capacity in volume",
+ b"FUEL TANK CENTER2 CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_TANK_CENTER3_CAPACITY": [
+ "Maximum capacity in volume",
+ b"FUEL TANK CENTER3 CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_TANK_LEFT_MAIN_CAPACITY": [
+ "Maximum capacity in volume",
+ b"FUEL TANK LEFT MAIN CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_TANK_LEFT_AUX_CAPACITY": [
+ "Maximum capacity in volume",
+ b"FUEL TANK LEFT AUX CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_TANK_LEFT_TIP_CAPACITY": [
+ "Maximum capacity in volume",
+ b"FUEL TANK LEFT TIP CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_TANK_RIGHT_MAIN_CAPACITY": [
+ "Maximum capacity in volume",
+ b"FUEL TANK RIGHT MAIN CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_TANK_RIGHT_AUX_CAPACITY": [
+ "Maximum capacity in volume",
+ b"FUEL TANK RIGHT AUX CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_TANK_RIGHT_TIP_CAPACITY": [
+ "Maximum capacity in volume",
+ b"FUEL TANK RIGHT TIP CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_TANK_EXTERNAL1_CAPACITY": [
+ "Maximum capacity in volume",
+ b"FUEL TANK EXTERNAL1 CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_TANK_EXTERNAL2_CAPACITY": [
+ "Maximum capacity in volume",
+ b"FUEL TANK EXTERNAL2 CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_LEFT_CAPACITY": [
+ "Maximum capacity in volume",
+ b"FUEL LEFT CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_RIGHT_CAPACITY": [
+ "Maximum capacity in volume",
+ b"FUEL RIGHT CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_TANK_CENTER_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL TANK CENTER QUANTITY",
+ b"Gallons",
+ "Y",
+ ],
+ "FUEL_TANK_CENTER2_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL TANK CENTER2 QUANTITY",
+ b"Gallons",
+ "Y",
+ ],
+ "FUEL_TANK_CENTER3_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL TANK CENTER3 QUANTITY",
+ b"Gallons",
+ "Y",
+ ],
+ "FUEL_TANK_LEFT_MAIN_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL TANK LEFT MAIN QUANTITY",
+ b"Gallons",
+ "Y",
+ ],
+ "FUEL_TANK_LEFT_AUX_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL TANK LEFT AUX QUANTITY",
+ b"Gallons",
+ "Y",
+ ],
+ "FUEL_TANK_LEFT_TIP_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL TANK LEFT TIP QUANTITY",
+ b"Gallons",
+ "Y",
+ ],
+ "FUEL_TANK_RIGHT_MAIN_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL TANK RIGHT MAIN QUANTITY",
+ b"Gallons",
+ "Y",
+ ],
+ "FUEL_TANK_RIGHT_AUX_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL TANK RIGHT AUX QUANTITY",
+ b"Gallons",
+ "Y",
+ ],
+ "FUEL_TANK_RIGHT_TIP_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL TANK RIGHT TIP QUANTITY",
+ b"Gallons",
+ "Y",
+ ],
+ "FUEL_TANK_EXTERNAL1_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL TANK EXTERNAL1 QUANTITY",
+ b"Gallons",
+ "Y",
+ ],
+ "FUEL_TANK_EXTERNAL2_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL TANK EXTERNAL2 QUANTITY",
+ b"Gallons",
+ "Y",
+ ],
+ "FUEL_LEFT_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL LEFT QUANTITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_RIGHT_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL RIGHT QUANTITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_TOTAL_QUANTITY": [
+ "Current quantity in volume",
+ b"FUEL TOTAL QUANTITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_WEIGHT_PER_GALLON": [
+ "Fuel weight per gallon",
+ b"FUEL WEIGHT PER GALLON",
+ b"Pounds",
+ "N",
+ ],
+ "FUEL_TANK_SELECTOR:index": [
+ "Which tank is selected. See fuel tank list.",
+ b"FUEL TANK SELECTOR:index",
+ b"Enum",
+ "N",
+ ],
+ "FUEL_CROSS_FEED": [
+ "Cross feed valve:; 0 = Closed; 1 = Open",
+ b"FUEL CROSS FEED",
+ b"Enum",
+ "N",
+ ],
+ "FUEL_TOTAL_CAPACITY": [
+ "Total capacity of the aircraft",
+ b"FUEL TOTAL CAPACITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_SELECTED_QUANTITY_PERCENT": [
+ "Percent or capacity for selected tank",
+ b"FUEL SELECTED QUANTITY PERCENT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "FUEL_SELECTED_QUANTITY": [
+ "Quantity of selected tank",
+ b"FUEL SELECTED QUANTITY",
+ b"Gallons",
+ "N",
+ ],
+ "FUEL_TOTAL_QUANTITY_WEIGHT": [
+ "Current total fuel weight of the aircraft",
+ b"FUEL TOTAL QUANTITY WEIGHT",
+ b"Pounds",
+ "N",
+ ],
+ "NUM_FUEL_SELECTORS": [
+ "Number of selectors on the aircraft",
+ b"NUM FUEL SELECTORS",
+ b"Number",
+ "N",
+ ],
+ "UNLIMITED_FUEL": ["Unlimited fuel flag", b"UNLIMITED FUEL", b"Bool", "N"],
+ "ESTIMATED_FUEL_FLOW": [
+ "Estimated fuel flow at cruise",
+ b"ESTIMATED FUEL FLOW",
+ b"Pounds per hour",
+ "N",
+ ],
+ }
- class __AircraftLightsData(RequestHelper):
- list = {
- "LIGHT_STROBE": ["Light switch state", b'LIGHT STROBE', b'Bool', 'Y'],
- "LIGHT_PANEL": ["Light switch state", b'LIGHT PANEL', b'Bool', 'Y'],
- "LIGHT_LANDING": ["Light switch state", b'LIGHT LANDING', b'Bool', 'Y'],
- "LIGHT_TAXI": ["Light switch state", b'LIGHT TAXI', b'Bool', 'Y'],
- "LIGHT_BEACON": ["Light switch state", b'LIGHT BEACON', b'Bool', 'Y'],
- "LIGHT_NAV": ["Light switch state", b'LIGHT NAV', b'Bool', 'Y'],
- "LIGHT_LOGO": ["Light switch state", b'LIGHT LOGO', b'Bool', 'Y'],
- "LIGHT_WING": ["Light switch state", b'LIGHT WING', b'Bool', 'Y'],
- "LIGHT_RECOGNITION": ["Light switch state", b'LIGHT RECOGNITION', b'Bool', 'Y'],
- "LIGHT_CABIN": ["Light switch state", b'LIGHT CABIN', b'Bool', 'Y'],
- "LIGHT_ON_STATES": ["Bit mask:; 0x0001: Nav; 0x0002: Beacon; 0x0004: Landing; 0x0008: Taxi; 0x0010: Strobe; 0x0020: Panel; 0x0040: Recognition; 0x0080: Wing; 0x0100: Logo; 0x0200: Cabin", b'LIGHT ON STATES', b'Mask', 'N'],
- "LIGHT_STATES": ["Same as LIGHT ON STATES", b'LIGHT STATES', b'Mask', 'N'],
- # "LANDING_LIGHT_PBH": ["Landing light pitch bank and heading", b'LANDING LIGHT PBH', b'SIMCONNECT_DATA_XYZ', 'N'],
- "LIGHT_TAXI_ON": ["Return true if the light is on.", b'LIGHT TAXI ON', b'Bool', 'N'],
- "LIGHT_STROBE_ON": ["Return true if the light is on.", b'LIGHT STROBE ON', b'Bool', 'N'],
- "LIGHT_PANEL_ON": ["Return true if the light is on.", b'LIGHT PANEL ON', b'Bool', 'N'],
- "LIGHT_RECOGNITION_ON": ["Return true if the light is on.", b'LIGHT RECOGNITION ON', b'Bool', 'N'],
- "LIGHT_WING_ON": ["Return true if the light is on.", b'LIGHT WING ON', b'Bool', 'N'],
- "LIGHT_LOGO_ON": ["Return true if the light is on.", b'LIGHT LOGO ON', b'Bool', 'N'],
- "LIGHT_CABIN_ON": ["Return true if the light is on.", b'LIGHT CABIN ON', b'Bool', 'N'],
- "LIGHT_HEAD_ON": ["Return true if the light is on.", b'LIGHT HEAD ON', b'Bool', 'N'],
- "LIGHT_BRAKE_ON": ["Return true if the light is on.", b'LIGHT BRAKE ON', b'Bool', 'N'],
- "LIGHT_NAV_ON": ["Return true if the light is on.", b'LIGHT NAV ON', b'Bool', 'N'],
- "LIGHT_BEACON_ON": ["Return true if the light is on.", b'LIGHT BEACON ON', b'Bool', 'N'],
- "LIGHT_LANDING_ON": ["Return true if the light is on.", b'LIGHT LANDING ON', b'Bool', 'N'],
- }
+ class __AircraftLightsData(RequestHelper):
+ list = {
+ "LIGHT_STROBE": ["Light switch state", b"LIGHT STROBE", b"Bool", "Y"],
+ "LIGHT_PANEL": ["Light switch state", b"LIGHT PANEL", b"Bool", "Y"],
+ "LIGHT_LANDING": ["Light switch state", b"LIGHT LANDING", b"Bool", "Y"],
+ "LIGHT_TAXI": ["Light switch state", b"LIGHT TAXI", b"Bool", "Y"],
+ "LIGHT_BEACON": ["Light switch state", b"LIGHT BEACON", b"Bool", "Y"],
+ "LIGHT_NAV": ["Light switch state", b"LIGHT NAV", b"Bool", "Y"],
+ "LIGHT_LOGO": ["Light switch state", b"LIGHT LOGO", b"Bool", "Y"],
+ "LIGHT_WING": ["Light switch state", b"LIGHT WING", b"Bool", "Y"],
+ "LIGHT_RECOGNITION": [
+ "Light switch state",
+ b"LIGHT RECOGNITION",
+ b"Bool",
+ "Y",
+ ],
+ "LIGHT_CABIN": ["Light switch state", b"LIGHT CABIN", b"Bool", "Y"],
+ "LIGHT_ON_STATES": [
+ "Bit mask:; 0x0001: Nav; 0x0002: Beacon; 0x0004: Landing; 0x0008: Taxi; 0x0010: Strobe; 0x0020: Panel; 0x0040: Recognition; 0x0080: Wing; 0x0100: Logo; 0x0200: Cabin",
+ b"LIGHT ON STATES",
+ b"Mask",
+ "N",
+ ],
+ "LIGHT_STATES": ["Same as LIGHT ON STATES", b"LIGHT STATES", b"Mask", "N"],
+ # "LANDING_LIGHT_PBH": ["Landing light pitch bank and heading", b'LANDING LIGHT PBH', b'SIMCONNECT_DATA_XYZ', 'N'],
+ "LIGHT_TAXI_ON": [
+ "Return true if the light is on.",
+ b"LIGHT TAXI ON",
+ b"Bool",
+ "N",
+ ],
+ "LIGHT_STROBE_ON": [
+ "Return true if the light is on.",
+ b"LIGHT STROBE ON",
+ b"Bool",
+ "N",
+ ],
+ "LIGHT_PANEL_ON": [
+ "Return true if the light is on.",
+ b"LIGHT PANEL ON",
+ b"Bool",
+ "N",
+ ],
+ "LIGHT_RECOGNITION_ON": [
+ "Return true if the light is on.",
+ b"LIGHT RECOGNITION ON",
+ b"Bool",
+ "N",
+ ],
+ "LIGHT_WING_ON": [
+ "Return true if the light is on.",
+ b"LIGHT WING ON",
+ b"Bool",
+ "N",
+ ],
+ "LIGHT_LOGO_ON": [
+ "Return true if the light is on.",
+ b"LIGHT LOGO ON",
+ b"Bool",
+ "N",
+ ],
+ "LIGHT_CABIN_ON": [
+ "Return true if the light is on.",
+ b"LIGHT CABIN ON",
+ b"Bool",
+ "N",
+ ],
+ "LIGHT_HEAD_ON": [
+ "Return true if the light is on.",
+ b"LIGHT HEAD ON",
+ b"Bool",
+ "N",
+ ],
+ "LIGHT_BRAKE_ON": [
+ "Return true if the light is on.",
+ b"LIGHT BRAKE ON",
+ b"Bool",
+ "N",
+ ],
+ "LIGHT_NAV_ON": [
+ "Return true if the light is on.",
+ b"LIGHT NAV ON",
+ b"Bool",
+ "N",
+ ],
+ "LIGHT_BEACON_ON": [
+ "Return true if the light is on.",
+ b"LIGHT BEACON ON",
+ b"Bool",
+ "N",
+ ],
+ "LIGHT_LANDING_ON": [
+ "Return true if the light is on.",
+ b"LIGHT LANDING ON",
+ b"Bool",
+ "N",
+ ],
+ }
- class __AircraftPositionandSpeedData(RequestHelper):
- list = {
- "GROUND_VELOCITY": ["Speed relative to the earths surface", b'GROUND VELOCITY', b'Knots', 'N'],
- "TOTAL_WORLD_VELOCITY": ["Speed relative to the earths center", b'TOTAL WORLD VELOCITY', b'Feet per second', 'N'],
- "VELOCITY_BODY_Z": ["True longitudinal speed, relative to aircraft axis", b'VELOCITY BODY Z', b'Feet per second', 'Y'],
- "VELOCITY_BODY_X": ["True lateral speed, relative to aircraft axis", b'VELOCITY BODY X', b'Feet per second', 'Y'],
- "VELOCITY_BODY_Y": ["True vertical speed, relative to aircraft axis", b'VELOCITY BODY Y', b'Feet per second', 'Y'],
- "VELOCITY_WORLD_Z": ["Speed relative to earth, in North/South direction", b'VELOCITY WORLD Z', b'Feet per second', 'Y'],
- "VELOCITY_WORLD_X": ["Speed relative to earth, in East/West direction", b'VELOCITY WORLD X', b'Feet per second', 'Y'],
- "VELOCITY_WORLD_Y": ["Speed relative to earth, in vertical direction", b'VELOCITY WORLD Y', b'Feet per second', 'Y'],
- "ACCELERATION_WORLD_X": ["Acceleration relative to earth, in east/west direction", b'ACCELERATION WORLD X', b'Feet per second squared', 'Y'],
- "ACCELERATION_WORLD_Y": ["Acceleration relative to earch, in vertical direction", b'ACCELERATION WORLD Y', b'Feet per second squared', 'Y'],
- "ACCELERATION_WORLD_Z": ["Acceleration relative to earth, in north/south direction", b'ACCELERATION WORLD Z', b'Feet per second squared', 'Y'],
- "ACCELERATION_BODY_X": ["Acceleration relative to aircraft axix, in east/west direction", b'ACCELERATION BODY X', b'Feet per second squared', 'Y'],
- "ACCELERATION_BODY_Y": ["Acceleration relative to aircraft axis, in vertical direction", b'ACCELERATION BODY Y', b'Feet per second squared', 'Y'],
- "ACCELERATION_BODY_Z": ["Acceleration relative to aircraft axis, in north/south direction", b'ACCELERATION BODY Z', b'Feet per second squared', 'Y'],
- "ROTATION_VELOCITY_BODY_X": ["Rotation relative to aircraft axis", b'ROTATION VELOCITY BODY X', b'Feet per second', 'Y'],
- "ROTATION_VELOCITY_BODY_Y": ["Rotation relative to aircraft axis", b'ROTATION VELOCITY BODY Y', b'Feet per second', 'Y'],
- "ROTATION_VELOCITY_BODY_Z": ["Rotation relative to aircraft axis", b'ROTATION VELOCITY BODY Z', b'Feet per second', 'Y'],
- "RELATIVE_WIND_VELOCITY_BODY_X": ["Lateral speed relative to wind", b'RELATIVE WIND VELOCITY BODY X', b'Feet per second', 'N'],
- "RELATIVE_WIND_VELOCITY_BODY_Y": ["Vertical speed relative to wind", b'RELATIVE WIND VELOCITY BODY Y', b'Feet per second', 'N'],
- "RELATIVE_WIND_VELOCITY_BODY_Z": ["Longitudinal speed relative to wind", b'RELATIVE WIND VELOCITY BODY Z', b'Feet per second', 'N'],
- "PLANE_ALT_ABOVE_GROUND": ["Altitude above the surface", b'PLANE ALT ABOVE GROUND', b'Feet', 'Y'],
- "PLANE_LATITUDE": ["Latitude of aircraft, North is positive, South negative", b'PLANE LATITUDE', b'Degrees', 'Y'],
- "PLANE_LONGITUDE": ["Longitude of aircraft, East is positive, West negative", b'PLANE LONGITUDE', b'Degrees', 'Y'],
- "PLANE_ALTITUDE": ["Altitude of aircraft", b'PLANE ALTITUDE', b'Feet', 'Y'],
- "PLANE_PITCH_DEGREES": ["Pitch angle, although the name mentions degrees the units used are radians", b'PLANE PITCH DEGREES', b'Radians', 'Y'],
- "PLANE_BANK_DEGREES": ["Bank angle, although the name mentions degrees the units used are radians", b'PLANE BANK DEGREES', b'Radians', 'Y'],
- "PLANE_HEADING_DEGREES_TRUE": ["Heading relative to true north, although the name mentions degrees the units used are radians", b'PLANE HEADING DEGREES TRUE', b'Radians', 'Y'],
- "PLANE_HEADING_DEGREES_MAGNETIC": ["Heading relative to magnetic north, although the name mentions degrees the units used are radians", b'PLANE HEADING DEGREES MAGNETIC', b'Radians', 'Y'],
- "MAGVAR": ["Magnetic variation", b'MAGVAR', b'Degrees', 'N'],
- "GROUND_ALTITUDE": ["Altitude of surface", b'GROUND ALTITUDE', b'Meters', 'N'],
- "SIM_ON_GROUND": ["On ground flag", b'SIM ON GROUND', b'Bool', 'N'],
- "INCIDENCE_ALPHA": ["Angle of attack", b'INCIDENCE ALPHA', b'Radians', 'N'],
- "INCIDENCE_BETA": ["Sideslip angle", b'INCIDENCE BETA', b'Radians', 'N'],
- "WING_FLEX_PCT:index": ["The current wing flex. Different values can be set for each wing (for example, during banking). Set an index of 1 for the left wing, and 2 for the right wing.", b'WING FLEX PCT:index', b'Percent over 100', 'Y'],
- # "STRUCT_LATLONALT": ["Returns the latitude, longitude and altitude of the user aircraft.", b'STRUCT LATLONALT', b'SIMCONNECT_DATA_LATLONALT', 'N'],
- # "STRUCT_LATLONALTPBH": ["Returns the pitch, bank and heading of the user aircraft.", b'STRUCT LATLONALTPBH', b'SIMCONNECT_DATA_LATLONALT', 'N'],
- # "STRUCT_SURFACE_RELATIVE_VELOCITY": ["The relative surface velocity.", b'STRUCT SURFACE RELATIVE VELOCITY', b'SIMCONNECT_DATA_XYZ', 'N'],
- # "STRUCT_WORLDVELOCITY": ["The world velocity.", b'STRUCT WORLDVELOCITY', b'SIMCONNECT_DATA_XYZ', 'N'],
- # "STRUCT_WORLD_ROTATION_VELOCITY": ["The world rotation velocity.", b'STRUCT WORLD ROTATION VELOCITY', b'SIMCONNECT_DATA_XYZ', 'N'],
- # "STRUCT_BODY_VELOCITY": ["The object body velocity.", b'STRUCT BODY VELOCITY', b'SIMCONNECT_DATA_XYZ', 'N'],
- # "STRUCT_BODY_ROTATION_VELOCITY": ["The body rotation velocity. Individual body rotation values are in the Aircraft Position and Speed section.", b'STRUCT BODY ROTATION VELOCITY', b'SIMCONNECT_DATA_XYZ', 'N'],
- # "STRUCT_WORLD_ACCELERATION": ["The world acceleration for each axis. Individual world acceleration values are in the Aircraft Position and Speed section.", b'STRUCT WORLD ACCELERATION', b'SIMCONNECT_DATA_XYZ', 'N'],
- # "STRUCT_ENGINE_POSITION:index": ["The engine position relative to the reference datum position for the aircraft.", b'STRUCT ENGINE POSITION:index', b'SIMCONNECT_DATA_XYZ.', 'N'],
- # "STRUCT_EYEPOINT_DYNAMIC_ANGLE": ["The angle of the eyepoint view. Zero, zero, zero is straight ahead.", b'STRUCT EYEPOINT DYNAMIC ANGLE', b'SIMCONNECT_DATA_XYZ', 'N'],
- # "STRUCT_EYEPOINT_DYNAMIC_OFFSET": ["A variable offset away from the EYEPOINT POSITION", b'STRUCT EYEPOINT DYNAMIC OFFSET', b'SIMCONNECT_DATA_XYZ', 'N'],
- # "EYEPOINT_POSITION": ["The eyepoint position relative to the reference datum position for the aircraft.", b'EYEPOINT POSITION', b'SIMCONNECT_DATA_XYZ', 'N'],
- }
+ class __AircraftPositionandSpeedData(RequestHelper):
+ list = {
+ "GROUND_VELOCITY": [
+ "Speed relative to the earths surface",
+ b"GROUND VELOCITY",
+ b"Knots",
+ "N",
+ ],
+ "TOTAL_WORLD_VELOCITY": [
+ "Speed relative to the earths center",
+ b"TOTAL WORLD VELOCITY",
+ b"Feet per second",
+ "N",
+ ],
+ "VELOCITY_BODY_Z": [
+ "True longitudinal speed, relative to aircraft axis",
+ b"VELOCITY BODY Z",
+ b"Feet per second",
+ "Y",
+ ],
+ "VELOCITY_BODY_X": [
+ "True lateral speed, relative to aircraft axis",
+ b"VELOCITY BODY X",
+ b"Feet per second",
+ "Y",
+ ],
+ "VELOCITY_BODY_Y": [
+ "True vertical speed, relative to aircraft axis",
+ b"VELOCITY BODY Y",
+ b"Feet per second",
+ "Y",
+ ],
+ "VELOCITY_WORLD_Z": [
+ "Speed relative to earth, in North/South direction",
+ b"VELOCITY WORLD Z",
+ b"Feet per second",
+ "Y",
+ ],
+ "VELOCITY_WORLD_X": [
+ "Speed relative to earth, in East/West direction",
+ b"VELOCITY WORLD X",
+ b"Feet per second",
+ "Y",
+ ],
+ "VELOCITY_WORLD_Y": [
+ "Speed relative to earth, in vertical direction",
+ b"VELOCITY WORLD Y",
+ b"Feet per second",
+ "Y",
+ ],
+ "ACCELERATION_WORLD_X": [
+ "Acceleration relative to earth, in east/west direction",
+ b"ACCELERATION WORLD X",
+ b"Feet per second squared",
+ "Y",
+ ],
+ "ACCELERATION_WORLD_Y": [
+ "Acceleration relative to earch, in vertical direction",
+ b"ACCELERATION WORLD Y",
+ b"Feet per second squared",
+ "Y",
+ ],
+ "ACCELERATION_WORLD_Z": [
+ "Acceleration relative to earth, in north/south direction",
+ b"ACCELERATION WORLD Z",
+ b"Feet per second squared",
+ "Y",
+ ],
+ "ACCELERATION_BODY_X": [
+ "Acceleration relative to aircraft axix, in east/west direction",
+ b"ACCELERATION BODY X",
+ b"Feet per second squared",
+ "Y",
+ ],
+ "ACCELERATION_BODY_Y": [
+ "Acceleration relative to aircraft axis, in vertical direction",
+ b"ACCELERATION BODY Y",
+ b"Feet per second squared",
+ "Y",
+ ],
+ "ACCELERATION_BODY_Z": [
+ "Acceleration relative to aircraft axis, in north/south direction",
+ b"ACCELERATION BODY Z",
+ b"Feet per second squared",
+ "Y",
+ ],
+ "ROTATION_VELOCITY_BODY_X": [
+ "Rotation relative to aircraft axis",
+ b"ROTATION VELOCITY BODY X",
+ b"Feet per second",
+ "Y",
+ ],
+ "ROTATION_VELOCITY_BODY_Y": [
+ "Rotation relative to aircraft axis",
+ b"ROTATION VELOCITY BODY Y",
+ b"Feet per second",
+ "Y",
+ ],
+ "ROTATION_VELOCITY_BODY_Z": [
+ "Rotation relative to aircraft axis",
+ b"ROTATION VELOCITY BODY Z",
+ b"Feet per second",
+ "Y",
+ ],
+ "RELATIVE_WIND_VELOCITY_BODY_X": [
+ "Lateral speed relative to wind",
+ b"RELATIVE WIND VELOCITY BODY X",
+ b"Feet per second",
+ "N",
+ ],
+ "RELATIVE_WIND_VELOCITY_BODY_Y": [
+ "Vertical speed relative to wind",
+ b"RELATIVE WIND VELOCITY BODY Y",
+ b"Feet per second",
+ "N",
+ ],
+ "RELATIVE_WIND_VELOCITY_BODY_Z": [
+ "Longitudinal speed relative to wind",
+ b"RELATIVE WIND VELOCITY BODY Z",
+ b"Feet per second",
+ "N",
+ ],
+ "PLANE_ALT_ABOVE_GROUND": [
+ "Altitude above the surface",
+ b"PLANE ALT ABOVE GROUND",
+ b"Feet",
+ "Y",
+ ],
+ "PLANE_LATITUDE": [
+ "Latitude of aircraft, North is positive, South negative",
+ b"PLANE LATITUDE",
+ b"Degrees",
+ "Y",
+ ],
+ "PLANE_LONGITUDE": [
+ "Longitude of aircraft, East is positive, West negative",
+ b"PLANE LONGITUDE",
+ b"Degrees",
+ "Y",
+ ],
+ "PLANE_ALTITUDE": ["Altitude of aircraft", b"PLANE ALTITUDE", b"Feet", "Y"],
+ "PLANE_PITCH_DEGREES": [
+ "Pitch angle, although the name mentions degrees the units used are radians",
+ b"PLANE PITCH DEGREES",
+ b"Radians",
+ "Y",
+ ],
+ "PLANE_BANK_DEGREES": [
+ "Bank angle, although the name mentions degrees the units used are radians",
+ b"PLANE BANK DEGREES",
+ b"Radians",
+ "Y",
+ ],
+ "PLANE_HEADING_DEGREES_TRUE": [
+ "Heading relative to true north, although the name mentions degrees the units used are radians",
+ b"PLANE HEADING DEGREES TRUE",
+ b"Radians",
+ "Y",
+ ],
+ "PLANE_HEADING_DEGREES_MAGNETIC": [
+ "Heading relative to magnetic north, although the name mentions degrees the units used are radians",
+ b"PLANE HEADING DEGREES MAGNETIC",
+ b"Radians",
+ "Y",
+ ],
+ "MAGVAR": ["Magnetic variation", b"MAGVAR", b"Degrees", "N"],
+ "GROUND_ALTITUDE": [
+ "Altitude of surface",
+ b"GROUND ALTITUDE",
+ b"Meters",
+ "N",
+ ],
+ "SIM_ON_GROUND": ["On ground flag", b"SIM ON GROUND", b"Bool", "N"],
+ "INCIDENCE_ALPHA": ["Angle of attack", b"INCIDENCE ALPHA", b"Radians", "N"],
+ "INCIDENCE_BETA": ["Sideslip angle", b"INCIDENCE BETA", b"Radians", "N"],
+ "WING_FLEX_PCT:index": [
+ "The current wing flex. Different values can be set for each wing (for example, during banking). Set an index of 1 for the left wing, and 2 for the right wing.",
+ b"WING FLEX PCT:index",
+ b"Percent over 100",
+ "Y",
+ ],
+ # "STRUCT_LATLONALT": ["Returns the latitude, longitude and altitude of the user aircraft.", b'STRUCT LATLONALT', b'SIMCONNECT_DATA_LATLONALT', 'N'],
+ # "STRUCT_LATLONALTPBH": ["Returns the pitch, bank and heading of the user aircraft.", b'STRUCT LATLONALTPBH', b'SIMCONNECT_DATA_LATLONALT', 'N'],
+ # "STRUCT_SURFACE_RELATIVE_VELOCITY": ["The relative surface velocity.", b'STRUCT SURFACE RELATIVE VELOCITY', b'SIMCONNECT_DATA_XYZ', 'N'],
+ # "STRUCT_WORLDVELOCITY": ["The world velocity.", b'STRUCT WORLDVELOCITY', b'SIMCONNECT_DATA_XYZ', 'N'],
+ # "STRUCT_WORLD_ROTATION_VELOCITY": ["The world rotation velocity.", b'STRUCT WORLD ROTATION VELOCITY', b'SIMCONNECT_DATA_XYZ', 'N'],
+ # "STRUCT_BODY_VELOCITY": ["The object body velocity.", b'STRUCT BODY VELOCITY', b'SIMCONNECT_DATA_XYZ', 'N'],
+ # "STRUCT_BODY_ROTATION_VELOCITY": ["The body rotation velocity. Individual body rotation values are in the Aircraft Position and Speed section.", b'STRUCT BODY ROTATION VELOCITY', b'SIMCONNECT_DATA_XYZ', 'N'],
+ # "STRUCT_WORLD_ACCELERATION": ["The world acceleration for each axis. Individual world acceleration values are in the Aircraft Position and Speed section.", b'STRUCT WORLD ACCELERATION', b'SIMCONNECT_DATA_XYZ', 'N'],
+ # "STRUCT_ENGINE_POSITION:index": ["The engine position relative to the reference datum position for the aircraft.", b'STRUCT ENGINE POSITION:index', b'SIMCONNECT_DATA_XYZ.', 'N'],
+ # "STRUCT_EYEPOINT_DYNAMIC_ANGLE": ["The angle of the eyepoint view. Zero, zero, zero is straight ahead.", b'STRUCT EYEPOINT DYNAMIC ANGLE', b'SIMCONNECT_DATA_XYZ', 'N'],
+ # "STRUCT_EYEPOINT_DYNAMIC_OFFSET": ["A variable offset away from the EYEPOINT POSITION", b'STRUCT EYEPOINT DYNAMIC OFFSET', b'SIMCONNECT_DATA_XYZ', 'N'],
+ # "EYEPOINT_POSITION": ["The eyepoint position relative to the reference datum position for the aircraft.", b'EYEPOINT POSITION', b'SIMCONNECT_DATA_XYZ', 'N'],
+ }
- class __AircraftFlightInstrumentationData(RequestHelper):
- list = {
- "AIRSPEED_TRUE": ["True airspeed", b'AIRSPEED TRUE', b'Knots', 'Y'],
- "AIRSPEED_INDICATED": ["Indicated airspeed", b'AIRSPEED INDICATED', b'Knots', 'Y'],
- "AIRSPEED_TRUE_CALIBRATE": ["Angle of True calibration scale on airspeed indicator", b'AIRSPEED TRUE CALIBRATE', b'Degrees', 'Y'],
- "AIRSPEED_BARBER_POLE": ["Redline airspeed (dynamic on some aircraft)", b'AIRSPEED BARBER POLE', b'Knots', 'N'],
- "AIRSPEED_MACH": ["Current mach", b'AIRSPEED MACH', b'Mach', 'N'],
- "VERTICAL_SPEED": ["Vertical speed indication", b'VERTICAL SPEED', b'feet/minute', 'Y'],
- "MACH_MAX_OPERATE": ["Maximum design mach", b'MACH MAX OPERATE', b'Mach', 'N'],
- "STALL_WARNING": ["Stall warning state", b'STALL WARNING', b'Bool', 'N'],
- "OVERSPEED_WARNING": ["Overspeed warning state", b'OVERSPEED WARNING', b'Bool', 'N'],
- "BARBER_POLE_MACH": ["Mach associated with maximum airspeed", b'BARBER POLE MACH', b'Mach', 'N'],
- "INDICATED_ALTITUDE": ["Altimeter indication", b'INDICATED ALTITUDE', b'Feet', 'Y'],
- "KOHLSMAN_SETTING_MB": ["Altimeter setting", b'KOHLSMAN SETTING MB', b'Millibars', 'Y'],
- "KOHLSMAN_SETTING_HG": ["Altimeter setting", b'KOHLSMAN SETTING HG', b'inHg', 'N'],
- "ATTITUDE_INDICATOR_PITCH_DEGREES": ["AI pitch indication", b'ATTITUDE INDICATOR PITCH DEGREES', b'Radians', 'N'],
- "ATTITUDE_INDICATOR_BANK_DEGREES": ["AI bank indication", b'ATTITUDE INDICATOR BANK DEGREES', b'Radians', 'N'],
- "ATTITUDE_BARS_POSITION": ["AI reference pitch reference bars", b'ATTITUDE BARS POSITION', b'Percent Over 100', 'N'],
- "ATTITUDE_CAGE": ["AI caged state", b'ATTITUDE CAGE', b'Bool', 'N'],
- "WISKEY_COMPASS_INDICATION_DEGREES": ["Magnetic compass indication", b'WISKEY COMPASS INDICATION DEGREES', b'Degrees', 'Y'],
- "PLANE_HEADING_DEGREES_GYRO": ["Heading indicator (directional gyro) indication", b'PLANE HEADING DEGREES GYRO', b'Radians', 'Y'],
- "HEADING_INDICATOR": ["Heading indicator (directional gyro) indication", b'HEADING INDICATOR', b'Radians', 'N'],
- "GYRO_DRIFT_ERROR": ["Angular error of heading indicator", b'GYRO DRIFT ERROR', b'Radians', 'N'],
- "DELTA_HEADING_RATE": ["Rate of turn of heading indicator", b'DELTA HEADING RATE', b'Radians per second', 'Y'],
- "TURN_COORDINATOR_BALL": ["Turn coordinator ball position", b'TURN COORDINATOR BALL', b'Position', 'N'],
- "ANGLE_OF_ATTACK_INDICATOR": ["AoA indication", b'ANGLE OF ATTACK INDICATOR', b'Radians', 'N'],
- "RADIO_HEIGHT": ["Radar altitude", b'RADIO HEIGHT', b'Feet', 'N'],
- "PARTIAL_PANEL_ADF": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL ADF', b'Enum', 'Y'],
- "PARTIAL_PANEL_AIRSPEED": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL AIRSPEED', b'Enum', 'Y'],
- "PARTIAL_PANEL_ALTIMETER": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL ALTIMETER', b'Enum', 'Y'],
- "PARTIAL_PANEL_ATTITUDE": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL ATTITUDE', b'Enum', 'Y'],
- "PARTIAL_PANEL_COMM": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL COMM', b'Enum', 'Y'],
- "PARTIAL_PANEL_COMPASS": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL COMPASS', b'Enum', 'Y'],
- "PARTIAL_PANEL_ELECTRICAL": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL ELECTRICAL', b'Enum', 'Y'],
- "PARTIAL_PANEL_AVIONICS": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL AVIONICS', b'Enum', 'N'],
- "PARTIAL_PANEL_ENGINE": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL ENGINE', b'Enum', 'Y'],
- "PARTIAL_PANEL_FUEL_INDICATOR": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL FUEL INDICATOR', b'Enum', 'N'],
- "PARTIAL_PANEL_HEADING": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL HEADING', b'Enum', 'Y'],
- "PARTIAL_PANEL_VERTICAL_VELOCITY": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL VERTICAL VELOCITY', b'Enum', 'Y'],
- "PARTIAL_PANEL_TRANSPONDER": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL TRANSPONDER', b'Enum', 'Y'],
- "PARTIAL_PANEL_NAV": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL NAV', b'Enum', 'Y'],
- "PARTIAL_PANEL_PITOT": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL PITOT', b'Enum', 'Y'],
- "PARTIAL_PANEL_TURN_COORDINATOR": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL TURN COORDINATOR', b'Enum', 'N'],
- "PARTIAL_PANEL_VACUUM": ["Gauge fail flag (0 = ok, 1 = fail, 2 = blank)", b'PARTIAL PANEL VACUUM', b'Enum', 'Y'],
- "MAX_G_FORCE": ["Maximum G force attained", b'MAX G FORCE', b'Gforce', 'N'],
- "MIN_G_FORCE": ["Minimum G force attained", b'MIN G FORCE', b'Gforce', 'N'],
- "SUCTION_PRESSURE": ["Vacuum system suction pressure", b'SUCTION PRESSURE', b'inHg', 'Y'],
- }
+ class __AircraftFlightInstrumentationData(RequestHelper):
+ list = {
+ "AIRSPEED_TRUE": ["True airspeed", b"AIRSPEED TRUE", b"Knots", "Y"],
+ "AIRSPEED_INDICATED": [
+ "Indicated airspeed",
+ b"AIRSPEED INDICATED",
+ b"Knots",
+ "Y",
+ ],
+ "AIRSPEED_TRUE_CALIBRATE": [
+ "Angle of True calibration scale on airspeed indicator",
+ b"AIRSPEED TRUE CALIBRATE",
+ b"Degrees",
+ "Y",
+ ],
+ "AIRSPEED_BARBER_POLE": [
+ "Redline airspeed (dynamic on some aircraft)",
+ b"AIRSPEED BARBER POLE",
+ b"Knots",
+ "N",
+ ],
+ "AIRSPEED_MACH": ["Current mach", b"AIRSPEED MACH", b"Mach", "N"],
+ "VERTICAL_SPEED": [
+ "Vertical speed indication",
+ b"VERTICAL SPEED",
+ b"feet/minute",
+ "Y",
+ ],
+ "MACH_MAX_OPERATE": [
+ "Maximum design mach",
+ b"MACH MAX OPERATE",
+ b"Mach",
+ "N",
+ ],
+ "STALL_WARNING": ["Stall warning state", b"STALL WARNING", b"Bool", "N"],
+ "OVERSPEED_WARNING": [
+ "Overspeed warning state",
+ b"OVERSPEED WARNING",
+ b"Bool",
+ "N",
+ ],
+ "BARBER_POLE_MACH": [
+ "Mach associated with maximum airspeed",
+ b"BARBER POLE MACH",
+ b"Mach",
+ "N",
+ ],
+ "INDICATED_ALTITUDE": [
+ "Altimeter indication",
+ b"INDICATED ALTITUDE",
+ b"Feet",
+ "Y",
+ ],
+ "KOHLSMAN_SETTING_MB": [
+ "Altimeter setting",
+ b"KOHLSMAN SETTING MB",
+ b"Millibars",
+ "Y",
+ ],
+ "KOHLSMAN_SETTING_HG": [
+ "Altimeter setting",
+ b"KOHLSMAN SETTING HG",
+ b"inHg",
+ "N",
+ ],
+ "ATTITUDE_INDICATOR_PITCH_DEGREES": [
+ "AI pitch indication",
+ b"ATTITUDE INDICATOR PITCH DEGREES",
+ b"Radians",
+ "N",
+ ],
+ "ATTITUDE_INDICATOR_BANK_DEGREES": [
+ "AI bank indication",
+ b"ATTITUDE INDICATOR BANK DEGREES",
+ b"Radians",
+ "N",
+ ],
+ "ATTITUDE_BARS_POSITION": [
+ "AI reference pitch reference bars",
+ b"ATTITUDE BARS POSITION",
+ b"Percent Over 100",
+ "N",
+ ],
+ "ATTITUDE_CAGE": ["AI caged state", b"ATTITUDE CAGE", b"Bool", "N"],
+ "WISKEY_COMPASS_INDICATION_DEGREES": [
+ "Magnetic compass indication",
+ b"WISKEY COMPASS INDICATION DEGREES",
+ b"Degrees",
+ "Y",
+ ],
+ "PLANE_HEADING_DEGREES_GYRO": [
+ "Heading indicator (directional gyro) indication",
+ b"PLANE HEADING DEGREES GYRO",
+ b"Radians",
+ "Y",
+ ],
+ "HEADING_INDICATOR": [
+ "Heading indicator (directional gyro) indication",
+ b"HEADING INDICATOR",
+ b"Radians",
+ "N",
+ ],
+ "GYRO_DRIFT_ERROR": [
+ "Angular error of heading indicator",
+ b"GYRO DRIFT ERROR",
+ b"Radians",
+ "N",
+ ],
+ "DELTA_HEADING_RATE": [
+ "Rate of turn of heading indicator",
+ b"DELTA HEADING RATE",
+ b"Radians per second",
+ "Y",
+ ],
+ "TURN_COORDINATOR_BALL": [
+ "Turn coordinator ball position",
+ b"TURN COORDINATOR BALL",
+ b"Position",
+ "N",
+ ],
+ "ANGLE_OF_ATTACK_INDICATOR": [
+ "AoA indication",
+ b"ANGLE OF ATTACK INDICATOR",
+ b"Radians",
+ "N",
+ ],
+ "RADIO_HEIGHT": ["Radar altitude", b"RADIO HEIGHT", b"Feet", "N"],
+ "PARTIAL_PANEL_ADF": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL ADF",
+ b"Enum",
+ "Y",
+ ],
+ "PARTIAL_PANEL_AIRSPEED": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL AIRSPEED",
+ b"Enum",
+ "Y",
+ ],
+ "PARTIAL_PANEL_ALTIMETER": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL ALTIMETER",
+ b"Enum",
+ "Y",
+ ],
+ "PARTIAL_PANEL_ATTITUDE": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL ATTITUDE",
+ b"Enum",
+ "Y",
+ ],
+ "PARTIAL_PANEL_COMM": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL COMM",
+ b"Enum",
+ "Y",
+ ],
+ "PARTIAL_PANEL_COMPASS": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL COMPASS",
+ b"Enum",
+ "Y",
+ ],
+ "PARTIAL_PANEL_ELECTRICAL": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL ELECTRICAL",
+ b"Enum",
+ "Y",
+ ],
+ "PARTIAL_PANEL_AVIONICS": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL AVIONICS",
+ b"Enum",
+ "N",
+ ],
+ "PARTIAL_PANEL_ENGINE": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL ENGINE",
+ b"Enum",
+ "Y",
+ ],
+ "PARTIAL_PANEL_FUEL_INDICATOR": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL FUEL INDICATOR",
+ b"Enum",
+ "N",
+ ],
+ "PARTIAL_PANEL_HEADING": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL HEADING",
+ b"Enum",
+ "Y",
+ ],
+ "PARTIAL_PANEL_VERTICAL_VELOCITY": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL VERTICAL VELOCITY",
+ b"Enum",
+ "Y",
+ ],
+ "PARTIAL_PANEL_TRANSPONDER": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL TRANSPONDER",
+ b"Enum",
+ "Y",
+ ],
+ "PARTIAL_PANEL_NAV": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL NAV",
+ b"Enum",
+ "Y",
+ ],
+ "PARTIAL_PANEL_PITOT": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL PITOT",
+ b"Enum",
+ "Y",
+ ],
+ "PARTIAL_PANEL_TURN_COORDINATOR": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL TURN COORDINATOR",
+ b"Enum",
+ "N",
+ ],
+ "PARTIAL_PANEL_VACUUM": [
+ "Gauge fail flag (0 = ok, 1 = fail, 2 = blank)",
+ b"PARTIAL PANEL VACUUM",
+ b"Enum",
+ "Y",
+ ],
+ "MAX_G_FORCE": ["Maximum G force attained", b"MAX G FORCE", b"Gforce", "N"],
+ "MIN_G_FORCE": ["Minimum G force attained", b"MIN G FORCE", b"Gforce", "N"],
+ "SUCTION_PRESSURE": [
+ "Vacuum system suction pressure",
+ b"SUCTION PRESSURE",
+ b"inHg",
+ "Y",
+ ],
+ }
- class __AircraftAvionicsData(RequestHelper):
- list = {
- "AVIONICS_MASTER_SWITCH": ["Avionics switch state", b'AVIONICS MASTER SWITCH', b'Bool', 'N'],
- "NAV_SOUND:index": ["Nav audio flag. Index of 1 or 2.", b'NAV SOUND:index', b'Bool', 'N'],
- "DME_SOUND": ["DME audio flag", b'DME SOUND', b'Bool', 'N'],
- "ADF_SOUND:index": ["ADF audio flag. Index of 0 or 1.", b'ADF SOUND:index', b'Bool', 'N'],
- "MARKER_SOUND": ["Marker audio flag", b'MARKER SOUND', b'Bool', 'N'],
- "COM_TRANSMIT:index": ["Audio panel com transmit state. Index of 1 or 2.", b'COM TRANSMIT:index', b'Bool', 'N'],
- "COM_RECIEVE_ALL": ["Flag if all Coms receiving", b'COM RECIEVE ALL', b'Bool', 'N'],
- "COM_ACTIVE_FREQUENCY:index": ["Com frequency. Index is 1 or 2.", b'COM ACTIVE FREQUENCY:index', b'MHz', 'N'],
- "COM_STANDBY_FREQUENCY:index": ["Com standby frequency. Index is 1 or 2.", b'COM STANDBY FREQUENCY:index', b'MHz', 'N'],
- "NAV_AVAILABLE:index": ["Flag if Nav equipped on aircraft", b'NAV AVAILABLE:index', b'Bool', 'N'],
- "NAV_ACTIVE_FREQUENCY:index": ["Nav active frequency. Index is 1 or 2.", b'NAV ACTIVE FREQUENCY:index', b'MHz', 'N'],
- "NAV_STANDBY_FREQUENCY:index": ["Nav standby frequency. Index is 1 or 2.", b'NAV STANDBY FREQUENCY:index', b'MHz', 'N'],
- "NAV_SIGNAL:index": ["Nav signal strength", b'NAV SIGNAL:index', b'Number', 'N'],
- "NAV_HAS_NAV:index": ["Flag if Nav has signal", b'NAV HAS NAV:index', b'Bool', 'N'],
- "NAV_HAS_LOCALIZER:index": ["Flag if tuned station is a localizer", b'NAV HAS LOCALIZER:index', b'Bool', 'N'],
- "NAV_HAS_DME:index": ["Flag if tuned station has a DME", b'NAV HAS DME:index', b'Bool', 'N'],
- "NAV_HAS_GLIDE_SLOPE:index": ["Flag if tuned station has a glideslope", b'NAV HAS GLIDE SLOPE:index', b'Bool', 'N'],
- "NAV_BACK_COURSE_FLAGS:index": ["Returns the following bit flags:; BIT0: 1=back course available; BIT1: 1=localizer tuned in; BIT2: 1=on course; BIT7: 1=station active", b'NAV BACK COURSE FLAGS:index', b'Flags', 'N'],
- "NAV_MAGVAR:index": ["Magnetic variation of tuned nav station", b'NAV MAGVAR:index', b'Degrees', 'N'],
- "NAV_RADIAL:index": ["Radial that aircraft is on", b'NAV RADIAL:index', b'Degrees', 'N'],
- "NAV_RADIAL_ERROR:index": ["Difference between current radial and OBS tuned radial", b'NAV RADIAL ERROR:index', b'Degrees', 'N'],
- "NAV_LOCALIZER:index": ["Localizer course heading", b'NAV LOCALIZER:index', b'Degrees', 'N'],
- "NAV_GLIDE_SLOPE_ERROR:index": ["Difference between current position and glideslope angle. Note that this provides 32 bit floating point precision, rather than the 8 bit integer precision of NAV GSI.", b'NAV GLIDE SLOPE ERROR:index', b'Degrees', 'N'],
- "NAV_CDI:index": ["CDI needle deflection (+/- 127)", b'NAV CDI:index', b'Number', 'N'],
- "NAV_GSI:index": ["Glideslope needle deflection (+/- 119). Note that this provides only 8 bit precision, whereas NAV GLIDE SLOPE ERROR provides 32 bit floating point precision.", b'NAV GSI:index', b'Number', 'N'],
- "NAV_GS_FLAG:index": ["Glideslope flag", b'NAV GS FLAG:index', b'Bool', 'N'],
- "NAV_OBS:index": ["OBS setting. Index of 1 or 2.", b'NAV OBS:index', b'Degrees', 'N'],
- "NAV_DME:index": ["DME distance", b'NAV DME:index', b'Nautical miles', 'N'],
- "NAV_DMESPEED:index": ["DME speed", b'NAV DMESPEED:index', b'Knots', 'N'],
- "ADF_ACTIVE_FREQUENCY:index": ["ADF frequency. Index of 1 or 2.", b'ADF ACTIVE FREQUENCY:index', b'Frequency ADF BCD32', 'N'],
- "ADF_STANDBY_FREQUENCY:index": ["ADF standby frequency", b'ADF STANDBY FREQUENCY:index', b'Hz', 'N'],
- "ADF_RADIAL:index": ["Current direction from NDB station", b'ADF RADIAL:index', b'Degrees', 'N'],
- "ADF_SIGNAL:index": ["Signal strength", b'ADF SIGNAL:index', b'Number', 'N'],
- "TRANSPONDER_CODE:index": ["4-digit code", b'TRANSPONDER CODE:index', b'BCO16', 'N'],
- "MARKER_BEACON_STATE": ["Marker beacon state:; 0 = None; 1 = Outer; 2 = Middle; 3 = Inner", b'MARKER BEACON STATE', b'Enum', 'Y'],
- "INNER_MARKER": ["Inner marker state", b'INNER MARKER', b'Bool', 'Y'],
- "MIDDLE_MARKER": ["Middle marker state", b'MIDDLE MARKER', b'Bool', 'Y'],
- "OUTER_MARKER": ["Outer marker state", b'OUTER MARKER', b'Bool', 'Y'],
- "NAV_RAW_GLIDE_SLOPE:index": ["Glide slope angle", b'NAV RAW GLIDE SLOPE:index', b'Degrees', 'N'],
- "ADF_CARD": ["ADF compass rose setting", b'ADF CARD', b'Degrees', 'N'],
- "HSI_CDI_NEEDLE": ["Needle deflection (+/- 127)", b'HSI CDI NEEDLE', b'Number', 'N'],
- "HSI_GSI_NEEDLE": ["Needle deflection (+/- 119)", b'HSI GSI NEEDLE', b'Number', 'N'],
- "HSI_CDI_NEEDLE_VALID": ["Signal valid", b'HSI CDI NEEDLE VALID', b'Bool', 'N'],
- "HSI_GSI_NEEDLE_VALID": ["Signal valid", b'HSI GSI NEEDLE VALID', b'Bool', 'N'],
- "HSI_TF_FLAGS": ["Nav TO/FROM flag:; 0 = Off; 1 = TO; 2 = FROM", b'HSI TF FLAGS', b'Enum', 'N'],
- "HSI_BEARING_VALID": ["This will return true if the HSI BEARING variable contains valid data.", b'HSI BEARING VALID', b'Bool', 'N'],
- "HSI_BEARING": ["If the GPS DRIVES NAV1 variable is true and the HSI BEARING VALID variable is true, this variable contains the HSI needle bearing. If the GPS DRIVES NAV1 variable is false and the HSI BEARING VALID variable is true, this variable contains the ADF1 frequency.", b'HSI BEARING', b'Degrees', 'N'],
- "HSI_HAS_LOCALIZER": ["Station is a localizer", b'HSI HAS LOCALIZER', b'Bool', 'N'],
- "HSI_SPEED": ["DME/GPS speed", b'HSI SPEED', b'Knots', 'N'],
- "HSI_DISTANCE": ["DME/GPS distance", b'HSI DISTANCE', b'Nautical miles', 'N'],
- "GPS_POSITION_LAT": ["Current GPS latitude", b'GPS POSITION LAT', b'Degrees', 'N'],
- "GPS_POSITION_LON": ["Current GPS longitude", b'GPS POSITION LON', b'Degrees', 'N'],
- "GPS_POSITION_ALT": ["Current GPS altitude", b'GPS POSITION ALT', b'Meters', 'N'],
- "GPS_MAGVAR": ["Current GPS magnetic variation", b'GPS MAGVAR', b'Radians', 'N'],
- "GPS_IS_ACTIVE_FLIGHT_PLAN": ["Flight plan mode active", b'GPS IS ACTIVE FLIGHT PLAN', b'Bool', 'N'],
- "GPS_IS_ACTIVE_WAY_POINT": ["Waypoint mode active", b'GPS IS ACTIVE WAY POINT', b'Bool', 'N'],
- "GPS_IS_ARRIVED": ["Is flight plan destination reached", b'GPS IS ARRIVED', b'Bool', 'N'],
- "GPS_IS_DIRECTTO_FLIGHTPLAN": ["Is Direct To Waypoint mode active", b'GPS IS DIRECTTO FLIGHTPLAN', b'Bool', 'N'],
- "GPS_GROUND_SPEED": ["Current ground speed", b'GPS GROUND SPEED', b'Meters per second', 'N'],
- "GPS_GROUND_TRUE_HEADING": ["Current true heading", b'GPS GROUND TRUE HEADING', b'Radians', 'N'],
- "GPS_GROUND_MAGNETIC_TRACK": ["Current magnetic ground track", b'GPS GROUND MAGNETIC TRACK', b'Radians', 'N'],
- "GPS_GROUND_TRUE_TRACK": ["Current true ground track", b'GPS GROUND TRUE TRACK', b'Radians', 'N'],
- "GPS_WP_DISTANCE": ["Distance to waypoint", b'GPS WP DISTANCE', b'Meters', 'N'],
- "GPS_WP_BEARING": ["Magnetic bearing to waypoint", b'GPS WP BEARING', b'Radians', 'N'],
- "GPS_WP_TRUE_BEARING": ["True bearing to waypoint", b'GPS WP TRUE BEARING', b'Radians', 'N'],
- "GPS_WP_CROSS_TRK": ["Cross track distance", b'GPS WP CROSS TRK', b'Meters', 'N'],
- "GPS_WP_DESIRED_TRACK": ["Desired track to waypoint", b'GPS WP DESIRED TRACK', b'Radians', 'N'],
- "GPS_WP_TRUE_REQ_HDG": ["Required true heading to waypoint", b'GPS WP TRUE REQ HDG', b'Radians', 'N'],
- "GPS_WP_VERTICAL_SPEED": ["Vertical speed to waypoint", b'GPS WP VERTICAL SPEED', b'Meters per second', 'N'],
- "GPS_WP_TRACK_ANGLE_ERROR": ["Tracking angle error to waypoint", b'GPS WP TRACK ANGLE ERROR', b'Radians', 'N'],
- "GPS_ETE": ["Estimated time enroute to destination", b'GPS ETE', b'Seconds', 'N'],
- "GPS_ETA": ["Estimated time of arrival at destination", b'GPS ETA', b'Seconds', 'N'],
- "GPS_WP_NEXT_LAT": ["Latitude of next waypoint", b'GPS WP NEXT LAT', b'Degrees', 'N'],
- "GPS_WP_NEXT_LON": ["Longitude of next waypoint", b'GPS WP NEXT LON', b'Degrees', 'N'],
- "GPS_WP_NEXT_ALT": ["Altitude of next waypoint", b'GPS WP NEXT ALT', b'Meters', 'N'],
- "GPS_WP_PREV_VALID": ["Is previous waypoint valid (i.e. current waypoint is not the first waypoint)", b'GPS WP PREV VALID', b'Bool', 'N'],
- "GPS_WP_PREV_LAT": ["Latitude of previous waypoint", b'GPS WP PREV LAT', b'Degrees', 'N'],
- "GPS_WP_PREV_LON": ["Longitude of previous waypoint", b'GPS WP PREV LON', b'Degrees', 'N'],
- "GPS_WP_PREV_ALT": ["Altitude of previous waypoint", b'GPS WP PREV ALT', b'Meters', 'N'],
- "GPS_WP_ETE": ["Estimated time enroute to waypoint", b'GPS WP ETE', b'Seconds', 'N'],
- "GPS_WP_ETA": ["Estimated time of arrival at waypoint", b'GPS WP ETA', b'Seconds', 'N'],
- "GPS_COURSE_TO_STEER": ["Suggested heading to steer (for autopilot)", b'GPS COURSE TO STEER', b'Radians', 'N'],
- "GPS_FLIGHT_PLAN_WP_INDEX": ["Index of waypoint", b'GPS FLIGHT PLAN WP INDEX', b'Number', 'N'],
- "GPS_FLIGHT_PLAN_WP_COUNT": ["Number of waypoints", b'GPS FLIGHT PLAN WP COUNT', b'Number', 'N'],
- "GPS_IS_ACTIVE_WP_LOCKED": ["Is switching to next waypoint locked", b'GPS IS ACTIVE WP LOCKED', b'Bool', 'N'],
- "GPS_IS_APPROACH_LOADED": ["Is approach loaded", b'GPS IS APPROACH LOADED', b'Bool', 'N'],
- "GPS_IS_APPROACH_ACTIVE": ["Is approach mode active", b'GPS IS APPROACH ACTIVE', b'Bool', 'N'],
- "GPS_APPROACH_IS_WP_RUNWAY": ["Waypoint is the runway", b'GPS APPROACH IS WP RUNWAY', b'Bool', 'N'],
- "GPS_APPROACH_APPROACH_INDEX": ["Index of approach for given airport", b'GPS APPROACH APPROACH INDEX', b'Number', 'N'],
- "GPS_APPROACH_TRANSITION_INDEX": ["Index of approach transition", b'GPS APPROACH TRANSITION INDEX', b'Number', 'N'],
- "GPS_APPROACH_IS_FINAL": ["Is approach transition final approach segment", b'GPS APPROACH IS FINAL', b'Bool', 'N'],
- "GPS_APPROACH_IS_MISSED": ["Is approach segment missed approach segment", b'GPS APPROACH IS MISSED', b'Bool', 'N'],
- "GPS_APPROACH_TIMEZONE_DEVIATION": ["Deviation of local time from GMT", b'GPS APPROACH TIMEZONE DEVIATION', b'Seconds', 'N'],
- "GPS_APPROACH_WP_INDEX": ["Index of current waypoint", b'GPS APPROACH WP INDEX', b'Number', 'N'],
- "GPS_APPROACH_WP_COUNT": ["Number of waypoints", b'GPS APPROACH WP COUNT', b'Number', 'N'],
- "GPS_DRIVES_NAV1": ["GPS is driving Nav 1 indicator", b'GPS DRIVES NAV1', b'Bool', 'N'],
- "COM_RECEIVE_ALL": ["Toggles all COM radios to receive on", b'COM RECEIVE ALL', b'Bool', 'N'],
- "COM_AVAILABLE": ["True if either COM1 or COM2 is available", b'COM AVAILABLE', b'Bool', 'N'],
- "COM_TEST:index": ["Enter an index of 1 or 2. True if the COM system is working.", b'COM TEST:index', b'Bool', 'N'],
- "TRANSPONDER_AVAILABLE": ["True if a transponder is available", b'TRANSPONDER AVAILABLE', b'Bool', 'N'],
- "ADF_AVAILABLE": ["True if ADF is available", b'ADF AVAILABLE', b'Bool', 'N'],
- "ADF_FREQUENCY:index": ["Legacy, use ADF ACTIVE FREQUENCY", b'ADF FREQUENCY:index', b'Frequency BCD16', 'N'],
- "ADF_EXT_FREQUENCY:index": ["Legacy, use ADF ACTIVE FREQUENCY", b'ADF EXT FREQUENCY:index', b'Frequency BCD16', 'N'],
- "ADF_IDENT": ["ICAO code", b'ADF IDENT', b'String', 'N'],
- "ADF_NAME": ["Descriptive name", b'ADF NAME', b'String', 'N'],
- "NAV_IDENT": ["ICAO code", b'NAV IDENT', b'String', 'N'],
- "NAV_NAME": ["Descriptive name", b'NAV NAME', b'String', 'N'],
- "NAV_CODES:index": ["Returns bit flags with the following meaning:; BIT7: 0= VOR 1= Localizer; BIT6: 1= glideslope available; BIT5: 1= no localizer backcourse; BIT4: 1= DME transmitter at glide slope transmitter; BIT3: 1= no nav signal available; BIT2: 1= voice available; BIT1: 1 = TACAN available; BIT0: 1= DME available", b'NAV CODES:index', b'Flags', 'N'],
- "NAV_GLIDE_SLOPE": ["The glide slope gradient.", b'NAV GLIDE SLOPE', b'Number', 'N'],
- "NAV_RELATIVE_BEARING_TO_STATION:index": ["Relative bearing to station", b'NAV RELATIVE BEARING TO STATION:index', b'Degrees', 'N'],
- "SELECTED_DME": ["Selected DME", b'SELECTED DME', b'Number', 'N'],
- "GPS_WP_NEXT_ID": ["ID of next GPS waypoint", b'GPS WP NEXT ID', b'String', 'N'],
- "GPS_WP_PREV_ID": ["ID of previous GPS waypoint", b'GPS WP PREV ID', b'String', 'N'],
- "GPS_TARGET_DISTANCE": ["Distance to target", b'GPS TARGET DISTANCE', b'Meters', 'N'],
- "GPS_TARGET_ALTITUDE": ["Altitude of GPS target", b'GPS TARGET ALTITUDE', b'Meters', 'N'],
- # "ADF_LATLONALT:index": ["Returns the latitude, longitude and altitude of the station the radio equipment is currently tuned to, or zeros if the radio is not tuned to any ADF station. Index of 1 or 2 for ADF 1 and ADF 2.", b'ADF LATLONALT:index', b'SIMCONNECT_DATA_LATLONALT', 'N'],
- # "NAV_VOR_LATLONALT:index": ["Returns the VOR station latitude, longitude and altitude.", b'NAV VOR LATLONALT:index', b'SIMCONNECT_DATA_LATLONALT', 'N'],
- # "NAV_GS_LATLONALT:index": ["Returns the glide slope.", b'NAV GS LATLONALT:index', b'SIMCONNECT_DATA_LATLONALT', 'N'],
- # "NAV_DME_LATLONALT:index": ["Returns the DME station.", b'NAV DME LATLONALT:index', b'SIMCONNECT_DATA_LATLONALT', 'N'],
- # "INNER_MARKER_LATLONALT": ["Returns the latitude, longitude and altitude of the inner marker of an approach to a runway, if the aircraft is within the required proximity, otherwise it will return zeros.", b'INNER MARKER LATLONALT', b'SIMCONNECT_DATA_LATLONALT', 'N'],
- # "MIDDLE_MARKER_LATLONALT": ["Returns the latitude, longitude and altitude of the middle marker.", b'MIDDLE MARKER LATLONALT', b'SIMCONNECT_DATA_LATLONALT', 'N'],
- # "OUTER_MARKER_LATLONALT": ["Returns the latitude, longitude and altitude of the outer marker.", b'OUTER MARKER LATLONALT', b'SIMCONNECT_DATA_LATLONALT', 'N'],
- }
+ class __AircraftAvionicsData(RequestHelper):
+ list = {
+ "AVIONICS_MASTER_SWITCH": [
+ "Avionics switch state",
+ b"AVIONICS MASTER SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "NAV_SOUND:index": [
+ "Nav audio flag. Index of 1 or 2.",
+ b"NAV SOUND:index",
+ b"Bool",
+ "N",
+ ],
+ "DME_SOUND": ["DME audio flag", b"DME SOUND", b"Bool", "N"],
+ "ADF_SOUND:index": [
+ "ADF audio flag. Index of 0 or 1.",
+ b"ADF SOUND:index",
+ b"Bool",
+ "N",
+ ],
+ "MARKER_SOUND": ["Marker audio flag", b"MARKER SOUND", b"Bool", "N"],
+ "COM_TRANSMIT:index": [
+ "Audio panel com transmit state. Index of 1 or 2.",
+ b"COM TRANSMIT:index",
+ b"Bool",
+ "N",
+ ],
+ "COM_RECIEVE_ALL": [
+ "Flag if all Coms receiving",
+ b"COM RECIEVE ALL",
+ b"Bool",
+ "N",
+ ],
+ "COM_ACTIVE_FREQUENCY:index": [
+ "Com frequency. Index is 1 or 2.",
+ b"COM ACTIVE FREQUENCY:index",
+ b"MHz",
+ "N",
+ ],
+ "COM_STANDBY_FREQUENCY:index": [
+ "Com standby frequency. Index is 1 or 2.",
+ b"COM STANDBY FREQUENCY:index",
+ b"MHz",
+ "N",
+ ],
+ "NAV_AVAILABLE:index": [
+ "Flag if Nav equipped on aircraft",
+ b"NAV AVAILABLE:index",
+ b"Bool",
+ "N",
+ ],
+ "NAV_ACTIVE_FREQUENCY:index": [
+ "Nav active frequency. Index is 1 or 2.",
+ b"NAV ACTIVE FREQUENCY:index",
+ b"MHz",
+ "N",
+ ],
+ "NAV_STANDBY_FREQUENCY:index": [
+ "Nav standby frequency. Index is 1 or 2.",
+ b"NAV STANDBY FREQUENCY:index",
+ b"MHz",
+ "N",
+ ],
+ "NAV_SIGNAL:index": [
+ "Nav signal strength",
+ b"NAV SIGNAL:index",
+ b"Number",
+ "N",
+ ],
+ "NAV_HAS_NAV:index": [
+ "Flag if Nav has signal",
+ b"NAV HAS NAV:index",
+ b"Bool",
+ "N",
+ ],
+ "NAV_HAS_LOCALIZER:index": [
+ "Flag if tuned station is a localizer",
+ b"NAV HAS LOCALIZER:index",
+ b"Bool",
+ "N",
+ ],
+ "NAV_HAS_DME:index": [
+ "Flag if tuned station has a DME",
+ b"NAV HAS DME:index",
+ b"Bool",
+ "N",
+ ],
+ "NAV_HAS_GLIDE_SLOPE:index": [
+ "Flag if tuned station has a glideslope",
+ b"NAV HAS GLIDE SLOPE:index",
+ b"Bool",
+ "N",
+ ],
+ "NAV_BACK_COURSE_FLAGS:index": [
+ "Returns the following bit flags:; BIT0: 1=back course available; BIT1: 1=localizer tuned in; BIT2: 1=on course; BIT7: 1=station active",
+ b"NAV BACK COURSE FLAGS:index",
+ b"Flags",
+ "N",
+ ],
+ "NAV_MAGVAR:index": [
+ "Magnetic variation of tuned nav station",
+ b"NAV MAGVAR:index",
+ b"Degrees",
+ "N",
+ ],
+ "NAV_RADIAL:index": [
+ "Radial that aircraft is on",
+ b"NAV RADIAL:index",
+ b"Degrees",
+ "N",
+ ],
+ "NAV_RADIAL_ERROR:index": [
+ "Difference between current radial and OBS tuned radial",
+ b"NAV RADIAL ERROR:index",
+ b"Degrees",
+ "N",
+ ],
+ "NAV_LOCALIZER:index": [
+ "Localizer course heading",
+ b"NAV LOCALIZER:index",
+ b"Degrees",
+ "N",
+ ],
+ "NAV_GLIDE_SLOPE_ERROR:index": [
+ "Difference between current position and glideslope angle. Note that this provides 32 bit floating point precision, rather than the 8 bit integer precision of NAV GSI.",
+ b"NAV GLIDE SLOPE ERROR:index",
+ b"Degrees",
+ "N",
+ ],
+ "NAV_CDI:index": [
+ "CDI needle deflection (+/- 127)",
+ b"NAV CDI:index",
+ b"Number",
+ "N",
+ ],
+ "NAV_GSI:index": [
+ "Glideslope needle deflection (+/- 119). Note that this provides only 8 bit precision, whereas NAV GLIDE SLOPE ERROR provides 32 bit floating point precision.",
+ b"NAV GSI:index",
+ b"Number",
+ "N",
+ ],
+ "NAV_GS_FLAG:index": [
+ "Glideslope flag",
+ b"NAV GS FLAG:index",
+ b"Bool",
+ "N",
+ ],
+ "NAV_OBS:index": [
+ "OBS setting. Index of 1 or 2.",
+ b"NAV OBS:index",
+ b"Degrees",
+ "N",
+ ],
+ "NAV_DME:index": ["DME distance", b"NAV DME:index", b"Nautical miles", "N"],
+ "NAV_DMESPEED:index": ["DME speed", b"NAV DMESPEED:index", b"Knots", "N"],
+ "ADF_ACTIVE_FREQUENCY:index": [
+ "ADF frequency. Index of 1 or 2.",
+ b"ADF ACTIVE FREQUENCY:index",
+ b"Frequency ADF BCD32",
+ "N",
+ ],
+ "ADF_STANDBY_FREQUENCY:index": [
+ "ADF standby frequency",
+ b"ADF STANDBY FREQUENCY:index",
+ b"Hz",
+ "N",
+ ],
+ "ADF_RADIAL:index": [
+ "Current direction from NDB station",
+ b"ADF RADIAL:index",
+ b"Degrees",
+ "N",
+ ],
+ "ADF_SIGNAL:index": [
+ "Signal strength",
+ b"ADF SIGNAL:index",
+ b"Number",
+ "N",
+ ],
+ "TRANSPONDER_CODE:index": [
+ "4-digit code",
+ b"TRANSPONDER CODE:index",
+ b"BCO16",
+ "N",
+ ],
+ "MARKER_BEACON_STATE": [
+ "Marker beacon state:; 0 = None; 1 = Outer; 2 = Middle; 3 = Inner",
+ b"MARKER BEACON STATE",
+ b"Enum",
+ "Y",
+ ],
+ "INNER_MARKER": ["Inner marker state", b"INNER MARKER", b"Bool", "Y"],
+ "MIDDLE_MARKER": ["Middle marker state", b"MIDDLE MARKER", b"Bool", "Y"],
+ "OUTER_MARKER": ["Outer marker state", b"OUTER MARKER", b"Bool", "Y"],
+ "NAV_RAW_GLIDE_SLOPE:index": [
+ "Glide slope angle",
+ b"NAV RAW GLIDE SLOPE:index",
+ b"Degrees",
+ "N",
+ ],
+ "ADF_CARD": ["ADF compass rose setting", b"ADF CARD", b"Degrees", "N"],
+ "HSI_CDI_NEEDLE": [
+ "Needle deflection (+/- 127)",
+ b"HSI CDI NEEDLE",
+ b"Number",
+ "N",
+ ],
+ "HSI_GSI_NEEDLE": [
+ "Needle deflection (+/- 119)",
+ b"HSI GSI NEEDLE",
+ b"Number",
+ "N",
+ ],
+ "HSI_CDI_NEEDLE_VALID": [
+ "Signal valid",
+ b"HSI CDI NEEDLE VALID",
+ b"Bool",
+ "N",
+ ],
+ "HSI_GSI_NEEDLE_VALID": [
+ "Signal valid",
+ b"HSI GSI NEEDLE VALID",
+ b"Bool",
+ "N",
+ ],
+ "HSI_TF_FLAGS": [
+ "Nav TO/FROM flag:; 0 = Off; 1 = TO; 2 = FROM",
+ b"HSI TF FLAGS",
+ b"Enum",
+ "N",
+ ],
+ "HSI_BEARING_VALID": [
+ "This will return true if the HSI BEARING variable contains valid data.",
+ b"HSI BEARING VALID",
+ b"Bool",
+ "N",
+ ],
+ "HSI_BEARING": [
+ "If the GPS DRIVES NAV1 variable is true and the HSI BEARING VALID variable is true, this variable contains the HSI needle bearing. If the GPS DRIVES NAV1 variable is false and the HSI BEARING VALID variable is true, this variable contains the ADF1 frequency.",
+ b"HSI BEARING",
+ b"Degrees",
+ "N",
+ ],
+ "HSI_HAS_LOCALIZER": [
+ "Station is a localizer",
+ b"HSI HAS LOCALIZER",
+ b"Bool",
+ "N",
+ ],
+ "HSI_SPEED": ["DME/GPS speed", b"HSI SPEED", b"Knots", "N"],
+ "HSI_DISTANCE": [
+ "DME/GPS distance",
+ b"HSI DISTANCE",
+ b"Nautical miles",
+ "N",
+ ],
+ "GPS_POSITION_LAT": [
+ "Current GPS latitude",
+ b"GPS POSITION LAT",
+ b"Degrees",
+ "N",
+ ],
+ "GPS_POSITION_LON": [
+ "Current GPS longitude",
+ b"GPS POSITION LON",
+ b"Degrees",
+ "N",
+ ],
+ "GPS_POSITION_ALT": [
+ "Current GPS altitude",
+ b"GPS POSITION ALT",
+ b"Meters",
+ "N",
+ ],
+ "GPS_MAGVAR": [
+ "Current GPS magnetic variation",
+ b"GPS MAGVAR",
+ b"Radians",
+ "N",
+ ],
+ "GPS_IS_ACTIVE_FLIGHT_PLAN": [
+ "Flight plan mode active",
+ b"GPS IS ACTIVE FLIGHT PLAN",
+ b"Bool",
+ "N",
+ ],
+ "GPS_IS_ACTIVE_WAY_POINT": [
+ "Waypoint mode active",
+ b"GPS IS ACTIVE WAY POINT",
+ b"Bool",
+ "N",
+ ],
+ "GPS_IS_ARRIVED": [
+ "Is flight plan destination reached",
+ b"GPS IS ARRIVED",
+ b"Bool",
+ "N",
+ ],
+ "GPS_IS_DIRECTTO_FLIGHTPLAN": [
+ "Is Direct To Waypoint mode active",
+ b"GPS IS DIRECTTO FLIGHTPLAN",
+ b"Bool",
+ "N",
+ ],
+ "GPS_GROUND_SPEED": [
+ "Current ground speed",
+ b"GPS GROUND SPEED",
+ b"Meters per second",
+ "N",
+ ],
+ "GPS_GROUND_TRUE_HEADING": [
+ "Current true heading",
+ b"GPS GROUND TRUE HEADING",
+ b"Radians",
+ "N",
+ ],
+ "GPS_GROUND_MAGNETIC_TRACK": [
+ "Current magnetic ground track",
+ b"GPS GROUND MAGNETIC TRACK",
+ b"Radians",
+ "N",
+ ],
+ "GPS_GROUND_TRUE_TRACK": [
+ "Current true ground track",
+ b"GPS GROUND TRUE TRACK",
+ b"Radians",
+ "N",
+ ],
+ "GPS_WP_DISTANCE": [
+ "Distance to waypoint",
+ b"GPS WP DISTANCE",
+ b"Meters",
+ "N",
+ ],
+ "GPS_WP_BEARING": [
+ "Magnetic bearing to waypoint",
+ b"GPS WP BEARING",
+ b"Radians",
+ "N",
+ ],
+ "GPS_WP_TRUE_BEARING": [
+ "True bearing to waypoint",
+ b"GPS WP TRUE BEARING",
+ b"Radians",
+ "N",
+ ],
+ "GPS_WP_CROSS_TRK": [
+ "Cross track distance",
+ b"GPS WP CROSS TRK",
+ b"Meters",
+ "N",
+ ],
+ "GPS_WP_DESIRED_TRACK": [
+ "Desired track to waypoint",
+ b"GPS WP DESIRED TRACK",
+ b"Radians",
+ "N",
+ ],
+ "GPS_WP_TRUE_REQ_HDG": [
+ "Required true heading to waypoint",
+ b"GPS WP TRUE REQ HDG",
+ b"Radians",
+ "N",
+ ],
+ "GPS_WP_VERTICAL_SPEED": [
+ "Vertical speed to waypoint",
+ b"GPS WP VERTICAL SPEED",
+ b"Meters per second",
+ "N",
+ ],
+ "GPS_WP_TRACK_ANGLE_ERROR": [
+ "Tracking angle error to waypoint",
+ b"GPS WP TRACK ANGLE ERROR",
+ b"Radians",
+ "N",
+ ],
+ "GPS_ETE": [
+ "Estimated time enroute to destination",
+ b"GPS ETE",
+ b"Seconds",
+ "N",
+ ],
+ "GPS_ETA": [
+ "Estimated time of arrival at destination",
+ b"GPS ETA",
+ b"Seconds",
+ "N",
+ ],
+ "GPS_WP_NEXT_LAT": [
+ "Latitude of next waypoint",
+ b"GPS WP NEXT LAT",
+ b"Degrees",
+ "N",
+ ],
+ "GPS_WP_NEXT_LON": [
+ "Longitude of next waypoint",
+ b"GPS WP NEXT LON",
+ b"Degrees",
+ "N",
+ ],
+ "GPS_WP_NEXT_ALT": [
+ "Altitude of next waypoint",
+ b"GPS WP NEXT ALT",
+ b"Meters",
+ "N",
+ ],
+ "GPS_WP_PREV_VALID": [
+ "Is previous waypoint valid (i.e. current waypoint is not the first waypoint)",
+ b"GPS WP PREV VALID",
+ b"Bool",
+ "N",
+ ],
+ "GPS_WP_PREV_LAT": [
+ "Latitude of previous waypoint",
+ b"GPS WP PREV LAT",
+ b"Degrees",
+ "N",
+ ],
+ "GPS_WP_PREV_LON": [
+ "Longitude of previous waypoint",
+ b"GPS WP PREV LON",
+ b"Degrees",
+ "N",
+ ],
+ "GPS_WP_PREV_ALT": [
+ "Altitude of previous waypoint",
+ b"GPS WP PREV ALT",
+ b"Meters",
+ "N",
+ ],
+ "GPS_WP_ETE": [
+ "Estimated time enroute to waypoint",
+ b"GPS WP ETE",
+ b"Seconds",
+ "N",
+ ],
+ "GPS_WP_ETA": [
+ "Estimated time of arrival at waypoint",
+ b"GPS WP ETA",
+ b"Seconds",
+ "N",
+ ],
+ "GPS_COURSE_TO_STEER": [
+ "Suggested heading to steer (for autopilot)",
+ b"GPS COURSE TO STEER",
+ b"Radians",
+ "N",
+ ],
+ "GPS_FLIGHT_PLAN_WP_INDEX": [
+ "Index of waypoint",
+ b"GPS FLIGHT PLAN WP INDEX",
+ b"Number",
+ "N",
+ ],
+ "GPS_FLIGHT_PLAN_WP_COUNT": [
+ "Number of waypoints",
+ b"GPS FLIGHT PLAN WP COUNT",
+ b"Number",
+ "N",
+ ],
+ "GPS_IS_ACTIVE_WP_LOCKED": [
+ "Is switching to next waypoint locked",
+ b"GPS IS ACTIVE WP LOCKED",
+ b"Bool",
+ "N",
+ ],
+ "GPS_IS_APPROACH_LOADED": [
+ "Is approach loaded",
+ b"GPS IS APPROACH LOADED",
+ b"Bool",
+ "N",
+ ],
+ "GPS_IS_APPROACH_ACTIVE": [
+ "Is approach mode active",
+ b"GPS IS APPROACH ACTIVE",
+ b"Bool",
+ "N",
+ ],
+ "GPS_APPROACH_IS_WP_RUNWAY": [
+ "Waypoint is the runway",
+ b"GPS APPROACH IS WP RUNWAY",
+ b"Bool",
+ "N",
+ ],
+ "GPS_APPROACH_APPROACH_INDEX": [
+ "Index of approach for given airport",
+ b"GPS APPROACH APPROACH INDEX",
+ b"Number",
+ "N",
+ ],
+ "GPS_APPROACH_TRANSITION_INDEX": [
+ "Index of approach transition",
+ b"GPS APPROACH TRANSITION INDEX",
+ b"Number",
+ "N",
+ ],
+ "GPS_APPROACH_IS_FINAL": [
+ "Is approach transition final approach segment",
+ b"GPS APPROACH IS FINAL",
+ b"Bool",
+ "N",
+ ],
+ "GPS_APPROACH_IS_MISSED": [
+ "Is approach segment missed approach segment",
+ b"GPS APPROACH IS MISSED",
+ b"Bool",
+ "N",
+ ],
+ "GPS_APPROACH_TIMEZONE_DEVIATION": [
+ "Deviation of local time from GMT",
+ b"GPS APPROACH TIMEZONE DEVIATION",
+ b"Seconds",
+ "N",
+ ],
+ "GPS_APPROACH_WP_INDEX": [
+ "Index of current waypoint",
+ b"GPS APPROACH WP INDEX",
+ b"Number",
+ "N",
+ ],
+ "GPS_APPROACH_WP_COUNT": [
+ "Number of waypoints",
+ b"GPS APPROACH WP COUNT",
+ b"Number",
+ "N",
+ ],
+ "GPS_DRIVES_NAV1": [
+ "GPS is driving Nav 1 indicator",
+ b"GPS DRIVES NAV1",
+ b"Bool",
+ "N",
+ ],
+ "COM_RECEIVE_ALL": [
+ "Toggles all COM radios to receive on",
+ b"COM RECEIVE ALL",
+ b"Bool",
+ "N",
+ ],
+ "COM_AVAILABLE": [
+ "True if either COM1 or COM2 is available",
+ b"COM AVAILABLE",
+ b"Bool",
+ "N",
+ ],
+ "COM_TEST:index": [
+ "Enter an index of 1 or 2. True if the COM system is working.",
+ b"COM TEST:index",
+ b"Bool",
+ "N",
+ ],
+ "TRANSPONDER_AVAILABLE": [
+ "True if a transponder is available",
+ b"TRANSPONDER AVAILABLE",
+ b"Bool",
+ "N",
+ ],
+ "ADF_AVAILABLE": [
+ "True if ADF is available",
+ b"ADF AVAILABLE",
+ b"Bool",
+ "N",
+ ],
+ "ADF_FREQUENCY:index": [
+ "Legacy, use ADF ACTIVE FREQUENCY",
+ b"ADF FREQUENCY:index",
+ b"Frequency BCD16",
+ "N",
+ ],
+ "ADF_EXT_FREQUENCY:index": [
+ "Legacy, use ADF ACTIVE FREQUENCY",
+ b"ADF EXT FREQUENCY:index",
+ b"Frequency BCD16",
+ "N",
+ ],
+ "ADF_IDENT": ["ICAO code", b"ADF IDENT", b"String", "N"],
+ "ADF_NAME": ["Descriptive name", b"ADF NAME", b"String", "N"],
+ "NAV_IDENT": ["ICAO code", b"NAV IDENT", b"String", "N"],
+ "NAV_NAME": ["Descriptive name", b"NAV NAME", b"String", "N"],
+ "NAV_CODES:index": [
+ "Returns bit flags with the following meaning:; BIT7: 0= VOR 1= Localizer; BIT6: 1= glideslope available; BIT5: 1= no localizer backcourse; BIT4: 1= DME transmitter at glide slope transmitter; BIT3: 1= no nav signal available; BIT2: 1= voice available; BIT1: 1 = TACAN available; BIT0: 1= DME available",
+ b"NAV CODES:index",
+ b"Flags",
+ "N",
+ ],
+ "NAV_GLIDE_SLOPE": [
+ "The glide slope gradient.",
+ b"NAV GLIDE SLOPE",
+ b"Number",
+ "N",
+ ],
+ "NAV_RELATIVE_BEARING_TO_STATION:index": [
+ "Relative bearing to station",
+ b"NAV RELATIVE BEARING TO STATION:index",
+ b"Degrees",
+ "N",
+ ],
+ "SELECTED_DME": ["Selected DME", b"SELECTED DME", b"Number", "N"],
+ "GPS_WP_NEXT_ID": [
+ "ID of next GPS waypoint",
+ b"GPS WP NEXT ID",
+ b"String",
+ "N",
+ ],
+ "GPS_WP_PREV_ID": [
+ "ID of previous GPS waypoint",
+ b"GPS WP PREV ID",
+ b"String",
+ "N",
+ ],
+ "GPS_TARGET_DISTANCE": [
+ "Distance to target",
+ b"GPS TARGET DISTANCE",
+ b"Meters",
+ "N",
+ ],
+ "GPS_TARGET_ALTITUDE": [
+ "Altitude of GPS target",
+ b"GPS TARGET ALTITUDE",
+ b"Meters",
+ "N",
+ ],
+ # "ADF_LATLONALT:index": ["Returns the latitude, longitude and altitude of the station the radio equipment is currently tuned to, or zeros if the radio is not tuned to any ADF station. Index of 1 or 2 for ADF 1 and ADF 2.", b'ADF LATLONALT:index', b'SIMCONNECT_DATA_LATLONALT', 'N'],
+ # "NAV_VOR_LATLONALT:index": ["Returns the VOR station latitude, longitude and altitude.", b'NAV VOR LATLONALT:index', b'SIMCONNECT_DATA_LATLONALT', 'N'],
+ # "NAV_GS_LATLONALT:index": ["Returns the glide slope.", b'NAV GS LATLONALT:index', b'SIMCONNECT_DATA_LATLONALT', 'N'],
+ # "NAV_DME_LATLONALT:index": ["Returns the DME station.", b'NAV DME LATLONALT:index', b'SIMCONNECT_DATA_LATLONALT', 'N'],
+ # "INNER_MARKER_LATLONALT": ["Returns the latitude, longitude and altitude of the inner marker of an approach to a runway, if the aircraft is within the required proximity, otherwise it will return zeros.", b'INNER MARKER LATLONALT', b'SIMCONNECT_DATA_LATLONALT', 'N'],
+ # "MIDDLE_MARKER_LATLONALT": ["Returns the latitude, longitude and altitude of the middle marker.", b'MIDDLE MARKER LATLONALT', b'SIMCONNECT_DATA_LATLONALT', 'N'],
+ # "OUTER_MARKER_LATLONALT": ["Returns the latitude, longitude and altitude of the outer marker.", b'OUTER MARKER LATLONALT', b'SIMCONNECT_DATA_LATLONALT', 'N'],
+ }
- class __AircraftControlsData(RequestHelper):
- list = {
- "YOKE_Y_POSITION": ["Percent control deflection fore/aft (for animation)", b'YOKE Y POSITION', b'Position', 'Y'],
- "YOKE_X_POSITION": ["Percent control deflection left/right (for animation)", b'YOKE X POSITION', b'Position', 'Y'],
- "RUDDER_PEDAL_POSITION": ["Percent rudder pedal deflection (for animation)", b'RUDDER PEDAL POSITION', b'Position', 'Y'],
- "RUDDER_POSITION": ["Percent rudder input deflection", b'RUDDER POSITION', b'Position', 'Y'],
- "ELEVATOR_POSITION": ["Percent elevator input deflection", b'ELEVATOR POSITION', b'Position', 'Y'],
- "AILERON_POSITION": ["Percent aileron input left/right", b'AILERON POSITION', b'Position', 'Y'],
- "ELEVATOR_TRIM_POSITION": ["Elevator trim deflection", b'ELEVATOR TRIM POSITION', b'Radians', 'Y'],
- "ELEVATOR_TRIM_INDICATOR": ["Percent elevator trim (for indication)", b'ELEVATOR TRIM INDICATOR', b'Position', 'N'],
- "ELEVATOR_TRIM_PCT": ["Percent elevator trim", b'ELEVATOR TRIM PCT', b'Percent Over 100', 'N'],
- "BRAKE_LEFT_POSITION": ["Percent left brake", b'BRAKE LEFT POSITION', b'Position', 'Y'],
- "BRAKE_RIGHT_POSITION": ["Percent right brake", b'BRAKE RIGHT POSITION', b'Position', 'Y'],
- "BRAKE_INDICATOR": ["Brake on indication", b'BRAKE INDICATOR', b'Position', 'N'],
- "BRAKE_PARKING_POSITION": ["Parking brake on", b'BRAKE PARKING POSITION', b'Position', 'Y'],
- "BRAKE_PARKING_INDICATOR": ["Parking brake indicator", b'BRAKE PARKING INDICATOR', b'Bool', 'N'],
- "SPOILERS_ARMED": ["Auto-spoilers armed", b'SPOILERS ARMED', b'Bool', 'N'],
- "SPOILERS_HANDLE_POSITION": ["Spoiler handle position", b'SPOILERS HANDLE POSITION', b'Percent Over 100', 'Y'],
- "SPOILERS_LEFT_POSITION": ["Percent left spoiler deflected", b'SPOILERS LEFT POSITION', b'Percent Over 100', 'N'],
- "SPOILERS_RIGHT_POSITION": ["Percent right spoiler deflected", b'SPOILERS RIGHT POSITION', b'Percent Over 100', 'N'],
- "FLAPS_HANDLE_PERCENT": ["Percent flap handle extended", b'FLAPS HANDLE PERCENT', b'Percent Over 100', 'N'],
- "FLAPS_HANDLE_INDEX": ["Index of current flap position", b'FLAPS HANDLE INDEX', b'Number', 'Y'],
- "FLAPS_NUM_HANDLE_POSITIONS": ["Number of flap positions", b'FLAPS NUM HANDLE POSITIONS', b'Number', 'N'],
- "TRAILING_EDGE_FLAPS_LEFT_PERCENT": ["Percent left trailing edge flap extended", b'TRAILING EDGE FLAPS LEFT PERCENT', b'Percent Over 100', 'Y'],
- "TRAILING_EDGE_FLAPS_RIGHT_PERCENT": ["Percent right trailing edge flap extended", b'TRAILING EDGE FLAPS RIGHT PERCENT', b'Percent Over 100', 'Y'],
- "TRAILING_EDGE_FLAPS_LEFT_ANGLE": ["Angle left trailing edge flap extended. Use TRAILING EDGE FLAPS LEFT PERCENT to set a value.", b'TRAILING EDGE FLAPS LEFT ANGLE', b'Radians', 'N'],
- "TRAILING_EDGE_FLAPS_RIGHT_ANGLE": ["Angle right trailing edge flap extended. Use TRAILING EDGE FLAPS RIGHT PERCENT to set a value.", b'TRAILING EDGE FLAPS RIGHT ANGLE', b'Radians', 'N'],
- "LEADING_EDGE_FLAPS_LEFT_PERCENT": ["Percent left leading edge flap extended", b'LEADING EDGE FLAPS LEFT PERCENT', b'Percent Over 100', 'Y'],
- "LEADING_EDGE_FLAPS_RIGHT_PERCENT": ["Percent right leading edge flap extended", b'LEADING EDGE FLAPS RIGHT PERCENT', b'Percent Over 100', 'Y'],
- "LEADING_EDGE_FLAPS_LEFT_ANGLE": ["Angle left leading edge flap extended. Use LEADING EDGE FLAPS LEFT PERCENT to set a value.", b'LEADING EDGE FLAPS LEFT ANGLE', b'Radians', 'N'],
- "LEADING_EDGE_FLAPS_RIGHT_ANGLE": ["Angle right leading edge flap extended. Use LEADING EDGE FLAPS RIGHT PERCENT to set a value.", b'LEADING EDGE FLAPS RIGHT ANGLE', b'Radians', 'N'],
- "AILERON_LEFT_DEFLECTION": ["Angle deflection", b'AILERON LEFT DEFLECTION', b'Radians', 'N'],
- "AILERON_LEFT_DEFLECTION_PCT": ["Percent deflection", b'AILERON LEFT DEFLECTION PCT', b'Percent Over 100', 'N'],
- "AILERON_RIGHT_DEFLECTION": ["Angle deflection", b'AILERON RIGHT DEFLECTION', b'Radians', 'N'],
- "AILERON_RIGHT_DEFLECTION_PCT": ["Percent deflection", b'AILERON RIGHT DEFLECTION PCT', b'Percent Over 100', 'N'],
- "AILERON_AVERAGE_DEFLECTION": ["Angle deflection", b'AILERON AVERAGE DEFLECTION', b'Radians', 'N'],
- "AILERON_TRIM": ["Angle deflection", b'AILERON TRIM', b'Radians', 'N'],
- "AILERON_TRIM_PCT": ["Percent deflection", b'AILERON TRIM PCT', b'Percent Over 100', 'Y'],
- "RUDDER_DEFLECTION": ["Angle deflection", b'RUDDER DEFLECTION', b'Radians', 'N'],
- "RUDDER_DEFLECTION_PCT": ["Percent deflection", b'RUDDER DEFLECTION PCT', b'Percent Over 100', 'N'],
- "RUDDER_TRIM": ["Angle deflection", b'RUDDER TRIM', b'Radians', 'N'],
- "RUDDER_TRIM_PCT": ["Percent deflection", b'RUDDER TRIM PCT', b'Percent Over 100', 'Y'],
- "FLAPS_AVAILABLE": ["True if flaps available", b'FLAPS AVAILABLE', b'Bool', 'N'],
- "FLAP_DAMAGE_BY_SPEED": ["True if flagps are damaged by excessive speed", b'FLAP DAMAGE BY SPEED', b'Bool', 'N'],
- "FLAP_SPEED_EXCEEDED": ["True if safe speed limit for flaps exceeded", b'FLAP SPEED EXCEEDED', b'Bool', 'N'],
- "ELEVATOR_DEFLECTION": ["Angle deflection", b'ELEVATOR DEFLECTION', b'Radians', 'N'],
- "ELEVATOR_DEFLECTION_PCT": ["Percent deflection", b'ELEVATOR DEFLECTION PCT', b'Percent Over 100', 'N'],
- "ALTERNATE_STATIC_SOURCE_OPEN": ["Alternate static air source", b'ALTERNATE STATIC SOURCE OPEN', b'Bool', 'N'],
- "AILERON_TRIM_PCT": ["The trim position of the ailerons. Zero is fully retracted.", b'AILERON TRIM PCT', b'Percent over 100', 'Y'],
- "RUDDER_TRIM_PCT": ["The trim position of the rudder. Zero is no trim.", b'RUDDER TRIM PCT', b'Percent over 100', 'Y'],
- "FOLDING_WING_HANDLE_POSITION": ["True if the folding wing handle is engaged.", b'FOLDING WING HANDLE POSITION', b'Bool', 'N'],
- "FUEL_DUMP_SWITCH": ["If true the aircraft is dumping fuel at the rate set in the configuration file.", b'FUEL DUMP SWITCH', b'Bool', 'N'],
- #"PARKING_BRAKE_SET": ["Set the parking brake on/off",b'PARKING BRAKE SET', b'Bool', 'Y'],
- }
+ class __AircraftControlsData(RequestHelper):
+ list = {
+ "YOKE_Y_POSITION": [
+ "Percent control deflection fore/aft (for animation)",
+ b"YOKE Y POSITION",
+ b"Position",
+ "Y",
+ ],
+ "YOKE_X_POSITION": [
+ "Percent control deflection left/right (for animation)",
+ b"YOKE X POSITION",
+ b"Position",
+ "Y",
+ ],
+ "RUDDER_PEDAL_POSITION": [
+ "Percent rudder pedal deflection (for animation)",
+ b"RUDDER PEDAL POSITION",
+ b"Position",
+ "Y",
+ ],
+ "RUDDER_POSITION": [
+ "Percent rudder input deflection",
+ b"RUDDER POSITION",
+ b"Position",
+ "Y",
+ ],
+ "ELEVATOR_POSITION": [
+ "Percent elevator input deflection",
+ b"ELEVATOR POSITION",
+ b"Position",
+ "Y",
+ ],
+ "AILERON_POSITION": [
+ "Percent aileron input left/right",
+ b"AILERON POSITION",
+ b"Position",
+ "Y",
+ ],
+ "ELEVATOR_TRIM_POSITION": [
+ "Elevator trim deflection",
+ b"ELEVATOR TRIM POSITION",
+ b"Radians",
+ "Y",
+ ],
+ "ELEVATOR_TRIM_INDICATOR": [
+ "Percent elevator trim (for indication)",
+ b"ELEVATOR TRIM INDICATOR",
+ b"Position",
+ "N",
+ ],
+ "ELEVATOR_TRIM_PCT": [
+ "Percent elevator trim",
+ b"ELEVATOR TRIM PCT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "BRAKE_LEFT_POSITION": [
+ "Percent left brake",
+ b"BRAKE LEFT POSITION",
+ b"Position",
+ "Y",
+ ],
+ "BRAKE_RIGHT_POSITION": [
+ "Percent right brake",
+ b"BRAKE RIGHT POSITION",
+ b"Position",
+ "Y",
+ ],
+ "BRAKE_INDICATOR": [
+ "Brake on indication",
+ b"BRAKE INDICATOR",
+ b"Position",
+ "N",
+ ],
+ "BRAKE_PARKING_POSITION": [
+ "Parking brake on",
+ b"BRAKE PARKING POSITION",
+ b"Position",
+ "Y",
+ ],
+ "BRAKE_PARKING_INDICATOR": [
+ "Parking brake indicator",
+ b"BRAKE PARKING INDICATOR",
+ b"Bool",
+ "N",
+ ],
+ "SPOILERS_ARMED": ["Auto-spoilers armed", b"SPOILERS ARMED", b"Bool", "N"],
+ "SPOILERS_HANDLE_POSITION": [
+ "Spoiler handle position",
+ b"SPOILERS HANDLE POSITION",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "SPOILERS_LEFT_POSITION": [
+ "Percent left spoiler deflected",
+ b"SPOILERS LEFT POSITION",
+ b"Percent Over 100",
+ "N",
+ ],
+ "SPOILERS_RIGHT_POSITION": [
+ "Percent right spoiler deflected",
+ b"SPOILERS RIGHT POSITION",
+ b"Percent Over 100",
+ "N",
+ ],
+ "FLAPS_HANDLE_PERCENT": [
+ "Percent flap handle extended",
+ b"FLAPS HANDLE PERCENT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "FLAPS_HANDLE_INDEX": [
+ "Index of current flap position",
+ b"FLAPS HANDLE INDEX",
+ b"Number",
+ "Y",
+ ],
+ "FLAPS_NUM_HANDLE_POSITIONS": [
+ "Number of flap positions",
+ b"FLAPS NUM HANDLE POSITIONS",
+ b"Number",
+ "N",
+ ],
+ "TRAILING_EDGE_FLAPS_LEFT_PERCENT": [
+ "Percent left trailing edge flap extended",
+ b"TRAILING EDGE FLAPS LEFT PERCENT",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "TRAILING_EDGE_FLAPS_RIGHT_PERCENT": [
+ "Percent right trailing edge flap extended",
+ b"TRAILING EDGE FLAPS RIGHT PERCENT",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "TRAILING_EDGE_FLAPS_LEFT_ANGLE": [
+ "Angle left trailing edge flap extended. Use TRAILING EDGE FLAPS LEFT PERCENT to set a value.",
+ b"TRAILING EDGE FLAPS LEFT ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "TRAILING_EDGE_FLAPS_RIGHT_ANGLE": [
+ "Angle right trailing edge flap extended. Use TRAILING EDGE FLAPS RIGHT PERCENT to set a value.",
+ b"TRAILING EDGE FLAPS RIGHT ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "LEADING_EDGE_FLAPS_LEFT_PERCENT": [
+ "Percent left leading edge flap extended",
+ b"LEADING EDGE FLAPS LEFT PERCENT",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "LEADING_EDGE_FLAPS_RIGHT_PERCENT": [
+ "Percent right leading edge flap extended",
+ b"LEADING EDGE FLAPS RIGHT PERCENT",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "LEADING_EDGE_FLAPS_LEFT_ANGLE": [
+ "Angle left leading edge flap extended. Use LEADING EDGE FLAPS LEFT PERCENT to set a value.",
+ b"LEADING EDGE FLAPS LEFT ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "LEADING_EDGE_FLAPS_RIGHT_ANGLE": [
+ "Angle right leading edge flap extended. Use LEADING EDGE FLAPS RIGHT PERCENT to set a value.",
+ b"LEADING EDGE FLAPS RIGHT ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "AILERON_LEFT_DEFLECTION": [
+ "Angle deflection",
+ b"AILERON LEFT DEFLECTION",
+ b"Radians",
+ "N",
+ ],
+ "AILERON_LEFT_DEFLECTION_PCT": [
+ "Percent deflection",
+ b"AILERON LEFT DEFLECTION PCT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "AILERON_RIGHT_DEFLECTION": [
+ "Angle deflection",
+ b"AILERON RIGHT DEFLECTION",
+ b"Radians",
+ "N",
+ ],
+ "AILERON_RIGHT_DEFLECTION_PCT": [
+ "Percent deflection",
+ b"AILERON RIGHT DEFLECTION PCT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "AILERON_AVERAGE_DEFLECTION": [
+ "Angle deflection",
+ b"AILERON AVERAGE DEFLECTION",
+ b"Radians",
+ "N",
+ ],
+ "AILERON_TRIM": ["Angle deflection", b"AILERON TRIM", b"Radians", "N"],
+ "AILERON_TRIM_PCT": [
+ "Percent deflection",
+ b"AILERON TRIM PCT",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "RUDDER_DEFLECTION": [
+ "Angle deflection",
+ b"RUDDER DEFLECTION",
+ b"Radians",
+ "N",
+ ],
+ "RUDDER_DEFLECTION_PCT": [
+ "Percent deflection",
+ b"RUDDER DEFLECTION PCT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "RUDDER_TRIM": ["Angle deflection", b"RUDDER TRIM", b"Radians", "N"],
+ "RUDDER_TRIM_PCT": [
+ "Percent deflection",
+ b"RUDDER TRIM PCT",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "FLAPS_AVAILABLE": [
+ "True if flaps available",
+ b"FLAPS AVAILABLE",
+ b"Bool",
+ "N",
+ ],
+ "FLAP_DAMAGE_BY_SPEED": [
+ "True if flagps are damaged by excessive speed",
+ b"FLAP DAMAGE BY SPEED",
+ b"Bool",
+ "N",
+ ],
+ "FLAP_SPEED_EXCEEDED": [
+ "True if safe speed limit for flaps exceeded",
+ b"FLAP SPEED EXCEEDED",
+ b"Bool",
+ "N",
+ ],
+ "ELEVATOR_DEFLECTION": [
+ "Angle deflection",
+ b"ELEVATOR DEFLECTION",
+ b"Radians",
+ "N",
+ ],
+ "ELEVATOR_DEFLECTION_PCT": [
+ "Percent deflection",
+ b"ELEVATOR DEFLECTION PCT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "ALTERNATE_STATIC_SOURCE_OPEN": [
+ "Alternate static air source",
+ b"ALTERNATE STATIC SOURCE OPEN",
+ b"Bool",
+ "N",
+ ],
+ "AILERON_TRIM_PCT": [
+ "The trim position of the ailerons. Zero is fully retracted.",
+ b"AILERON TRIM PCT",
+ b"Percent over 100",
+ "Y",
+ ],
+ "RUDDER_TRIM_PCT": [
+ "The trim position of the rudder. Zero is no trim.",
+ b"RUDDER TRIM PCT",
+ b"Percent over 100",
+ "Y",
+ ],
+ "FOLDING_WING_HANDLE_POSITION": [
+ "True if the folding wing handle is engaged.",
+ b"FOLDING WING HANDLE POSITION",
+ b"Bool",
+ "N",
+ ],
+ "FUEL_DUMP_SWITCH": [
+ "If true the aircraft is dumping fuel at the rate set in the configuration file.",
+ b"FUEL DUMP SWITCH",
+ b"Bool",
+ "N",
+ ],
+ # "PARKING_BRAKE_SET": ["Set the parking brake on/off",b'PARKING BRAKE SET', b'Bool', 'Y'],
+ }
- class __AircraftAutopilotData(RequestHelper):
- list = {
- "AUTOPILOT_AVAILABLE": ["Available flag", b'AUTOPILOT AVAILABLE', b'Bool', 'N'],
- "AUTOPILOT_MASTER": ["On/off flag", b'AUTOPILOT MASTER', b'Bool', 'N'],
- "AUTOPILOT_NAV_SELECTED": ["Index of Nav radio selected", b'AUTOPILOT NAV SELECTED', b'Number', 'N'],
- "AUTOPILOT_WING_LEVELER": ["Wing leveler active", b'AUTOPILOT WING LEVELER', b'Bool', 'N'],
- "AUTOPILOT_NAV1_LOCK": ["Lateral nav mode active", b'AUTOPILOT NAV1 LOCK', b'Bool', 'N'],
- "AUTOPILOT_HEADING_LOCK": ["Heading mode active", b'AUTOPILOT HEADING LOCK', b'Bool', 'N'],
- "AUTOPILOT_HEADING_LOCK_DIR": ["Selected heading", b'AUTOPILOT HEADING LOCK DIR', b'Degrees', 'N'],
- "AUTOPILOT_ALTITUDE_LOCK": ["Altitude hole active", b'AUTOPILOT ALTITUDE LOCK', b'Bool', 'N'],
- "AUTOPILOT_ALTITUDE_LOCK_VAR": ["Selected altitude", b'AUTOPILOT ALTITUDE LOCK VAR', b'Feet', 'N'],
- "AUTOPILOT_ATTITUDE_HOLD": ["Attitude hold active", b'AUTOPILOT ATTITUDE HOLD', b'Bool', 'N'],
- "AUTOPILOT_GLIDESLOPE_HOLD": ["GS hold active", b'AUTOPILOT GLIDESLOPE HOLD', b'Bool', 'N'],
- "AUTOPILOT_PITCH_HOLD_REF": ["Current reference pitch", b'AUTOPILOT PITCH HOLD REF', b'Radians', 'N'],
- "AUTOPILOT_APPROACH_HOLD": ["Approach mode active", b'AUTOPILOT APPROACH HOLD', b'Bool', 'N'],
- "AUTOPILOT_BACKCOURSE_HOLD": ["Back course mode active", b'AUTOPILOT BACKCOURSE HOLD', b'Bool', 'N'],
- "AUTOPILOT_VERTICAL_HOLD_VAR": ["Selected vertical speed", b'AUTOPILOT VERTICAL HOLD VAR', b'Feet/minute', 'N'],
- "AUTOPILOT_PITCH_HOLD": ["Set to True if the autopilot pitch hold has is engaged.", b'AUTOPILOT PITCH HOLD', b'Bool', 'N'],
- "AUTOPILOT_FLIGHT_DIRECTOR_ACTIVE": ["Flight director active", b'AUTOPILOT FLIGHT DIRECTOR ACTIVE', b'Bool', 'N'],
- "AUTOPILOT_FLIGHT_DIRECTOR_PITCH": ["Reference pitch angle", b'AUTOPILOT FLIGHT DIRECTOR PITCH', b'Radians', 'N'],
- "AUTOPILOT_FLIGHT_DIRECTOR_BANK": ["Reference bank angle", b'AUTOPILOT FLIGHT DIRECTOR BANK', b'Radians', 'N'],
- "AUTOPILOT_AIRSPEED_HOLD": ["Airspeed hold active", b'AUTOPILOT AIRSPEED HOLD', b'Bool', 'N'],
- "AUTOPILOT_AIRSPEED_HOLD_VAR": ["Selected airspeed", b'AUTOPILOT AIRSPEED HOLD VAR', b'Knots', 'N'],
- "AUTOPILOT_MACH_HOLD": ["Mach hold active", b'AUTOPILOT MACH HOLD', b'Bool', 'N'],
- "AUTOPILOT_MACH_HOLD_VAR": ["Selected mach", b'AUTOPILOT MACH HOLD VAR', b'Number', 'N'],
- "AUTOPILOT_YAW_DAMPER": ["Yaw damper active", b'AUTOPILOT YAW DAMPER', b'Bool', 'N'],
- "AUTOPILOT_RPM_HOLD_VAR": ["Selected rpm", b'AUTOPILOT RPM HOLD VAR', b'Number', 'N'],
- "AUTOPILOT_THROTTLE_ARM": ["Autothrottle armed", b'AUTOPILOT THROTTLE ARM', b'Bool', 'N'],
- "AUTOPILOT_TAKEOFF_POWER_ACTIVE": ["Takeoff / Go Around power mode active", b'AUTOPILOT TAKEOFF POWER ACTIVE', b'Bool', 'N'],
- "AUTOTHROTTLE_ACTIVE": ["Auto-throttle active", b'AUTOTHROTTLE ACTIVE', b'Bool', 'N'],
- "AUTOPILOT_NAV1_LOCK": ["True if autopilot nav1 lock applied", b'AUTOPILOT NAV1 LOCK', b'Bool', 'N'],
- "AUTOPILOT_VERTICAL_HOLD": ["True if autopilot vertical hold applied", b'AUTOPILOT VERTICAL HOLD', b'Bool', 'N'],
- "AUTOPILOT_RPM_HOLD": ["True if autopilot rpm hold applied", b'AUTOPILOT RPM HOLD', b'Bool', 'N'],
- "AUTOPILOT_MAX_BANK": ["True if autopilot max bank applied", b'AUTOPILOT MAX BANK', b'Radians', 'N'],
- "FLY_BY_WIRE_ELAC_SWITCH": ["True if the fly by wire Elevators and Ailerons computer is on.", b'FLY BY WIRE ELAC SWITCH', b'Bool', 'N'],
- "FLY_BY_WIRE_FAC_SWITCH": ["True if the fly by wire Flight Augmentation computer is on.", b'FLY BY WIRE FAC SWITCH', b'Bool', 'N'],
- "FLY_BY_WIRE_SEC_SWITCH": ["True if the fly by wire Spoilers and Elevators computer is on.", b'FLY BY WIRE SEC SWITCH', b'Bool', 'N'],
- "FLY_BY_WIRE_ELAC_FAILED": ["True if the Elevators and Ailerons computer has failed.", b'FLY BY WIRE ELAC FAILED', b'Bool', 'N'],
- "FLY_BY_WIRE_FAC_FAILED": ["True if the Flight Augmentation computer has failed.", b'FLY BY WIRE FAC FAILED', b'Bool', 'N'],
- "FLY_BY_WIRE_SEC_FAILED": ["True if the Spoilers and Elevators computer has failed.", b'FLY BY WIRE SEC FAILED', b'Bool', 'N'],
- "AUTOPILOT_FLIGHT_LEVEL_CHANGE": ["True if autopilot FLC mode applied", b'AUTOPILOT FLIGHT LEVEL CHANGE', b'Bool', 'N'],
- }
+ class __AircraftAutopilotData(RequestHelper):
+ list = {
+ "AUTOPILOT_AVAILABLE": [
+ "Available flag",
+ b"AUTOPILOT AVAILABLE",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_MASTER": ["On/off flag", b"AUTOPILOT MASTER", b"Bool", "N"],
+ "AUTOPILOT_NAV_SELECTED": [
+ "Index of Nav radio selected",
+ b"AUTOPILOT NAV SELECTED",
+ b"Number",
+ "N",
+ ],
+ "AUTOPILOT_WING_LEVELER": [
+ "Wing leveler active",
+ b"AUTOPILOT WING LEVELER",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_NAV1_LOCK": [
+ "Lateral nav mode active",
+ b"AUTOPILOT NAV1 LOCK",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_HEADING_LOCK": [
+ "Heading mode active",
+ b"AUTOPILOT HEADING LOCK",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_HEADING_LOCK_DIR": [
+ "Selected heading",
+ b"AUTOPILOT HEADING LOCK DIR",
+ b"Degrees",
+ "N",
+ ],
+ "AUTOPILOT_ALTITUDE_LOCK": [
+ "Altitude hole active",
+ b"AUTOPILOT ALTITUDE LOCK",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_ALTITUDE_LOCK_VAR": [
+ "Selected altitude",
+ b"AUTOPILOT ALTITUDE LOCK VAR",
+ b"Feet",
+ "N",
+ ],
+ "AUTOPILOT_ATTITUDE_HOLD": [
+ "Attitude hold active",
+ b"AUTOPILOT ATTITUDE HOLD",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_GLIDESLOPE_HOLD": [
+ "GS hold active",
+ b"AUTOPILOT GLIDESLOPE HOLD",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_PITCH_HOLD_REF": [
+ "Current reference pitch",
+ b"AUTOPILOT PITCH HOLD REF",
+ b"Radians",
+ "N",
+ ],
+ "AUTOPILOT_APPROACH_HOLD": [
+ "Approach mode active",
+ b"AUTOPILOT APPROACH HOLD",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_BACKCOURSE_HOLD": [
+ "Back course mode active",
+ b"AUTOPILOT BACKCOURSE HOLD",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_VERTICAL_HOLD_VAR": [
+ "Selected vertical speed",
+ b"AUTOPILOT VERTICAL HOLD VAR",
+ b"Feet/minute",
+ "N",
+ ],
+ "AUTOPILOT_PITCH_HOLD": [
+ "Set to True if the autopilot pitch hold has is engaged.",
+ b"AUTOPILOT PITCH HOLD",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_FLIGHT_DIRECTOR_ACTIVE": [
+ "Flight director active",
+ b"AUTOPILOT FLIGHT DIRECTOR ACTIVE",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_FLIGHT_DIRECTOR_PITCH": [
+ "Reference pitch angle",
+ b"AUTOPILOT FLIGHT DIRECTOR PITCH",
+ b"Radians",
+ "N",
+ ],
+ "AUTOPILOT_FLIGHT_DIRECTOR_BANK": [
+ "Reference bank angle",
+ b"AUTOPILOT FLIGHT DIRECTOR BANK",
+ b"Radians",
+ "N",
+ ],
+ "AUTOPILOT_AIRSPEED_HOLD": [
+ "Airspeed hold active",
+ b"AUTOPILOT AIRSPEED HOLD",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_AIRSPEED_HOLD_VAR": [
+ "Selected airspeed",
+ b"AUTOPILOT AIRSPEED HOLD VAR",
+ b"Knots",
+ "N",
+ ],
+ "AUTOPILOT_MACH_HOLD": [
+ "Mach hold active",
+ b"AUTOPILOT MACH HOLD",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_MACH_HOLD_VAR": [
+ "Selected mach",
+ b"AUTOPILOT MACH HOLD VAR",
+ b"Number",
+ "N",
+ ],
+ "AUTOPILOT_YAW_DAMPER": [
+ "Yaw damper active",
+ b"AUTOPILOT YAW DAMPER",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_RPM_HOLD_VAR": [
+ "Selected rpm",
+ b"AUTOPILOT RPM HOLD VAR",
+ b"Number",
+ "N",
+ ],
+ "AUTOPILOT_THROTTLE_ARM": [
+ "Autothrottle armed",
+ b"AUTOPILOT THROTTLE ARM",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_TAKEOFF_POWER_ACTIVE": [
+ "Takeoff / Go Around power mode active",
+ b"AUTOPILOT TAKEOFF POWER ACTIVE",
+ b"Bool",
+ "N",
+ ],
+ "AUTOTHROTTLE_ACTIVE": [
+ "Auto-throttle active",
+ b"AUTOTHROTTLE ACTIVE",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_NAV1_LOCK": [
+ "True if autopilot nav1 lock applied",
+ b"AUTOPILOT NAV1 LOCK",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_VERTICAL_HOLD": [
+ "True if autopilot vertical hold applied",
+ b"AUTOPILOT VERTICAL HOLD",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_RPM_HOLD": [
+ "True if autopilot rpm hold applied",
+ b"AUTOPILOT RPM HOLD",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_MAX_BANK": [
+ "True if autopilot max bank applied",
+ b"AUTOPILOT MAX BANK",
+ b"Radians",
+ "N",
+ ],
+ "FLY_BY_WIRE_ELAC_SWITCH": [
+ "True if the fly by wire Elevators and Ailerons computer is on.",
+ b"FLY BY WIRE ELAC SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "FLY_BY_WIRE_FAC_SWITCH": [
+ "True if the fly by wire Flight Augmentation computer is on.",
+ b"FLY BY WIRE FAC SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "FLY_BY_WIRE_SEC_SWITCH": [
+ "True if the fly by wire Spoilers and Elevators computer is on.",
+ b"FLY BY WIRE SEC SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "FLY_BY_WIRE_ELAC_FAILED": [
+ "True if the Elevators and Ailerons computer has failed.",
+ b"FLY BY WIRE ELAC FAILED",
+ b"Bool",
+ "N",
+ ],
+ "FLY_BY_WIRE_FAC_FAILED": [
+ "True if the Flight Augmentation computer has failed.",
+ b"FLY BY WIRE FAC FAILED",
+ b"Bool",
+ "N",
+ ],
+ "FLY_BY_WIRE_SEC_FAILED": [
+ "True if the Spoilers and Elevators computer has failed.",
+ b"FLY BY WIRE SEC FAILED",
+ b"Bool",
+ "N",
+ ],
+ "AUTOPILOT_FLIGHT_LEVEL_CHANGE": [
+ "True if autopilot FLC mode applied",
+ b"AUTOPILOT FLIGHT LEVEL CHANGE",
+ b"Bool",
+ "N",
+ ],
+ }
- class __AircraftLandingGearData(RequestHelper):
- list = {
- "IS_GEAR_RETRACTABLE": ["True if gear can be retracted", b'IS GEAR RETRACTABLE', b'Bool', 'N'],
- "IS_GEAR_SKIS": ["True if landing gear is skis", b'IS GEAR SKIS', b'Bool', 'N'],
- "IS_GEAR_FLOATS": ["True if landing gear is floats", b'IS GEAR FLOATS', b'Bool', 'N'],
- "IS_GEAR_SKIDS": ["True if landing gear is skids", b'IS GEAR SKIDS', b'Bool', 'N'],
- "IS_GEAR_WHEELS": ["True if landing gear is wheels", b'IS GEAR WHEELS', b'Bool', 'N'],
- "GEAR_HANDLE_POSITION": ["True if gear handle is applied", b'GEAR HANDLE POSITION', b'Bool', 'Y'],
- "GEAR_HYDRAULIC_PRESSURE": ["Gear hydraulic pressure", b'GEAR HYDRAULIC PRESSURE', b'psf', 'N'],
- "TAILWHEEL_LOCK_ON": ["True if tailwheel lock applied", b'TAILWHEEL LOCK ON', b'Bool', 'N'],
- "GEAR_CENTER_POSITION": ["Percent center gear extended", b'GEAR CENTER POSITION', b'Percent Over 100', 'Y'],
- "GEAR_LEFT_POSITION": ["Percent left gear extended", b'GEAR LEFT POSITION', b'Percent Over 100', 'Y'],
- "GEAR_RIGHT_POSITION": ["Percent right gear extended", b'GEAR RIGHT POSITION', b'Percent Over 100', 'Y'],
- "GEAR_TAIL_POSITION": ["Percent tail gear extended", b'GEAR TAIL POSITION', b'Percent Over 100', 'N'],
- "GEAR_AUX_POSITION": ["Percent auxiliary gear extended", b'GEAR AUX POSITION', b'Percent Over 100', 'N'],
- "GEAR_POSITION:index": ["Position of landing gear:; 0 = unknown; 1 = up; 2 = down", b'GEAR POSITION:index', b'Enum', 'Y'],
- "GEAR_ANIMATION_POSITION:index": ["Percent gear animation extended", b'GEAR ANIMATION POSITION:index', b'Number', 'N'],
- "GEAR_TOTAL_PCT_EXTENDED": ["Percent total gear extended", b'GEAR TOTAL PCT EXTENDED', b'Percentage', 'N'],
- "AUTO_BRAKE_SWITCH_CB": ["Auto brake switch position", b'AUTO BRAKE SWITCH CB', b'Number', 'N'],
- "WATER_RUDDER_HANDLE_POSITION": ["Position of the water rudder handle (0 handle retracted, 100 rudder handle applied)", b'WATER RUDDER HANDLE POSITION', b'Percent Over 100', 'Y'],
- "WATER_LEFT_RUDDER_EXTENDED": ["Percent extended", b'WATER LEFT RUDDER EXTENDED', b'Percentage', 'N'],
- "WATER_RIGHT_RUDDER_EXTENDED": ["Percent extended", b'WATER RIGHT RUDDER EXTENDED', b'Percentage', 'N'],
- "GEAR_CENTER_STEER_ANGLE": ["Center wheel angle, negative to the left, positive to the right.", b'GEAR CENTER STEER ANGLE', b'Percent Over 100', 'N'],
- "GEAR_LEFT_STEER_ANGLE": ["Left wheel angle, negative to the left, positive to the right.", b'GEAR LEFT STEER ANGLE', b'Percent Over 100', 'N'],
- "GEAR_RIGHT_STEER_ANGLE": ["Right wheel angle, negative to the left, positive to the right.", b'GEAR RIGHT STEER ANGLE', b'Percent Over 100', 'N'],
- "GEAR_AUX_STEER_ANGLE": ["Aux wheel angle, negative to the left, positive to the right. The aux wheel is the fourth set of gear, sometimes used on helicopters.", b'GEAR AUX STEER ANGLE', b'Percent Over 100', 'N'],
- "GEAR_STEER_ANGLE:index": ["Alternative method of getting the steer angle. Index is; 0 = center; 1 = left; 2 = right; 3 = aux", b'GEAR STEER ANGLE:index', b'Percent Over 100', 'N'],
- "WATER_LEFT_RUDDER_STEER_ANGLE": ["Water left rudder angle, negative to the left, positive to the right.", b'WATER LEFT RUDDER STEER ANGLE', b'Percent Over 100', 'N'],
- "WATER_RIGHT_RUDDER_STEER_ANGLE": ["Water right rudder angle, negative to the left, positive to the right.", b'WATER RIGHT RUDDER STEER ANGLE', b'Percent Over 100', 'N'],
- "GEAR_CENTER_STEER_ANGLE_PCT": ["Center steer angle as a percentage", b'GEAR CENTER STEER ANGLE PCT', b'Percent Over 100', 'N'],
- "GEAR_LEFT_STEER_ANGLE_PCT": ["Left steer angle as a percentage", b'GEAR LEFT STEER ANGLE PCT', b'Percent Over 100', 'N'],
- "GEAR_RIGHT_STEER_ANGLE_PCT": ["Right steer angle as a percentage", b'GEAR RIGHT STEER ANGLE PCT', b'Percent Over 100', 'N'],
- "GEAR_AUX_STEER_ANGLE_PCT": ["Aux steer angle as a percentage", b'GEAR AUX STEER ANGLE PCT', b'Percent Over 100', 'N'],
- "GEAR_STEER_ANGLE_PCT:index": ["Alternative method of getting steer angle as a percentage. Index is; 0 = center; 1 = left; 2 = right; 3 = aux", b'GEAR STEER ANGLE PCT:index', b'Percent Over 100', 'N'],
- "WATER_LEFT_RUDDER_STEER_ANGLE_PCT": ["Water left rudder angle as a percentage", b'WATER LEFT RUDDER STEER ANGLE PCT', b'Percent Over 100', 'N'],
- "WATER_RIGHT_RUDDER_STEER_ANGLE_PCT": ["Water right rudder as a percentage", b'WATER RIGHT RUDDER STEER ANGLE PCT', b'Percent Over 100', 'N'],
- "WHEEL_RPM:index": ["Wheel rpm. Index is; 0 = center; 1 = left; 2 = right; 3 = aux", b'WHEEL RPM:index', b'Rpm', 'N'],
- "CENTER_WHEEL_RPM": ["Center landing gear rpm", b'CENTER WHEEL RPM', b'Rpm', 'N'],
- "LEFT_WHEEL_RPM": ["Left landing gear rpm", b'LEFT WHEEL RPM', b'Rpm', 'N'],
- "RIGHT_WHEEL_RPM": ["Right landing gear rpm", b'RIGHT WHEEL RPM', b'Rpm', 'N'],
- "AUX_WHEEL_RPM": ["Rpm of fourth set of gear wheels.", b'AUX WHEEL RPM', b'Rpm', 'N'],
- "WHEEL_ROTATION_ANGLE:index": ["Wheel rotation angle. Index is; 0 = center; 1 = left; 2 = right; 3 = aux", b'WHEEL ROTATION ANGLE:index', b'Radians', 'N'],
- "CENTER_WHEEL_ROTATION_ANGLE": ["Center wheel rotation angle", b'CENTER WHEEL ROTATION ANGLE', b'Radians', 'N'],
- "LEFT_WHEEL_ROTATION_ANGLE": ["Left wheel rotation angle", b'LEFT WHEEL ROTATION ANGLE', b'Radians', 'N'],
- "RIGHT_WHEEL_ROTATION_ANGLE": ["Right wheel rotation angle", b'RIGHT WHEEL ROTATION ANGLE', b'Radians', 'N'],
- "AUX_WHEEL_ROTATION_ANGLE": ["Aux wheel rotation angle", b'AUX WHEEL ROTATION ANGLE', b'Radians', 'N'],
- "GEAR_EMERGENCY_HANDLE_POSITION": ["True if gear emergency handle applied", b'GEAR EMERGENCY HANDLE POSITION', b'Bool', 'N'],
- "GEAR_WARNING": ["One of:; 0: unknown; 1: normal; 2: amphib", b'GEAR WARNING', b'Enum', 'N'],
- "ANTISKID_BRAKES_ACTIVE": ["True if antiskid brakes active", b'ANTISKID BRAKES ACTIVE', b'Bool', 'N'],
- "RETRACT_FLOAT_SWITCH": ["True if retract float switch on", b'RETRACT FLOAT SWITCH', b'Bool', 'N'],
- "RETRACT_LEFT_FLOAT_EXTENDED": ["If aircraft has retractable floats.", b'RETRACT LEFT FLOAT EXTENDED', b'Percent', 'N'],
- "RETRACT_RIGHT_FLOAT_EXTENDED": ["If aircraft has retractable floats.", b'RETRACT RIGHT FLOAT EXTENDED', b'Percent', 'N'],
- "STEER_INPUT_CONTROL": ["Position of steering tiller", b'STEER INPUT CONTROL', b'Percent over 100', 'N'],
- "GEAR_DAMAGE_BY_SPEED": ["True if gear has been damaged by excessive speed", b'GEAR DAMAGE BY SPEED', b'Bool', 'N'],
- "GEAR_SPEED_EXCEEDED": ["True if safe speed limit for gear exceeded", b'GEAR SPEED EXCEEDED', b'Bool', 'N'],
- "NOSEWHEEL_LOCK_ON": ["True if the nosewheel lock is engaged.", b'NOSEWHEEL LOCK ON', b'Bool', 'N'],
- }
+ class __AircraftLandingGearData(RequestHelper):
+ list = {
+ "IS_GEAR_RETRACTABLE": [
+ "True if gear can be retracted",
+ b"IS GEAR RETRACTABLE",
+ b"Bool",
+ "N",
+ ],
+ "IS_GEAR_SKIS": [
+ "True if landing gear is skis",
+ b"IS GEAR SKIS",
+ b"Bool",
+ "N",
+ ],
+ "IS_GEAR_FLOATS": [
+ "True if landing gear is floats",
+ b"IS GEAR FLOATS",
+ b"Bool",
+ "N",
+ ],
+ "IS_GEAR_SKIDS": [
+ "True if landing gear is skids",
+ b"IS GEAR SKIDS",
+ b"Bool",
+ "N",
+ ],
+ "IS_GEAR_WHEELS": [
+ "True if landing gear is wheels",
+ b"IS GEAR WHEELS",
+ b"Bool",
+ "N",
+ ],
+ "GEAR_HANDLE_POSITION": [
+ "True if gear handle is applied",
+ b"GEAR HANDLE POSITION",
+ b"Bool",
+ "Y",
+ ],
+ "GEAR_HYDRAULIC_PRESSURE": [
+ "Gear hydraulic pressure",
+ b"GEAR HYDRAULIC PRESSURE",
+ b"psf",
+ "N",
+ ],
+ "TAILWHEEL_LOCK_ON": [
+ "True if tailwheel lock applied",
+ b"TAILWHEEL LOCK ON",
+ b"Bool",
+ "N",
+ ],
+ "GEAR_CENTER_POSITION": [
+ "Percent center gear extended",
+ b"GEAR CENTER POSITION",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "GEAR_LEFT_POSITION": [
+ "Percent left gear extended",
+ b"GEAR LEFT POSITION",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "GEAR_RIGHT_POSITION": [
+ "Percent right gear extended",
+ b"GEAR RIGHT POSITION",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "GEAR_TAIL_POSITION": [
+ "Percent tail gear extended",
+ b"GEAR TAIL POSITION",
+ b"Percent Over 100",
+ "N",
+ ],
+ "GEAR_AUX_POSITION": [
+ "Percent auxiliary gear extended",
+ b"GEAR AUX POSITION",
+ b"Percent Over 100",
+ "N",
+ ],
+ "GEAR_POSITION:index": [
+ "Position of landing gear:; 0 = unknown; 1 = up; 2 = down",
+ b"GEAR POSITION:index",
+ b"Enum",
+ "Y",
+ ],
+ "GEAR_ANIMATION_POSITION:index": [
+ "Percent gear animation extended",
+ b"GEAR ANIMATION POSITION:index",
+ b"Number",
+ "N",
+ ],
+ "GEAR_TOTAL_PCT_EXTENDED": [
+ "Percent total gear extended",
+ b"GEAR TOTAL PCT EXTENDED",
+ b"Percentage",
+ "N",
+ ],
+ "AUTO_BRAKE_SWITCH_CB": [
+ "Auto brake switch position",
+ b"AUTO BRAKE SWITCH CB",
+ b"Number",
+ "N",
+ ],
+ "WATER_RUDDER_HANDLE_POSITION": [
+ "Position of the water rudder handle (0 handle retracted, 100 rudder handle applied)",
+ b"WATER RUDDER HANDLE POSITION",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "WATER_LEFT_RUDDER_EXTENDED": [
+ "Percent extended",
+ b"WATER LEFT RUDDER EXTENDED",
+ b"Percentage",
+ "N",
+ ],
+ "WATER_RIGHT_RUDDER_EXTENDED": [
+ "Percent extended",
+ b"WATER RIGHT RUDDER EXTENDED",
+ b"Percentage",
+ "N",
+ ],
+ "GEAR_CENTER_STEER_ANGLE": [
+ "Center wheel angle, negative to the left, positive to the right.",
+ b"GEAR CENTER STEER ANGLE",
+ b"Percent Over 100",
+ "N",
+ ],
+ "GEAR_LEFT_STEER_ANGLE": [
+ "Left wheel angle, negative to the left, positive to the right.",
+ b"GEAR LEFT STEER ANGLE",
+ b"Percent Over 100",
+ "N",
+ ],
+ "GEAR_RIGHT_STEER_ANGLE": [
+ "Right wheel angle, negative to the left, positive to the right.",
+ b"GEAR RIGHT STEER ANGLE",
+ b"Percent Over 100",
+ "N",
+ ],
+ "GEAR_AUX_STEER_ANGLE": [
+ "Aux wheel angle, negative to the left, positive to the right. The aux wheel is the fourth set of gear, sometimes used on helicopters.",
+ b"GEAR AUX STEER ANGLE",
+ b"Percent Over 100",
+ "N",
+ ],
+ "GEAR_STEER_ANGLE:index": [
+ "Alternative method of getting the steer angle. Index is; 0 = center; 1 = left; 2 = right; 3 = aux",
+ b"GEAR STEER ANGLE:index",
+ b"Percent Over 100",
+ "N",
+ ],
+ "WATER_LEFT_RUDDER_STEER_ANGLE": [
+ "Water left rudder angle, negative to the left, positive to the right.",
+ b"WATER LEFT RUDDER STEER ANGLE",
+ b"Percent Over 100",
+ "N",
+ ],
+ "WATER_RIGHT_RUDDER_STEER_ANGLE": [
+ "Water right rudder angle, negative to the left, positive to the right.",
+ b"WATER RIGHT RUDDER STEER ANGLE",
+ b"Percent Over 100",
+ "N",
+ ],
+ "GEAR_CENTER_STEER_ANGLE_PCT": [
+ "Center steer angle as a percentage",
+ b"GEAR CENTER STEER ANGLE PCT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "GEAR_LEFT_STEER_ANGLE_PCT": [
+ "Left steer angle as a percentage",
+ b"GEAR LEFT STEER ANGLE PCT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "GEAR_RIGHT_STEER_ANGLE_PCT": [
+ "Right steer angle as a percentage",
+ b"GEAR RIGHT STEER ANGLE PCT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "GEAR_AUX_STEER_ANGLE_PCT": [
+ "Aux steer angle as a percentage",
+ b"GEAR AUX STEER ANGLE PCT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "GEAR_STEER_ANGLE_PCT:index": [
+ "Alternative method of getting steer angle as a percentage. Index is; 0 = center; 1 = left; 2 = right; 3 = aux",
+ b"GEAR STEER ANGLE PCT:index",
+ b"Percent Over 100",
+ "N",
+ ],
+ "WATER_LEFT_RUDDER_STEER_ANGLE_PCT": [
+ "Water left rudder angle as a percentage",
+ b"WATER LEFT RUDDER STEER ANGLE PCT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "WATER_RIGHT_RUDDER_STEER_ANGLE_PCT": [
+ "Water right rudder as a percentage",
+ b"WATER RIGHT RUDDER STEER ANGLE PCT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "WHEEL_RPM:index": [
+ "Wheel rpm. Index is; 0 = center; 1 = left; 2 = right; 3 = aux",
+ b"WHEEL RPM:index",
+ b"Rpm",
+ "N",
+ ],
+ "CENTER_WHEEL_RPM": [
+ "Center landing gear rpm",
+ b"CENTER WHEEL RPM",
+ b"Rpm",
+ "N",
+ ],
+ "LEFT_WHEEL_RPM": ["Left landing gear rpm", b"LEFT WHEEL RPM", b"Rpm", "N"],
+ "RIGHT_WHEEL_RPM": [
+ "Right landing gear rpm",
+ b"RIGHT WHEEL RPM",
+ b"Rpm",
+ "N",
+ ],
+ "AUX_WHEEL_RPM": [
+ "Rpm of fourth set of gear wheels.",
+ b"AUX WHEEL RPM",
+ b"Rpm",
+ "N",
+ ],
+ "WHEEL_ROTATION_ANGLE:index": [
+ "Wheel rotation angle. Index is; 0 = center; 1 = left; 2 = right; 3 = aux",
+ b"WHEEL ROTATION ANGLE:index",
+ b"Radians",
+ "N",
+ ],
+ "CENTER_WHEEL_ROTATION_ANGLE": [
+ "Center wheel rotation angle",
+ b"CENTER WHEEL ROTATION ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "LEFT_WHEEL_ROTATION_ANGLE": [
+ "Left wheel rotation angle",
+ b"LEFT WHEEL ROTATION ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "RIGHT_WHEEL_ROTATION_ANGLE": [
+ "Right wheel rotation angle",
+ b"RIGHT WHEEL ROTATION ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "AUX_WHEEL_ROTATION_ANGLE": [
+ "Aux wheel rotation angle",
+ b"AUX WHEEL ROTATION ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "GEAR_EMERGENCY_HANDLE_POSITION": [
+ "True if gear emergency handle applied",
+ b"GEAR EMERGENCY HANDLE POSITION",
+ b"Bool",
+ "N",
+ ],
+ "GEAR_WARNING": [
+ "One of:; 0: unknown; 1: normal; 2: amphib",
+ b"GEAR WARNING",
+ b"Enum",
+ "N",
+ ],
+ "ANTISKID_BRAKES_ACTIVE": [
+ "True if antiskid brakes active",
+ b"ANTISKID BRAKES ACTIVE",
+ b"Bool",
+ "N",
+ ],
+ "RETRACT_FLOAT_SWITCH": [
+ "True if retract float switch on",
+ b"RETRACT FLOAT SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "RETRACT_LEFT_FLOAT_EXTENDED": [
+ "If aircraft has retractable floats.",
+ b"RETRACT LEFT FLOAT EXTENDED",
+ b"Percent",
+ "N",
+ ],
+ "RETRACT_RIGHT_FLOAT_EXTENDED": [
+ "If aircraft has retractable floats.",
+ b"RETRACT RIGHT FLOAT EXTENDED",
+ b"Percent",
+ "N",
+ ],
+ "STEER_INPUT_CONTROL": [
+ "Position of steering tiller",
+ b"STEER INPUT CONTROL",
+ b"Percent over 100",
+ "N",
+ ],
+ "GEAR_DAMAGE_BY_SPEED": [
+ "True if gear has been damaged by excessive speed",
+ b"GEAR DAMAGE BY SPEED",
+ b"Bool",
+ "N",
+ ],
+ "GEAR_SPEED_EXCEEDED": [
+ "True if safe speed limit for gear exceeded",
+ b"GEAR SPEED EXCEEDED",
+ b"Bool",
+ "N",
+ ],
+ "NOSEWHEEL_LOCK_ON": [
+ "True if the nosewheel lock is engaged.",
+ b"NOSEWHEEL LOCK ON",
+ b"Bool",
+ "N",
+ ],
+ }
- class __AircraftEnvironmentData(RequestHelper):
- list = {
- "AMBIENT_DENSITY": ["Ambient density", b'AMBIENT DENSITY', b'Slugs per cubic feet', 'N'],
- "AMBIENT_TEMPERATURE": ["Ambient temperature", b'AMBIENT TEMPERATURE', b'Celsius', 'N'],
- "AMBIENT_PRESSURE": ["Ambient pressure", b'AMBIENT PRESSURE', b'inHg', 'N'],
- "AMBIENT_WIND_VELOCITY": ["Wind velocity", b'AMBIENT WIND VELOCITY', b'Knots', 'N'],
- "AMBIENT_WIND_DIRECTION": ["Wind direction", b'AMBIENT WIND DIRECTION', b'Degrees', 'N'],
- "AMBIENT_WIND_X": ["Wind component in East/West direction.", b'AMBIENT WIND X', b'Meters per second', 'N'],
- "AMBIENT_WIND_Y": ["Wind component in vertical direction.", b'AMBIENT WIND Y', b'Meters per second', 'N'],
- "AMBIENT_WIND_Z": ["Wind component in North/South direction.", b'AMBIENT WIND Z', b'Meters per second', 'N'],
- "STRUCT_AMBIENT_WIND": ["X (latitude), Y (vertical) and Z (longitude) components of the wind.", b'STRUCT AMBIENT WIND', b'Feet per second', 'N'],
- "AIRCRAFT_WIND_X": ["Wind component in aircraft lateral axis", b'AIRCRAFT WIND X', b'Knots', 'N'],
- "AIRCRAFT_WIND_Y": ["Wind component in aircraft vertical axis", b'AIRCRAFT WIND Y', b'Knots', 'N'],
- "AIRCRAFT_WIND_Z": ["Wind component in aircraft longitudinal axis", b'AIRCRAFT WIND Z', b'Knots', 'N'],
- "BAROMETER_PRESSURE": ["Barometric pressure", b'BAROMETER PRESSURE', b'Millibars', 'N'],
- "SEA_LEVEL_PRESSURE": ["Barometric pressure at sea level", b'SEA LEVEL PRESSURE', b'Millibars', 'N'],
- "TOTAL_AIR_TEMPERATURE": ["Total air temperature is the air temperature at the front of the aircraft where the ram pressure from the speed of the aircraft is taken into account.", b'TOTAL AIR TEMPERATURE', b'Celsius', 'N'],
- "WINDSHIELD_RAIN_EFFECT_AVAILABLE": ["Is visual effect available on this aircraft", b'WINDSHIELD RAIN EFFECT AVAILABLE', b'Bool', 'N'],
- "AMBIENT_IN_CLOUD": ["True if the aircraft is in a cloud.", b'AMBIENT IN CLOUD', b'Bool', 'N'],
- "AMBIENT_VISIBILITY": ["Ambient visibility", b'AMBIENT VISIBILITY', b'Meters', 'N'],
- "STANDARD_ATM_TEMPERATURE": ["Outside temperature on the standard ATM scale", b'STANDARD ATM TEMPERATURE', b'Rankine', 'N'],
- }
+ class __AircraftEnvironmentData(RequestHelper):
+ list = {
+ "AMBIENT_DENSITY": [
+ "Ambient density",
+ b"AMBIENT DENSITY",
+ b"Slugs per cubic feet",
+ "N",
+ ],
+ "AMBIENT_TEMPERATURE": [
+ "Ambient temperature",
+ b"AMBIENT TEMPERATURE",
+ b"Celsius",
+ "N",
+ ],
+ "AMBIENT_PRESSURE": ["Ambient pressure", b"AMBIENT PRESSURE", b"inHg", "N"],
+ "AMBIENT_WIND_VELOCITY": [
+ "Wind velocity",
+ b"AMBIENT WIND VELOCITY",
+ b"Knots",
+ "N",
+ ],
+ "AMBIENT_WIND_DIRECTION": [
+ "Wind direction",
+ b"AMBIENT WIND DIRECTION",
+ b"Degrees",
+ "N",
+ ],
+ "AMBIENT_WIND_X": [
+ "Wind component in East/West direction.",
+ b"AMBIENT WIND X",
+ b"Meters per second",
+ "N",
+ ],
+ "AMBIENT_WIND_Y": [
+ "Wind component in vertical direction.",
+ b"AMBIENT WIND Y",
+ b"Meters per second",
+ "N",
+ ],
+ "AMBIENT_WIND_Z": [
+ "Wind component in North/South direction.",
+ b"AMBIENT WIND Z",
+ b"Meters per second",
+ "N",
+ ],
+ "STRUCT_AMBIENT_WIND": [
+ "X (latitude), Y (vertical) and Z (longitude) components of the wind.",
+ b"STRUCT AMBIENT WIND",
+ b"Feet per second",
+ "N",
+ ],
+ "AIRCRAFT_WIND_X": [
+ "Wind component in aircraft lateral axis",
+ b"AIRCRAFT WIND X",
+ b"Knots",
+ "N",
+ ],
+ "AIRCRAFT_WIND_Y": [
+ "Wind component in aircraft vertical axis",
+ b"AIRCRAFT WIND Y",
+ b"Knots",
+ "N",
+ ],
+ "AIRCRAFT_WIND_Z": [
+ "Wind component in aircraft longitudinal axis",
+ b"AIRCRAFT WIND Z",
+ b"Knots",
+ "N",
+ ],
+ "BAROMETER_PRESSURE": [
+ "Barometric pressure",
+ b"BAROMETER PRESSURE",
+ b"Millibars",
+ "N",
+ ],
+ "SEA_LEVEL_PRESSURE": [
+ "Barometric pressure at sea level",
+ b"SEA LEVEL PRESSURE",
+ b"Millibars",
+ "N",
+ ],
+ "TOTAL_AIR_TEMPERATURE": [
+ "Total air temperature is the air temperature at the front of the aircraft where the ram pressure from the speed of the aircraft is taken into account.",
+ b"TOTAL AIR TEMPERATURE",
+ b"Celsius",
+ "N",
+ ],
+ "WINDSHIELD_RAIN_EFFECT_AVAILABLE": [
+ "Is visual effect available on this aircraft",
+ b"WINDSHIELD RAIN EFFECT AVAILABLE",
+ b"Bool",
+ "N",
+ ],
+ "AMBIENT_IN_CLOUD": [
+ "True if the aircraft is in a cloud.",
+ b"AMBIENT IN CLOUD",
+ b"Bool",
+ "N",
+ ],
+ "AMBIENT_VISIBILITY": [
+ "Ambient visibility",
+ b"AMBIENT VISIBILITY",
+ b"Meters",
+ "N",
+ ],
+ "STANDARD_ATM_TEMPERATURE": [
+ "Outside temperature on the standard ATM scale",
+ b"STANDARD ATM TEMPERATURE",
+ b"Rankine",
+ "N",
+ ],
+ }
- class __HelicopterSpecificData(RequestHelper):
- list = {
- "ROTOR_BRAKE_HANDLE_POS": ["Percent actuated", b'ROTOR BRAKE HANDLE POS', b'Percent Over 100', 'N'],
- "ROTOR_BRAKE_ACTIVE": ["Active", b'ROTOR BRAKE ACTIVE', b'Bool', 'N'],
- "ROTOR_CLUTCH_SWITCH_POS": ["Switch position", b'ROTOR CLUTCH SWITCH POS', b'Bool', 'N'],
- "ROTOR_CLUTCH_ACTIVE": ["Active", b'ROTOR CLUTCH ACTIVE', b'Bool', 'N'],
- "ROTOR_TEMPERATURE": ["Main rotor transmission temperature", b'ROTOR TEMPERATURE', b'Rankine', 'N'],
- "ROTOR_CHIP_DETECTED": ["Chip detection", b'ROTOR CHIP DETECTED', b'Bool', 'N'],
- "ROTOR_GOV_SWITCH_POS": ["Switch position", b'ROTOR GOV SWITCH POS', b'Bool', 'N'],
- "ROTOR_GOV_ACTIVE": ["Active", b'ROTOR GOV ACTIVE', b'Bool', 'N'],
- "ROTOR_LATERAL_TRIM_PCT": ["Trim percent", b'ROTOR LATERAL TRIM PCT', b'Percent Over 100', 'N'],
- "ROTOR_RPM_PCT": ["Percent max rated rpm", b'ROTOR RPM PCT', b'Percent Over 100', 'N'],
- "ENG_TURBINE_TEMPERATURE": ["Turbine temperature. Applies only to Bell helicopter.", b'ENG TURBINE TEMPERATURE', b'Celsius', 'N'],
- "ENG_TORQUE_PERCENT:index": ["Torque. Returns main rotor torque for Bell helicopter, or the indexed rotor torque of other helicopters.", b'ENG TORQUE PERCENT:index', b'Percent scalar 16K (Ft/lbs * 16384)', 'N'],
- "ENG_FUEL_PRESSURE": ["Fuel pressure. Applies only to Bell helicopter.", b'ENG FUEL PRESSURE', b'PSI', 'N'],
- "ENG_ELECTRICAL_LOAD": ["Electrical load. Applies only to Bell helicopter.", b'ENG ELECTRICAL LOAD', b'Percent', 'N'],
- "ENG_TRANSMISSION_PRESSURE": ["Transmission pressure. Applies only to Bell helicopter.", b'ENG TRANSMISSION PRESSURE', b'PSI', 'N'],
- "ENG_TRANSMISSION_TEMPERATURE": ["Transmission temperature. Applies only to Bell helicopter.", b'ENG TRANSMISSION TEMPERATURE', b'Celsius', 'N'],
- "ENG_ROTOR_RPM:index": ["Rotor rpm. Returns main rotor rpm for Bell helicopter, or the indexed rotor rpm of other helicopters.", b'ENG ROTOR RPM:index', b'Percent scalar 16K (Max rpm * 16384)', 'N'],
- "COLLECTIVE_POSITION": ["The position of the helicopter's collective. 0 is fully up, 100 fully depressed.", b'COLLECTIVE POSITION', b'Percent over 100', 'N'],
- }
+ class __HelicopterSpecificData(RequestHelper):
+ list = {
+ "ROTOR_BRAKE_HANDLE_POS": [
+ "Percent actuated",
+ b"ROTOR BRAKE HANDLE POS",
+ b"Percent Over 100",
+ "N",
+ ],
+ "ROTOR_BRAKE_ACTIVE": ["Active", b"ROTOR BRAKE ACTIVE", b"Bool", "N"],
+ "ROTOR_CLUTCH_SWITCH_POS": [
+ "Switch position",
+ b"ROTOR CLUTCH SWITCH POS",
+ b"Bool",
+ "N",
+ ],
+ "ROTOR_CLUTCH_ACTIVE": ["Active", b"ROTOR CLUTCH ACTIVE", b"Bool", "N"],
+ "ROTOR_TEMPERATURE": [
+ "Main rotor transmission temperature",
+ b"ROTOR TEMPERATURE",
+ b"Rankine",
+ "N",
+ ],
+ "ROTOR_CHIP_DETECTED": [
+ "Chip detection",
+ b"ROTOR CHIP DETECTED",
+ b"Bool",
+ "N",
+ ],
+ "ROTOR_GOV_SWITCH_POS": [
+ "Switch position",
+ b"ROTOR GOV SWITCH POS",
+ b"Bool",
+ "N",
+ ],
+ "ROTOR_GOV_ACTIVE": ["Active", b"ROTOR GOV ACTIVE", b"Bool", "N"],
+ "ROTOR_LATERAL_TRIM_PCT": [
+ "Trim percent",
+ b"ROTOR LATERAL TRIM PCT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "ROTOR_RPM_PCT": [
+ "Percent max rated rpm",
+ b"ROTOR RPM PCT",
+ b"Percent Over 100",
+ "N",
+ ],
+ "ENG_TURBINE_TEMPERATURE": [
+ "Turbine temperature. Applies only to Bell helicopter.",
+ b"ENG TURBINE TEMPERATURE",
+ b"Celsius",
+ "N",
+ ],
+ "ENG_TORQUE_PERCENT:index": [
+ "Torque. Returns main rotor torque for Bell helicopter, or the indexed rotor torque of other helicopters.",
+ b"ENG TORQUE PERCENT:index",
+ b"Percent scalar 16K (Ft/lbs * 16384)",
+ "N",
+ ],
+ "ENG_FUEL_PRESSURE": [
+ "Fuel pressure. Applies only to Bell helicopter.",
+ b"ENG FUEL PRESSURE",
+ b"PSI",
+ "N",
+ ],
+ "ENG_ELECTRICAL_LOAD": [
+ "Electrical load. Applies only to Bell helicopter.",
+ b"ENG ELECTRICAL LOAD",
+ b"Percent",
+ "N",
+ ],
+ "ENG_TRANSMISSION_PRESSURE": [
+ "Transmission pressure. Applies only to Bell helicopter.",
+ b"ENG TRANSMISSION PRESSURE",
+ b"PSI",
+ "N",
+ ],
+ "ENG_TRANSMISSION_TEMPERATURE": [
+ "Transmission temperature. Applies only to Bell helicopter.",
+ b"ENG TRANSMISSION TEMPERATURE",
+ b"Celsius",
+ "N",
+ ],
+ "ENG_ROTOR_RPM:index": [
+ "Rotor rpm. Returns main rotor rpm for Bell helicopter, or the indexed rotor rpm of other helicopters.",
+ b"ENG ROTOR RPM:index",
+ b"Percent scalar 16K (Max rpm * 16384)",
+ "N",
+ ],
+ "COLLECTIVE_POSITION": [
+ "The position of the helicopter's collective. 0 is fully up, 100 fully depressed.",
+ b"COLLECTIVE POSITION",
+ b"Percent over 100",
+ "N",
+ ],
+ }
- class __SlingsandHoists(RequestHelper):
- list = {
- "NUM_SLING_CABLES": ["The number of sling cables (not hoists) that are configured for the aircraft. Refer to the document Notes on Aircraft Systems.", b'NUM SLING CABLES', b'Number', 'N'],
- "PAYLOAD_STATION_OBJECT:index": ["Places the named object at the payload station identified by the index (starting from 1). The string is the Container name (refer to the title property of Simulation Object Configuration Files).", b'PAYLOAD STATION OBJECT:index', b'String', 'Y- set only'],
- "PAYLOAD_STATION_NUM_SIMOBJECTS:index": ["The number of objects at the payload station (indexed from 1).", b'PAYLOAD STATION NUM SIMOBJECTS:index', b'Number', 'N'],
- "SLING_OBJECT_ATTACHED:index": ["If units are set as boolean, returns True if a sling object is attached. If units are set as a string, returns the container title of the object. There can be multiple sling positions, indexed from 1. The sling positions are set in the Aircraft Configuration File.", b'SLING OBJECT ATTACHED:index', b'Bool/String', 'N'],
- "SLING_CABLE_BROKEN:index": ["True if the cable is broken.", b'SLING CABLE BROKEN:index', b'Bool', 'N'],
- "SLING_CABLE_EXTENDED_LENGTH:index": ["The length of the cable extending from the aircraft.", b'SLING CABLE EXTENDED LENGTH:index', b'Feet', 'Y'],
- "SLING_ACTIVE_PAYLOAD_STATION:index": ["The payload station (identified by the parameter) where objects will be placed from the sling (identified by the index).", b'SLING ACTIVE PAYLOAD STATION:index', b'Number', 'Y'],
- "SLING_HOIST_PERCENT_DEPLOYED:index": ["The percentage of the full length of the sling cable deployed.", b'SLING HOIST PERCENT DEPLOYED:index', b'Percent over 100', 'N'],
- "IS_ATTACHED_TO_SLING": ["Set to true if this object is attached to a sling.", b'IS ATTACHED TO SLING', b'Bool', 'N'],
- }
+ class __SlingsandHoists(RequestHelper):
+ list = {
+ "NUM_SLING_CABLES": [
+ "The number of sling cables (not hoists) that are configured for the aircraft. Refer to the document Notes on Aircraft Systems.",
+ b"NUM SLING CABLES",
+ b"Number",
+ "N",
+ ],
+ "PAYLOAD_STATION_OBJECT:index": [
+ "Places the named object at the payload station identified by the index (starting from 1). The string is the Container name (refer to the title property of Simulation Object Configuration Files).",
+ b"PAYLOAD STATION OBJECT:index",
+ b"String",
+ "Y- set only",
+ ],
+ "PAYLOAD_STATION_NUM_SIMOBJECTS:index": [
+ "The number of objects at the payload station (indexed from 1).",
+ b"PAYLOAD STATION NUM SIMOBJECTS:index",
+ b"Number",
+ "N",
+ ],
+ "SLING_OBJECT_ATTACHED:index": [
+ "If units are set as boolean, returns True if a sling object is attached. If units are set as a string, returns the container title of the object. There can be multiple sling positions, indexed from 1. The sling positions are set in the Aircraft Configuration File.",
+ b"SLING OBJECT ATTACHED:index",
+ b"Bool/String",
+ "N",
+ ],
+ "SLING_CABLE_BROKEN:index": [
+ "True if the cable is broken.",
+ b"SLING CABLE BROKEN:index",
+ b"Bool",
+ "N",
+ ],
+ "SLING_CABLE_EXTENDED_LENGTH:index": [
+ "The length of the cable extending from the aircraft.",
+ b"SLING CABLE EXTENDED LENGTH:index",
+ b"Feet",
+ "Y",
+ ],
+ "SLING_ACTIVE_PAYLOAD_STATION:index": [
+ "The payload station (identified by the parameter) where objects will be placed from the sling (identified by the index).",
+ b"SLING ACTIVE PAYLOAD STATION:index",
+ b"Number",
+ "Y",
+ ],
+ "SLING_HOIST_PERCENT_DEPLOYED:index": [
+ "The percentage of the full length of the sling cable deployed.",
+ b"SLING HOIST PERCENT DEPLOYED:index",
+ b"Percent over 100",
+ "N",
+ ],
+ "IS_ATTACHED_TO_SLING": [
+ "Set to true if this object is attached to a sling.",
+ b"IS ATTACHED TO SLING",
+ b"Bool",
+ "N",
+ ],
+ }
- class __AircraftMiscellaneousSystemsData(RequestHelper):
- list = {
- "SMOKE_ENABLE": ["Set to True to activate the smoke system, if one is available (for example, on the Extra).", b'SMOKE ENABLE', b'Bool', 'Y'],
- "SMOKESYSTEM_AVAILABLE": ["Smoke system available", b'SMOKESYSTEM AVAILABLE', b'Bool', 'N'],
- "PITOT_HEAT": ["Pitot heat active", b'PITOT HEAT', b'Bool', 'N'],
- "FOLDING_WING_LEFT_PERCENT": ["Left folding wing position, 100 is fully folded", b'FOLDING WING LEFT PERCENT', b'Percent Over 100', 'Y'],
- "FOLDING_WING_RIGHT_PERCENT": ["Right folding wing position, 100 is fully folded", b'FOLDING WING RIGHT PERCENT', b'Percent Over 100', 'Y'],
- "CANOPY_OPEN": ["Percent primary door/exit open", b'CANOPY OPEN', b'Percent Over 100', 'Y'],
- "TAILHOOK_POSITION": ["Percent tail hook extended", b'TAILHOOK POSITION', b'Percent Over 100', 'Y'],
- "EXIT_OPEN:index": ["Percent door/exit open", b'EXIT OPEN:index', b'Percent Over 100', 'Y'],
- "STALL_HORN_AVAILABLE": ["True if stall alarm available", b'STALL HORN AVAILABLE', b'Bool', 'N'],
- "ENGINE_MIXURE_AVAILABLE": ["True if engine mixture is available for prop engines. Obsolete value as mixture is always available. Spelling error in variable name.", b'ENGINE MIXURE AVAILABLE', b'Bool', 'N'],
- "CARB_HEAT_AVAILABLE": ["True if carb heat available", b'CARB HEAT AVAILABLE', b'Bool', 'N'],
- "SPOILER_AVAILABLE": ["True if spoiler system available", b'SPOILER AVAILABLE', b'Bool', 'N'],
- "IS_TAIL_DRAGGER": ["True if the aircraft is a taildragger", b'IS TAIL DRAGGER', b'Bool', 'N'],
- "STROBES_AVAILABLE": ["True if strobe lights are available", b'STROBES AVAILABLE', b'Bool', 'N'],
- "TOE_BRAKES_AVAILABLE": ["True if toe brakes are available", b'TOE BRAKES AVAILABLE', b'Bool', 'N'],
- "PUSHBACK_STATE": ["Type of pushback :; 0 = Straight; 1 = Left; 2 = Right", b'PUSHBACK STATE', b'Enum', 'Y'],
- "ELECTRICAL_MASTER_BATTERY": ["Battery switch position", b'ELECTRICAL MASTER BATTERY', b'Bool', 'Y'],
- "ELECTRICAL_TOTAL_LOAD_AMPS": ["Total load amps", b'ELECTRICAL TOTAL LOAD AMPS', b'Amperes', 'Y'],
- "ELECTRICAL_BATTERY_LOAD": ["Battery load", b'ELECTRICAL BATTERY LOAD', b'Amperes', 'Y'],
- "ELECTRICAL_BATTERY_VOLTAGE": ["Battery voltage", b'ELECTRICAL BATTERY VOLTAGE', b'Volts', 'Y'],
- "ELECTRICAL_MAIN_BUS_VOLTAGE": ["Main bus voltage", b'ELECTRICAL MAIN BUS VOLTAGE', b'Volts', 'Y'],
- "ELECTRICAL_MAIN_BUS_AMPS": ["Main bus current", b'ELECTRICAL MAIN BUS AMPS', b'Amperes', 'Y'],
- "ELECTRICAL_AVIONICS_BUS_VOLTAGE": ["Avionics bus voltage", b'ELECTRICAL AVIONICS BUS VOLTAGE', b'Volts', 'Y'],
- "ELECTRICAL_AVIONICS_BUS_AMPS": ["Avionics bus current", b'ELECTRICAL AVIONICS BUS AMPS', b'Amperes', 'Y'],
- "ELECTRICAL_HOT_BATTERY_BUS_VOLTAGE": ["Voltage available when battery switch is turned off", b'ELECTRICAL HOT BATTERY BUS VOLTAGE', b'Volts', 'Y'],
- "ELECTRICAL_HOT_BATTERY_BUS_AMPS": ["Current available when battery switch is turned off", b'ELECTRICAL HOT BATTERY BUS AMPS', b'Amperes', 'Y'],
- "ELECTRICAL_BATTERY_BUS_VOLTAGE": ["Battery bus voltage", b'ELECTRICAL BATTERY BUS VOLTAGE', b'Volts', 'Y'],
- "ELECTRICAL_BATTERY_BUS_AMPS": ["Battery bus current", b'ELECTRICAL BATTERY BUS AMPS', b'Amperes', 'Y'],
- "ELECTRICAL_GENALT_BUS_VOLTAGE:index": ["Genalt bus voltage (takes engine index)", b'ELECTRICAL GENALT BUS VOLTAGE:index', b'Volts', 'Y'],
- "ELECTRICAL_GENALT_BUS_AMPS:index": ["Genalt bus current (takes engine index)", b'ELECTRICAL GENALT BUS AMPS:index', b'Amperes', 'Y'],
- "CIRCUIT_GENERAL_PANEL_ON": ["Is electrical power available to this circuit", b'CIRCUIT GENERAL PANEL ON', b'Bool', 'N'],
- "CIRCUIT_FLAP_MOTOR_ON": ["Is electrical power available to this circuit", b'CIRCUIT FLAP MOTOR ON', b'Bool', 'N'],
- "CIRCUIT_GEAR_MOTOR_ON": ["Is electrical power available to this circuit", b'CIRCUIT GEAR MOTOR ON', b'Bool', 'N'],
- "CIRCUIT_AUTOPILOT_ON": ["Is electrical power available to this circuit", b'CIRCUIT AUTOPILOT ON', b'Bool', 'N'],
- "CIRCUIT_AVIONICS_ON": ["Is electrical power available to this circuit", b'CIRCUIT AVIONICS ON', b'Bool', 'N'],
- "CIRCUIT_PITOT_HEAT_ON": ["Is electrical power available to this circuit", b'CIRCUIT PITOT HEAT ON', b'Bool', 'N'],
- "CIRCUIT_PROP_SYNC_ON": ["Is electrical power available to this circuit", b'CIRCUIT PROP SYNC ON', b'Bool', 'N'],
- "CIRCUIT_AUTO_FEATHER_ON": ["Is electrical power available to this circuit", b'CIRCUIT AUTO FEATHER ON', b'Bool', 'N'],
- "CIRCUIT_AUTO_BRAKES_ON": ["Is electrical power available to this circuit", b'CIRCUIT AUTO BRAKES ON', b'Bool', 'N'],
- "CIRCUIT_STANDY_VACUUM_ON": ["Is electrical power available to this circuit", b'CIRCUIT STANDY VACUUM ON', b'Bool', 'N'],
- "CIRCUIT_MARKER_BEACON_ON": ["Is electrical power available to this circuit", b'CIRCUIT MARKER BEACON ON', b'Bool', 'N'],
- "CIRCUIT_GEAR_WARNING_ON": ["Is electrical power available to this circuit", b'CIRCUIT GEAR WARNING ON', b'Bool', 'N'],
- "CIRCUIT_HYDRAULIC_PUMP_ON": ["Is electrical power available to this circuit", b'CIRCUIT HYDRAULIC PUMP ON', b'Bool', 'N'],
- "HYDRAULIC_PRESSURE:index": ["Hydraulic system pressure. Indexes start at 1.", b'HYDRAULIC PRESSURE:index', b'Pound force per square foot', 'N'],
- "HYDRAULIC_RESERVOIR_PERCENT:index": ["Hydraulic pressure changes will follow changes to this variable. Indexes start at 1.", b'HYDRAULIC RESERVOIR PERCENT:index', b'Percent Over 100', 'Y'],
- "HYDRAULIC_SYSTEM_INTEGRITY": ["Percent system functional", b'HYDRAULIC SYSTEM INTEGRITY', b'Percent Over 100', 'N'],
- "STRUCTURAL_DEICE_SWITCH": ["True if the aircraft structure deice switch is on", b'STRUCTURAL DEICE SWITCH', b'Bool', 'N'],
- "APPLY_HEAT_TO_SYSTEMS": ["Used when too close to a fire.", b'APPLY HEAT TO SYSTEMS', b'Bool', 'Y'],
- "DROPPABLE_OBJECTS_TYPE:index": ["The type of droppable object at the station number identified by the index.", b'DROPPABLE OBJECTS TYPE:index', b'String', 'Y'],
- "DROPPABLE_OBJECTS_COUNT:index": ["The number of droppable objects at the station number identified by the index.", b'DROPPABLE OBJECTS COUNT:index', b'Number', 'N'],
- }
+ class __AircraftMiscellaneousSystemsData(RequestHelper):
+ list = {
+ "SMOKE_ENABLE": [
+ "Set to True to activate the smoke system, if one is available (for example, on the Extra).",
+ b"SMOKE ENABLE",
+ b"Bool",
+ "Y",
+ ],
+ "SMOKESYSTEM_AVAILABLE": [
+ "Smoke system available",
+ b"SMOKESYSTEM AVAILABLE",
+ b"Bool",
+ "N",
+ ],
+ "PITOT_HEAT": ["Pitot heat active", b"PITOT HEAT", b"Bool", "N"],
+ "FOLDING_WING_LEFT_PERCENT": [
+ "Left folding wing position, 100 is fully folded",
+ b"FOLDING WING LEFT PERCENT",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "FOLDING_WING_RIGHT_PERCENT": [
+ "Right folding wing position, 100 is fully folded",
+ b"FOLDING WING RIGHT PERCENT",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "CANOPY_OPEN": [
+ "Percent primary door/exit open",
+ b"CANOPY OPEN",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "TAILHOOK_POSITION": [
+ "Percent tail hook extended",
+ b"TAILHOOK POSITION",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "EXIT_OPEN:index": [
+ "Percent door/exit open",
+ b"EXIT OPEN:index",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "STALL_HORN_AVAILABLE": [
+ "True if stall alarm available",
+ b"STALL HORN AVAILABLE",
+ b"Bool",
+ "N",
+ ],
+ "ENGINE_MIXURE_AVAILABLE": [
+ "True if engine mixture is available for prop engines. Obsolete value as mixture is always available. Spelling error in variable name.",
+ b"ENGINE MIXURE AVAILABLE",
+ b"Bool",
+ "N",
+ ],
+ "CARB_HEAT_AVAILABLE": [
+ "True if carb heat available",
+ b"CARB HEAT AVAILABLE",
+ b"Bool",
+ "N",
+ ],
+ "SPOILER_AVAILABLE": [
+ "True if spoiler system available",
+ b"SPOILER AVAILABLE",
+ b"Bool",
+ "N",
+ ],
+ "IS_TAIL_DRAGGER": [
+ "True if the aircraft is a taildragger",
+ b"IS TAIL DRAGGER",
+ b"Bool",
+ "N",
+ ],
+ "STROBES_AVAILABLE": [
+ "True if strobe lights are available",
+ b"STROBES AVAILABLE",
+ b"Bool",
+ "N",
+ ],
+ "TOE_BRAKES_AVAILABLE": [
+ "True if toe brakes are available",
+ b"TOE BRAKES AVAILABLE",
+ b"Bool",
+ "N",
+ ],
+ "PUSHBACK_STATE": [
+ "Type of pushback :; 0 = Straight; 1 = Left; 2 = Right",
+ b"PUSHBACK STATE",
+ b"Enum",
+ "Y",
+ ],
+ "ELECTRICAL_MASTER_BATTERY": [
+ "Battery switch position",
+ b"ELECTRICAL MASTER BATTERY",
+ b"Bool",
+ "Y",
+ ],
+ "ELECTRICAL_TOTAL_LOAD_AMPS": [
+ "Total load amps",
+ b"ELECTRICAL TOTAL LOAD AMPS",
+ b"Amperes",
+ "Y",
+ ],
+ "ELECTRICAL_BATTERY_LOAD": [
+ "Battery load",
+ b"ELECTRICAL BATTERY LOAD",
+ b"Amperes",
+ "Y",
+ ],
+ "ELECTRICAL_BATTERY_VOLTAGE": [
+ "Battery voltage",
+ b"ELECTRICAL BATTERY VOLTAGE",
+ b"Volts",
+ "Y",
+ ],
+ "ELECTRICAL_MAIN_BUS_VOLTAGE": [
+ "Main bus voltage",
+ b"ELECTRICAL MAIN BUS VOLTAGE",
+ b"Volts",
+ "Y",
+ ],
+ "ELECTRICAL_MAIN_BUS_AMPS": [
+ "Main bus current",
+ b"ELECTRICAL MAIN BUS AMPS",
+ b"Amperes",
+ "Y",
+ ],
+ "ELECTRICAL_AVIONICS_BUS_VOLTAGE": [
+ "Avionics bus voltage",
+ b"ELECTRICAL AVIONICS BUS VOLTAGE",
+ b"Volts",
+ "Y",
+ ],
+ "ELECTRICAL_AVIONICS_BUS_AMPS": [
+ "Avionics bus current",
+ b"ELECTRICAL AVIONICS BUS AMPS",
+ b"Amperes",
+ "Y",
+ ],
+ "ELECTRICAL_HOT_BATTERY_BUS_VOLTAGE": [
+ "Voltage available when battery switch is turned off",
+ b"ELECTRICAL HOT BATTERY BUS VOLTAGE",
+ b"Volts",
+ "Y",
+ ],
+ "ELECTRICAL_HOT_BATTERY_BUS_AMPS": [
+ "Current available when battery switch is turned off",
+ b"ELECTRICAL HOT BATTERY BUS AMPS",
+ b"Amperes",
+ "Y",
+ ],
+ "ELECTRICAL_BATTERY_BUS_VOLTAGE": [
+ "Battery bus voltage",
+ b"ELECTRICAL BATTERY BUS VOLTAGE",
+ b"Volts",
+ "Y",
+ ],
+ "ELECTRICAL_BATTERY_BUS_AMPS": [
+ "Battery bus current",
+ b"ELECTRICAL BATTERY BUS AMPS",
+ b"Amperes",
+ "Y",
+ ],
+ "ELECTRICAL_GENALT_BUS_VOLTAGE:index": [
+ "Genalt bus voltage (takes engine index)",
+ b"ELECTRICAL GENALT BUS VOLTAGE:index",
+ b"Volts",
+ "Y",
+ ],
+ "ELECTRICAL_GENALT_BUS_AMPS:index": [
+ "Genalt bus current (takes engine index)",
+ b"ELECTRICAL GENALT BUS AMPS:index",
+ b"Amperes",
+ "Y",
+ ],
+ "CIRCUIT_GENERAL_PANEL_ON": [
+ "Is electrical power available to this circuit",
+ b"CIRCUIT GENERAL PANEL ON",
+ b"Bool",
+ "N",
+ ],
+ "CIRCUIT_FLAP_MOTOR_ON": [
+ "Is electrical power available to this circuit",
+ b"CIRCUIT FLAP MOTOR ON",
+ b"Bool",
+ "N",
+ ],
+ "CIRCUIT_GEAR_MOTOR_ON": [
+ "Is electrical power available to this circuit",
+ b"CIRCUIT GEAR MOTOR ON",
+ b"Bool",
+ "N",
+ ],
+ "CIRCUIT_AUTOPILOT_ON": [
+ "Is electrical power available to this circuit",
+ b"CIRCUIT AUTOPILOT ON",
+ b"Bool",
+ "N",
+ ],
+ "CIRCUIT_AVIONICS_ON": [
+ "Is electrical power available to this circuit",
+ b"CIRCUIT AVIONICS ON",
+ b"Bool",
+ "N",
+ ],
+ "CIRCUIT_PITOT_HEAT_ON": [
+ "Is electrical power available to this circuit",
+ b"CIRCUIT PITOT HEAT ON",
+ b"Bool",
+ "N",
+ ],
+ "CIRCUIT_PROP_SYNC_ON": [
+ "Is electrical power available to this circuit",
+ b"CIRCUIT PROP SYNC ON",
+ b"Bool",
+ "N",
+ ],
+ "CIRCUIT_AUTO_FEATHER_ON": [
+ "Is electrical power available to this circuit",
+ b"CIRCUIT AUTO FEATHER ON",
+ b"Bool",
+ "N",
+ ],
+ "CIRCUIT_AUTO_BRAKES_ON": [
+ "Is electrical power available to this circuit",
+ b"CIRCUIT AUTO BRAKES ON",
+ b"Bool",
+ "N",
+ ],
+ "CIRCUIT_STANDY_VACUUM_ON": [
+ "Is electrical power available to this circuit",
+ b"CIRCUIT STANDY VACUUM ON",
+ b"Bool",
+ "N",
+ ],
+ "CIRCUIT_MARKER_BEACON_ON": [
+ "Is electrical power available to this circuit",
+ b"CIRCUIT MARKER BEACON ON",
+ b"Bool",
+ "N",
+ ],
+ "CIRCUIT_GEAR_WARNING_ON": [
+ "Is electrical power available to this circuit",
+ b"CIRCUIT GEAR WARNING ON",
+ b"Bool",
+ "N",
+ ],
+ "CIRCUIT_HYDRAULIC_PUMP_ON": [
+ "Is electrical power available to this circuit",
+ b"CIRCUIT HYDRAULIC PUMP ON",
+ b"Bool",
+ "N",
+ ],
+ "HYDRAULIC_PRESSURE:index": [
+ "Hydraulic system pressure. Indexes start at 1.",
+ b"HYDRAULIC PRESSURE:index",
+ b"Pound force per square foot",
+ "N",
+ ],
+ "HYDRAULIC_RESERVOIR_PERCENT:index": [
+ "Hydraulic pressure changes will follow changes to this variable. Indexes start at 1.",
+ b"HYDRAULIC RESERVOIR PERCENT:index",
+ b"Percent Over 100",
+ "Y",
+ ],
+ "HYDRAULIC_SYSTEM_INTEGRITY": [
+ "Percent system functional",
+ b"HYDRAULIC SYSTEM INTEGRITY",
+ b"Percent Over 100",
+ "N",
+ ],
+ "STRUCTURAL_DEICE_SWITCH": [
+ "True if the aircraft structure deice switch is on",
+ b"STRUCTURAL DEICE SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "APPLY_HEAT_TO_SYSTEMS": [
+ "Used when too close to a fire.",
+ b"APPLY HEAT TO SYSTEMS",
+ b"Bool",
+ "Y",
+ ],
+ "DROPPABLE_OBJECTS_TYPE:index": [
+ "The type of droppable object at the station number identified by the index.",
+ b"DROPPABLE OBJECTS TYPE:index",
+ b"String",
+ "Y",
+ ],
+ "DROPPABLE_OBJECTS_COUNT:index": [
+ "The number of droppable objects at the station number identified by the index.",
+ b"DROPPABLE OBJECTS COUNT:index",
+ b"Number",
+ "N",
+ ],
+ }
- class __AircraftMiscellaneousData(RequestHelper):
- list = {
- "TOTAL_WEIGHT": ["Total weight of the aircraft", b'TOTAL WEIGHT', b'Pounds', 'N'],
- "MAX_GROSS_WEIGHT": ["Maximum gross weight of the aircaft", b'MAX GROSS WEIGHT', b'Pounds', 'N'],
- "EMPTY_WEIGHT": ["Empty weight of the aircraft", b'EMPTY WEIGHT', b'Pounds', 'N'],
- "IS_USER_SIM": ["Is this the user loaded aircraft", b'IS USER SIM', b'Bool', 'N'],
- "SIM_DISABLED": ["Is sim disabled", b'SIM DISABLED', b'Bool', 'Y'],
- "G_FORCE": ["Current g force", b'G FORCE', b'GForce', 'Y'],
- "ATC_HEAVY": ["Is this aircraft recognized by ATC as heavy", b'ATC HEAVY', b'Bool', 'Y'],
- "AUTO_COORDINATION": ["Is auto-coordination active", b'AUTO COORDINATION', b'Bool', 'Y'],
- "REALISM": ["General realism percent", b'REALISM', b'Number', 'Y'],
- "TRUE_AIRSPEED_SELECTED": ["True if True Airspeed has been selected", b'TRUE AIRSPEED SELECTED', b'Bool', 'Y'],
- "DESIGN_SPEED_VS0": ["Design speed at VS0", b'DESIGN SPEED VS0', b'Feet per second', 'N'],
- "DESIGN_SPEED_VS1": ["Design speed at VS1", b'DESIGN SPEED VS1', b'Feet per second', 'N'],
- "DESIGN_SPEED_VC": ["Design speed at VC", b'DESIGN SPEED VC', b'Feet per second', 'N'],
- "MIN_DRAG_VELOCITY": ["Minimum drag velocity", b'MIN DRAG VELOCITY', b'Feet per second', 'N'],
- "ESTIMATED_CRUISE_SPEED": ["Estimated cruise speed", b'ESTIMATED CRUISE SPEED', b'Feet per second', 'N'],
- "CG_PERCENT": ["Longitudinal CG position as a percent of reference chord", b'CG PERCENT', b'Percent over 100', 'N'],
- "CG_PERCENT_LATERAL": ["Lateral CG position as a percent of reference chord", b'CG PERCENT LATERAL', b'Percent over 100', 'N'],
- "IS_SLEW_ACTIVE": ["True if slew is active", b'IS SLEW ACTIVE', b'Bool', 'Y'],
- "IS_SLEW_ALLOWED": ["True if slew is enabled", b'IS SLEW ALLOWED', b'Bool', 'Y'],
- "ATC_SUGGESTED_MIN_RWY_TAKEOFF": ["Suggested minimum runway length for takeoff. Used by ATC ", b'ATC SUGGESTED MIN RWY TAKEOFF', b'Feet', 'N'],
- "ATC_SUGGESTED_MIN_RWY_LANDING": ["Suggested minimum runway length for landing. Used by ATC ", b'ATC SUGGESTED MIN RWY LANDING', b'Feet', 'N'],
- "PAYLOAD_STATION_WEIGHT:index": ["Individual payload station weight", b'PAYLOAD STATION WEIGHT:index', b'Pounds', 'Y'],
- "PAYLOAD_STATION_COUNT": ["Number of payload stations", b'PAYLOAD STATION COUNT', b'Number', 'N'],
- "USER_INPUT_ENABLED": ["Is input allowed from the user", b'USER INPUT ENABLED', b'Bool', 'Y'],
- "TYPICAL_DESCENT_RATE": ["Normal descent rate", b'TYPICAL DESCENT RATE', b'Feet per minute', 'N'],
- "VISUAL_MODEL_RADIUS": ["Model radius", b'VISUAL MODEL RADIUS', b'Meters', 'N'],
- "SIGMA_SQRT": ["Sigma sqrt", b'SIGMA SQRT', b'Number', 'N'],
- "DYNAMIC_PRESSURE": ["Dynamic pressure", b'DYNAMIC PRESSURE', b'foot pounds', 'N'],
- "TOTAL_VELOCITY": ["Velocity regardless of direction. For example, if a helicopter is ascending vertically at 100 fps, getting this variable will return 100.", b'TOTAL VELOCITY', b'Feet per second', 'N'],
- "AIRSPEED_SELECT_INDICATED_OR_TRUE": ["The airspeed, whether true or indicated airspeed has been selected.", b'AIRSPEED SELECT INDICATED OR TRUE', b'Knots', 'N'],
- "VARIOMETER_RATE": ["Variometer rate", b'VARIOMETER RATE', b'Feet per second', 'N'],
- "VARIOMETER_SWITCH": ["True if the variometer switch is on", b'VARIOMETER SWITCH', b'Bool', 'N'],
- "PRESSURE_ALTITUDE": ["Altitude reading", b'PRESSURE ALTITUDE', b'Meters', 'N'],
- "MAGNETIC_COMPASS": ["Compass reading", b'MAGNETIC COMPASS', b'Degrees', 'N'],
- "TURN_INDICATOR_RATE": ["Turn indicator reading", b'TURN INDICATOR RATE', b'Radians per second', 'N'],
- "TURN_INDICATOR_SWITCH": ["True if turn indicator switch is on", b'TURN INDICATOR SWITCH', b'Bool', 'N'],
- "YOKE_Y_INDICATOR": ["Yoke position in vertical direction", b'YOKE Y INDICATOR', b'Position', 'N'],
- "YOKE_X_INDICATOR": ["Yoke position in horizontal direction", b'YOKE X INDICATOR', b'Position', 'N'],
- "RUDDER_PEDAL_INDICATOR": ["Rudder pedal position", b'RUDDER PEDAL INDICATOR', b'Position', 'N'],
- "BRAKE_DEPENDENT_HYDRAULIC_PRESSURE": ["Brake dependent hydraulic pressure reading", b'BRAKE DEPENDENT HYDRAULIC PRESSURE', b'foot pounds', 'N'],
- "PANEL_ANTI_ICE_SWITCH": ["True if panel anti-ice switch is on", b'PANEL ANTI ICE SWITCH', b'Bool', 'N'],
- "WING_AREA": ["Total wing area", b'WING AREA', b'Square feet', 'N'],
- "WING_SPAN": ["Total wing span", b'WING SPAN', b'Feet', 'N'],
- "BETA_DOT": ["Beta dot", b'BETA DOT', b'Radians per second', 'N'],
- "LINEAR_CL_ALPHA": ["Linear CL alpha", b'LINEAR CL ALPHA', b'Per radian', 'N'],
- "STALL_ALPHA": ["Stall alpha", b'STALL ALPHA', b'Radians', 'N'],
- "ZERO_LIFT_ALPHA": ["Zero lift alpha", b'ZERO LIFT ALPHA', b'Radians', 'N'],
- "CG_AFT_LIMIT": ["Aft limit of CG", b'CG AFT LIMIT', b'Percent over 100', 'N'],
- "CG_FWD_LIMIT": ["Forward limit of CG", b'CG FWD LIMIT', b'Percent over 100', 'N'],
- "CG_MAX_MACH": ["Max mach CG", b'CG MAX MACH', b'Machs', 'N'],
- "CG_MIN_MACH": ["Min mach CG", b'CG MIN MACH', b'Machs', 'N'],
- "PAYLOAD_STATION_NAME": ["Descriptive name for payload station", b'PAYLOAD STATION NAME', b'String', 'N'],
- "ELEVON_DEFLECTION": ["Elevon deflection", b'ELEVON DEFLECTION', b'Radians', 'N'],
- "EXIT_TYPE": ["One of:; 0: Main; 1: Cargo; 2: Emergency; 3: Unknown", b'EXIT TYPE', b'Enum', 'N'],
- "EXIT_POSX": ["Position of exit relative to datum reference point", b'EXIT POSX', b'Feet', 'N'],
- "EXIT_POSY": ["Position of exit relative to datum reference point", b'EXIT POSY', b'Feet', 'N'],
- "EXIT_POSZ": ["Position of exit relative to datum reference point", b'EXIT POSZ', b'Feet', 'N'],
- "DECISION_HEIGHT": ["Design decision height", b'DECISION HEIGHT', b'Feet', 'N'],
- "DECISION_ALTITUDE_MSL": ["Design decision altitude above mean sea level", b'DECISION ALTITUDE MSL', b'Feet', 'N'],
- "EMPTY_WEIGHT_PITCH_MOI": ["Empty weight pitch moment of inertia", b'EMPTY WEIGHT PITCH MOI', b'slug feet squared', 'N'],
- "EMPTY_WEIGHT_ROLL_MOI": ["Empty weight roll moment of inertia", b'EMPTY WEIGHT ROLL MOI', b'slug feet squared', 'N'],
- "EMPTY_WEIGHT_YAW_MOI": ["Empty weight yaw moment of inertia", b'EMPTY WEIGHT YAW MOI', b'slug feet squared', 'N'],
- "EMPTY_WEIGHT_CROSS_COUPLED_MOI": ["Empty weigth cross coupled moment of inertia", b'EMPTY WEIGHT CROSS COUPLED MOI', b'slug feet squared', 'N'],
- "TOTAL_WEIGHT_PITCH_MOI": ["Total weight pitch moment of inertia", b'TOTAL WEIGHT PITCH MOI', b'slug feet squared', 'N'],
- "TOTAL_WEIGHT_ROLL_MOI": ["Total weight roll moment of inertia", b'TOTAL WEIGHT ROLL MOI', b'slug feet squared', 'N'],
- "TOTAL_WEIGHT_YAW_MOI": ["Total weight yaw moment of inertia", b'TOTAL WEIGHT YAW MOI', b'slug feet squared', 'N'],
- "TOTAL_WEIGHT_CROSS_COUPLED_MOI": ["Total weight cross coupled moment of inertia", b'TOTAL WEIGHT CROSS COUPLED MOI', b'slug feet squared', 'N'],
- "WATER_BALLAST_VALVE": ["True if water ballast valve is available", b'WATER BALLAST VALVE', b'Bool', 'N'],
- "MAX_RATED_ENGINE_RPM": ["Maximum rated rpm", b'MAX RATED ENGINE RPM', b'Rpm', 'N'],
- "FULL_THROTTLE_THRUST_TO_WEIGHT_RATIO": ["Full throttle thrust to weight ratio", b'FULL THROTTLE THRUST TO WEIGHT RATIO', b'Number', 'N'],
- "PROP_AUTO_CRUISE_ACTIVE": ["True if prop auto cruise active", b'PROP AUTO CRUISE ACTIVE', b'Bool', 'N'],
- "PROP_ROTATION_ANGLE": ["Prop rotation angle", b'PROP ROTATION ANGLE', b'Radians', 'N'],
- "PROP_BETA_MAX": ["Prop beta max", b'PROP BETA MAX', b'Radians', 'N'],
- "PROP_BETA_MIN": ["Prop beta min", b'PROP BETA MIN', b'Radians', 'N'],
- "PROP_BETA_MIN_REVERSE": ["Prop beta min reverse", b'PROP BETA MIN REVERSE', b'Radians', 'N'],
- "FUEL_SELECTED_TRANSFER_MODE": ["One of:; -1: off; 0: auto; 1: forward; 2: aft; 3: manual", b'FUEL SELECTED TRANSFER MODE', b'Enum', 'N'],
- "DROPPABLE_OBJECTS_UI_NAME": ["Descriptive name, used in User Interface dialogs, of a droppable object", b'DROPPABLE OBJECTS UI NAME', b'String', 'N'],
- "MANUAL_FUEL_PUMP_HANDLE": ["Position of manual fuel pump handle. 100 is fully deployed.", b'MANUAL FUEL PUMP HANDLE', b'Percent over 100', 'N'],
- "BLEED_AIR_SOURCE_CONTROL": ["One of:; 0: min; 1: auto; 2: off; 3: apu; 4: engines", b'BLEED AIR SOURCE CONTROL', b'Enum', 'N'],
- "ELECTRICAL_OLD_CHARGING_AMPS": ["Legacy, use ELECTRICAL BATTERY LOAD", b'ELECTRICAL OLD CHARGING AMPS', b'Amps', 'N'],
- "HYDRAULIC_SWITCH": ["True if hydraulic switch is on", b'HYDRAULIC SWITCH', b'Bool', 'N'],
- "CONCORDE_VISOR_POSITION_PERCENT": ["0 = up, 1.0 = extended/down", b'CONCORDE VISOR POSITION PERCENT', b'Percent over 100', 'N'],
- "CONCORDE_NOSE_ANGLE": ["0 = up", b'CONCORDE NOSE ANGLE', b'Radians', 'N'],
- "REALISM_CRASH_WITH_OTHERS": ["True indicates crashing with other aircraft is possible.", b'REALISM CRASH WITH OTHERS', b'Bool', 'N'],
- "REALISM_CRASH_DETECTION": ["True indicates crash detection is turned on.", b'REALISM CRASH DETECTION', b'Bool', 'N'],
- "MANUAL_INSTRUMENT_LIGHTS": ["True if instrument lights are set manually", b'MANUAL INSTRUMENT LIGHTS', b'Bool', 'N'],
- "PITOT_ICE_PCT": ["Amount of pitot ice. 100 is fully iced.", b'PITOT ICE PCT', b'Percent over 100', 'N'],
- "SEMIBODY_LOADFACTOR_Y": ["Semibody loadfactor x and z are not supported.", b'SEMIBODY LOADFACTOR Y', b'Number', 'N'],
- "SEMIBODY_LOADFACTOR_YDOT": ["Semibody loadfactory ydot", b'SEMIBODY LOADFACTOR YDOT', b'Per second', 'N'],
- "RAD_INS_SWITCH": ["True if Rad INS switch on", b'RAD INS SWITCH', b'Bool', 'N'],
- "SIMULATED_RADIUS": ["Simulated radius", b'SIMULATED RADIUS', b'Feet', 'N'],
- "STRUCTURAL_ICE_PCT": ["Amount of ice on aircraft structure. 100 is fully iced.", b'STRUCTURAL ICE PCT', b'Percent over 100', 'N'],
- "ARTIFICIAL_GROUND_ELEVATION": ["In case scenery is not loaded for AI planes, this variable can be used to set a default surface elevation.", b'ARTIFICIAL GROUND ELEVATION', b'Feet', 'N'],
- "SURFACE_INFO_VALID": ["True indicates SURFACE CONDITION is meaningful.", b'SURFACE INFO VALID', b'Bool', 'N'],
- "SURFACE_CONDITION": ["One of:; 0: Normal; 1: Wet; 2: Icy; 3: Snow", b'SURFACE CONDITION', b'Enum', 'N'],
- "PUSHBACK_ANGLE": ["Pushback angle (the heading of the tug)", b'PUSHBACK ANGLE', b'Radians', 'N'],
- "PUSHBACK_CONTACTX": ["The towpoint position, relative to the aircrafts datum reference point.", b'PUSHBACK CONTACTX', b'Feet', 'N'],
- "PUSHBACK_CONTACTY": ["Pushback contact position in vertical direction", b'PUSHBACK CONTACTY', b'Feet', 'N'],
- "PUSHBACK_CONTACTZ": ["Pushback contact position in fore/aft direction", b'PUSHBACK CONTACTZ', b'Feet', 'N'],
- "PUSHBACK_WAIT": ["True if waiting for pushback.", b'PUSHBACK WAIT', b'Bool', 'N'],
- "YAW_STRING_ANGLE": ["The yaw string angle. Yaw strings are attached to gliders as visible indicators of the yaw angle. An animation of this is not implemented in ESP.", b'YAW STRING ANGLE', b'Radians', 'N'],
- "YAW_STRING_PCT_EXTENDED": ["Yaw string angle as a percentage", b'YAW STRING PCT EXTENDED', b'Percent over 100', 'N'],
- "INDUCTOR_COMPASS_PERCENT_DEVIATION": ["Inductor compass deviation reading", b'INDUCTOR COMPASS PERCENT DEVIATION', b'Percent over 100', 'N'],
- "INDUCTOR_COMPASS_HEADING_REF": ["Inductor compass heading", b'INDUCTOR COMPASS HEADING REF', b'Radians', 'N'],
- "ANEMOMETER_PCT_RPM": ["Anemometer rpm as a percentage", b'ANEMOMETER PCT RPM', b'Percent over 100', 'N'],
- "ROTOR_ROTATION_ANGLE": ["Main rotor rotation angle (helicopters only)", b'ROTOR ROTATION ANGLE', b'Radians', 'N'],
- "DISK_PITCH_ANGLE": ["Main rotor pitch angle (helicopters only)", b'DISK PITCH ANGLE', b'Radians', 'N'],
- "DISK_BANK_ANGLE": ["Main rotor bank angle (helicopters only)", b'DISK BANK ANGLE', b'Radians', 'N'],
- "DISK_PITCH_PCT": ["Main rotor pitch percent (helicopters only)", b'DISK PITCH PCT', b'Percent over 100', 'N'],
- "DISK_BANK_PCT": ["Main rotor bank percent (helicopters only)", b'DISK BANK PCT', b'Percent over 100', 'N'],
- "DISK_CONING_PCT": ["Main rotor coning percent (helicopters only)", b'DISK CONING PCT', b'Percent over 100', 'N'],
- # "NAV_VOR_LLAF64": ["Nav VOR latitude, longitude, altitude", b'NAV VOR LLAF64', b'SIMCONNECT_DATA_LATLONALT', 'N'],
- # "NAV_GS_LLAF64": ["Nav GS latitude, longitude, altitude", b'NAV GS LLAF64', b'SIMCONNECT_DATA_LATLONALT', 'N'],
- "STATIC_CG_TO_GROUND": ["Static CG to ground", b'STATIC CG TO GROUND', b'Feet', 'N'],
- "STATIC_PITCH": ["Static pitch", b'STATIC PITCH', b'Radians', 'N'],
- "CRASH_SEQUENCE": ["One of:; 0: off; 1: complete; 3: reset; 4: pause; 11: start", b'CRASH SEQUENCE', b'Enum', 'N'],
- "CRASH_FLAG": ["One of:; 0: None; 2: Mountain; 4: General; 6: Building; 8: Splash; 10: Gear up; 12: Overstress; 14: Building; 16: Aircraft; 18: Fuel Truck", b'CRASH FLAG', b'Enum', 'N'],
- "TOW_RELEASE_HANDLE": ["Position of tow release handle. 100 is fully deployed.", b'TOW RELEASE HANDLE', b'Percent over 100', 'N'],
- "TOW_CONNECTION": ["True if a towline is connected to both tow plane and glider.", b'TOW CONNECTION', b'Bool', 'N'],
- "APU_PCT_RPM": ["Auxiliary power unit rpm, as a percentage", b'APU PCT RPM', b'Percent over 100', 'N'],
- "APU_PCT_STARTER": ["Auxiliary power unit starter, as a percentage", b'APU PCT STARTER', b'Percent over 100', 'N'],
- "APU_VOLTS": ["Auxiliary power unit voltage", b'APU VOLTS', b'Volts', 'N'],
- "APU_GENERATOR_SWITCH": ["True if APU generator switch on", b'APU GENERATOR SWITCH', b'Bool', 'N'],
- "APU_GENERATOR_ACTIVE": ["True if APU generator active", b'APU GENERATOR ACTIVE', b'Bool', 'N'],
- "APU_ON_FIRE_DETECTED": ["True if APU on fire", b'APU ON FIRE DETECTED', b'Bool', 'N'],
- "PRESSURIZATION_CABIN_ALTITUDE": ["The current altitude of the cabin pressurization..", b'PRESSURIZATION CABIN ALTITUDE', b'Feet', 'N'],
- "PRESSURIZATION_CABIN_ALTITUDE_GOAL": ["The set altitude of the cabin pressurization.", b'PRESSURIZATION CABIN ALTITUDE GOAL', b'Feet', 'N'],
- "PRESSURIZATION_CABIN_ALTITUDE_RATE": ["The rate at which cabin pressurization changes.", b'PRESSURIZATION CABIN ALTITUDE RATE', b'Feet per second', 'N'],
- "PRESSURIZATION_PRESSURE_DIFFERENTIAL": ["The difference in pressure between the set altitude pressurization and the current pressurization.", b'PRESSURIZATION PRESSURE DIFFERENTIAL', b'foot pounds', 'N'],
- "PRESSURIZATION_DUMP_SWITCH": ["True if the cabin pressurization dump switch is on.", b'PRESSURIZATION DUMP SWITCH', b'Bool', 'N'],
- "FIRE_BOTTLE_SWITCH": ["True if the fire bottle switch is on.", b'FIRE BOTTLE SWITCH', b'Bool', 'N'],
- "FIRE_BOTTLE_DISCHARGED": ["True if the fire bottle is discharged.", b'FIRE BOTTLE DISCHARGED', b'Bool', 'N'],
- "CABIN_NO_SMOKING_ALERT_SWITCH": ["True if the No Smoking switch is on.", b'CABIN NO SMOKING ALERT SWITCH', b'Bool', 'Y'],
- "CABIN_SEATBELTS_ALERT_SWITCH": ["True if the Seatbelts switch is on.", b'CABIN SEATBELTS ALERT SWITCH', b'Bool', 'Y'],
- "GPWS_WARNING": ["True if Ground Proximity Warning System installed.", b'GPWS WARNING', b'Bool', 'N'],
- "GPWS_SYSTEM_ACTIVE": ["True if the Ground Proximity Warning System is active", b'GPWS SYSTEM ACTIVE', b'Bool', 'Y'],
- "CRASH_FLAG": ["One of:; 0: None; 2: Mountain; 4: General; 6: Building; 8: Splash; 10: Gear up; 12: Overstress; 14: Building; 16: Aircraft; 18: Fuel Truck", b'CRASH FLAG', b'Enum', 'N'],
- "IS_ALTITUDE_FREEZE_ON": ["True if the altitude of the aircraft is frozen.", b'IS ALTITUDE FREEZE ON', b'Bool', 'N'],
- "IS_ATTITUDE_FREEZE_ON": ["True if the attitude (pitch, bank and heading) of the aircraft is frozen.", b'IS ATTITUDE FREEZE ON', b'Bool', 'N'],
- # found in sdk
- "SLING_HOOK_IN_PICKUP_MODE:index": [" ", b'SLING HOOK IN PICKUP MODE:index', b'Bool', 'N'],
- "AI_TRAFFIC_STATE": [" ", b'AI TRAFFIC STATE', b'String', 'N'],
- "AI_TRAFFIC_ASSIGNED_PARKING": [" ", b'AI TRAFFIC ASSIGNED PARKING', b'String', 'N'],
- "RECIP_ENG_FUEL_TANKS_USED:index": [" ", b'RECIP ENG FUEL TANKS USED:index', b'Mask', 'Y'],
- "TURB_ENG_TANKS_USED:index": [" ", b'TURB ENG TANKS USED:index', b'Mask', 'N'],
- "ENG_TURBINE_TEMPERATURE:index": [" ", b'ENG TURBINE TEMPERATURE:index', b'Celsius', 'N'],
- "ENG_ELECTRICAL_LOAD:index": [" ", b'ENG ELECTRICAL LOAD:index', b'Percent', 'N'],
- "ENG_TRANSMISSION_PRESSURE:index": [" ", b'ENG TRANSMISSION PRESSURE:index', b'PSI', 'N'],
- "ENG_TRANSMISSION_TEMPERATURE:index": [" ", b'ENG TRANSMISSION TEMPERATURE:index', b'Celsius', 'N'],
- "SURFACE_TYPE": [" ", b'SURFACE TYPE', b'Enum', 'N'],
- "COM_STATUS:index": [" ", b'COM STATUS:index', b'Enum', 'N'],
- "NAV_TOFROM:index": [" ", b'NAV TOFROM:index', b'Enum', 'N'],
- "GPS_APPROACH_MODE": [" ", b'GPS APPROACH MODE', b'Enum', 'N'],
- "GPS_APPROACH_WP_TYPE": [" ", b'GPS APPROACH WP TYPE', b'Enum', 'N'],
- "GPS_APPROACH_SEGMENT_TYPE": [" ", b'GPS APPROACH SEGMENT TYPE', b'Enum', 'N'],
- "GPS_APPROACH_APPROACH_TYPE": [" ", b'GPS APPROACH APPROACH TYPE', b'Enum', 'N'],
- "AMBIENT_PRECIP_STATE": [" ", b'AMBIENT PRECIP STATE', b'Mask', 'N'],
- "CATEGORY": [" ", b'CATEGORY', b'String', 'N'],
- "CONCORDE_VISOR_NOSE_HANDLE": [" ", b'CONCORDE VISOR NOSE HANDLE', b'Enum', 'N'],
- "IS_LATITUDE_LONGITUDE_FREEZE_ON": [" ", b'IS LATITUDE LONGITUDE FREEZE ON', b'Bool', 'N'],
- "TIME_OF_DAY": [" ", b'TIME OF DAY', b'Enum', 'N'],
- "SIMULATION_RATE": [" ", b'SIMULATION RATE', b'Number', 'N'],
- "UNITS_OF_MEASURE": [" ", b'UNITS OF MEASURE', b'Enum', 'N'],
- }
+ class __AircraftMiscellaneousData(RequestHelper):
+ list = {
+ "TOTAL_WEIGHT": [
+ "Total weight of the aircraft",
+ b"TOTAL WEIGHT",
+ b"Pounds",
+ "N",
+ ],
+ "MAX_GROSS_WEIGHT": [
+ "Maximum gross weight of the aircaft",
+ b"MAX GROSS WEIGHT",
+ b"Pounds",
+ "N",
+ ],
+ "EMPTY_WEIGHT": [
+ "Empty weight of the aircraft",
+ b"EMPTY WEIGHT",
+ b"Pounds",
+ "N",
+ ],
+ "IS_USER_SIM": [
+ "Is this the user loaded aircraft",
+ b"IS USER SIM",
+ b"Bool",
+ "N",
+ ],
+ "SIM_DISABLED": ["Is sim disabled", b"SIM DISABLED", b"Bool", "Y"],
+ "G_FORCE": ["Current g force", b"G FORCE", b"GForce", "Y"],
+ "ATC_HEAVY": [
+ "Is this aircraft recognized by ATC as heavy",
+ b"ATC HEAVY",
+ b"Bool",
+ "Y",
+ ],
+ "AUTO_COORDINATION": [
+ "Is auto-coordination active",
+ b"AUTO COORDINATION",
+ b"Bool",
+ "Y",
+ ],
+ "REALISM": ["General realism percent", b"REALISM", b"Number", "Y"],
+ "TRUE_AIRSPEED_SELECTED": [
+ "True if True Airspeed has been selected",
+ b"TRUE AIRSPEED SELECTED",
+ b"Bool",
+ "Y",
+ ],
+ "DESIGN_SPEED_VS0": [
+ "Design speed at VS0",
+ b"DESIGN SPEED VS0",
+ b"Feet per second",
+ "N",
+ ],
+ "DESIGN_SPEED_VS1": [
+ "Design speed at VS1",
+ b"DESIGN SPEED VS1",
+ b"Feet per second",
+ "N",
+ ],
+ "DESIGN_SPEED_VC": [
+ "Design speed at VC",
+ b"DESIGN SPEED VC",
+ b"Feet per second",
+ "N",
+ ],
+ "MIN_DRAG_VELOCITY": [
+ "Minimum drag velocity",
+ b"MIN DRAG VELOCITY",
+ b"Feet per second",
+ "N",
+ ],
+ "ESTIMATED_CRUISE_SPEED": [
+ "Estimated cruise speed",
+ b"ESTIMATED CRUISE SPEED",
+ b"Feet per second",
+ "N",
+ ],
+ "CG_PERCENT": [
+ "Longitudinal CG position as a percent of reference chord",
+ b"CG PERCENT",
+ b"Percent over 100",
+ "N",
+ ],
+ "CG_PERCENT_LATERAL": [
+ "Lateral CG position as a percent of reference chord",
+ b"CG PERCENT LATERAL",
+ b"Percent over 100",
+ "N",
+ ],
+ "IS_SLEW_ACTIVE": [
+ "True if slew is active",
+ b"IS SLEW ACTIVE",
+ b"Bool",
+ "Y",
+ ],
+ "IS_SLEW_ALLOWED": [
+ "True if slew is enabled",
+ b"IS SLEW ALLOWED",
+ b"Bool",
+ "Y",
+ ],
+ "ATC_SUGGESTED_MIN_RWY_TAKEOFF": [
+ "Suggested minimum runway length for takeoff. Used by ATC ",
+ b"ATC SUGGESTED MIN RWY TAKEOFF",
+ b"Feet",
+ "N",
+ ],
+ "ATC_SUGGESTED_MIN_RWY_LANDING": [
+ "Suggested minimum runway length for landing. Used by ATC ",
+ b"ATC SUGGESTED MIN RWY LANDING",
+ b"Feet",
+ "N",
+ ],
+ "PAYLOAD_STATION_WEIGHT:index": [
+ "Individual payload station weight",
+ b"PAYLOAD STATION WEIGHT:index",
+ b"Pounds",
+ "Y",
+ ],
+ "PAYLOAD_STATION_COUNT": [
+ "Number of payload stations",
+ b"PAYLOAD STATION COUNT",
+ b"Number",
+ "N",
+ ],
+ "USER_INPUT_ENABLED": [
+ "Is input allowed from the user",
+ b"USER INPUT ENABLED",
+ b"Bool",
+ "Y",
+ ],
+ "TYPICAL_DESCENT_RATE": [
+ "Normal descent rate",
+ b"TYPICAL DESCENT RATE",
+ b"Feet per minute",
+ "N",
+ ],
+ "VISUAL_MODEL_RADIUS": [
+ "Model radius",
+ b"VISUAL MODEL RADIUS",
+ b"Meters",
+ "N",
+ ],
+ "SIGMA_SQRT": ["Sigma sqrt", b"SIGMA SQRT", b"Number", "N"],
+ "DYNAMIC_PRESSURE": [
+ "Dynamic pressure",
+ b"DYNAMIC PRESSURE",
+ b"foot pounds",
+ "N",
+ ],
+ "TOTAL_VELOCITY": [
+ "Velocity regardless of direction. For example, if a helicopter is ascending vertically at 100 fps, getting this variable will return 100.",
+ b"TOTAL VELOCITY",
+ b"Feet per second",
+ "N",
+ ],
+ "AIRSPEED_SELECT_INDICATED_OR_TRUE": [
+ "The airspeed, whether true or indicated airspeed has been selected.",
+ b"AIRSPEED SELECT INDICATED OR TRUE",
+ b"Knots",
+ "N",
+ ],
+ "VARIOMETER_RATE": [
+ "Variometer rate",
+ b"VARIOMETER RATE",
+ b"Feet per second",
+ "N",
+ ],
+ "VARIOMETER_SWITCH": [
+ "True if the variometer switch is on",
+ b"VARIOMETER SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "PRESSURE_ALTITUDE": [
+ "Altitude reading",
+ b"PRESSURE ALTITUDE",
+ b"Meters",
+ "N",
+ ],
+ "MAGNETIC_COMPASS": [
+ "Compass reading",
+ b"MAGNETIC COMPASS",
+ b"Degrees",
+ "N",
+ ],
+ "TURN_INDICATOR_RATE": [
+ "Turn indicator reading",
+ b"TURN INDICATOR RATE",
+ b"Radians per second",
+ "N",
+ ],
+ "TURN_INDICATOR_SWITCH": [
+ "True if turn indicator switch is on",
+ b"TURN INDICATOR SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "YOKE_Y_INDICATOR": [
+ "Yoke position in vertical direction",
+ b"YOKE Y INDICATOR",
+ b"Position",
+ "N",
+ ],
+ "YOKE_X_INDICATOR": [
+ "Yoke position in horizontal direction",
+ b"YOKE X INDICATOR",
+ b"Position",
+ "N",
+ ],
+ "RUDDER_PEDAL_INDICATOR": [
+ "Rudder pedal position",
+ b"RUDDER PEDAL INDICATOR",
+ b"Position",
+ "N",
+ ],
+ "BRAKE_DEPENDENT_HYDRAULIC_PRESSURE": [
+ "Brake dependent hydraulic pressure reading",
+ b"BRAKE DEPENDENT HYDRAULIC PRESSURE",
+ b"foot pounds",
+ "N",
+ ],
+ "PANEL_ANTI_ICE_SWITCH": [
+ "True if panel anti-ice switch is on",
+ b"PANEL ANTI ICE SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "WING_AREA": ["Total wing area", b"WING AREA", b"Square feet", "N"],
+ "WING_SPAN": ["Total wing span", b"WING SPAN", b"Feet", "N"],
+ "BETA_DOT": ["Beta dot", b"BETA DOT", b"Radians per second", "N"],
+ "LINEAR_CL_ALPHA": [
+ "Linear CL alpha",
+ b"LINEAR CL ALPHA",
+ b"Per radian",
+ "N",
+ ],
+ "STALL_ALPHA": ["Stall alpha", b"STALL ALPHA", b"Radians", "N"],
+ "ZERO_LIFT_ALPHA": ["Zero lift alpha", b"ZERO LIFT ALPHA", b"Radians", "N"],
+ "CG_AFT_LIMIT": [
+ "Aft limit of CG",
+ b"CG AFT LIMIT",
+ b"Percent over 100",
+ "N",
+ ],
+ "CG_FWD_LIMIT": [
+ "Forward limit of CG",
+ b"CG FWD LIMIT",
+ b"Percent over 100",
+ "N",
+ ],
+ "CG_MAX_MACH": ["Max mach CG", b"CG MAX MACH", b"Machs", "N"],
+ "CG_MIN_MACH": ["Min mach CG", b"CG MIN MACH", b"Machs", "N"],
+ "PAYLOAD_STATION_NAME": [
+ "Descriptive name for payload station",
+ b"PAYLOAD STATION NAME",
+ b"String",
+ "N",
+ ],
+ "ELEVON_DEFLECTION": [
+ "Elevon deflection",
+ b"ELEVON DEFLECTION",
+ b"Radians",
+ "N",
+ ],
+ "EXIT_TYPE": [
+ "One of:; 0: Main; 1: Cargo; 2: Emergency; 3: Unknown",
+ b"EXIT TYPE",
+ b"Enum",
+ "N",
+ ],
+ "EXIT_POSX": [
+ "Position of exit relative to datum reference point",
+ b"EXIT POSX",
+ b"Feet",
+ "N",
+ ],
+ "EXIT_POSY": [
+ "Position of exit relative to datum reference point",
+ b"EXIT POSY",
+ b"Feet",
+ "N",
+ ],
+ "EXIT_POSZ": [
+ "Position of exit relative to datum reference point",
+ b"EXIT POSZ",
+ b"Feet",
+ "N",
+ ],
+ "DECISION_HEIGHT": [
+ "Design decision height",
+ b"DECISION HEIGHT",
+ b"Feet",
+ "N",
+ ],
+ "DECISION_ALTITUDE_MSL": [
+ "Design decision altitude above mean sea level",
+ b"DECISION ALTITUDE MSL",
+ b"Feet",
+ "N",
+ ],
+ "EMPTY_WEIGHT_PITCH_MOI": [
+ "Empty weight pitch moment of inertia",
+ b"EMPTY WEIGHT PITCH MOI",
+ b"slug feet squared",
+ "N",
+ ],
+ "EMPTY_WEIGHT_ROLL_MOI": [
+ "Empty weight roll moment of inertia",
+ b"EMPTY WEIGHT ROLL MOI",
+ b"slug feet squared",
+ "N",
+ ],
+ "EMPTY_WEIGHT_YAW_MOI": [
+ "Empty weight yaw moment of inertia",
+ b"EMPTY WEIGHT YAW MOI",
+ b"slug feet squared",
+ "N",
+ ],
+ "EMPTY_WEIGHT_CROSS_COUPLED_MOI": [
+ "Empty weigth cross coupled moment of inertia",
+ b"EMPTY WEIGHT CROSS COUPLED MOI",
+ b"slug feet squared",
+ "N",
+ ],
+ "TOTAL_WEIGHT_PITCH_MOI": [
+ "Total weight pitch moment of inertia",
+ b"TOTAL WEIGHT PITCH MOI",
+ b"slug feet squared",
+ "N",
+ ],
+ "TOTAL_WEIGHT_ROLL_MOI": [
+ "Total weight roll moment of inertia",
+ b"TOTAL WEIGHT ROLL MOI",
+ b"slug feet squared",
+ "N",
+ ],
+ "TOTAL_WEIGHT_YAW_MOI": [
+ "Total weight yaw moment of inertia",
+ b"TOTAL WEIGHT YAW MOI",
+ b"slug feet squared",
+ "N",
+ ],
+ "TOTAL_WEIGHT_CROSS_COUPLED_MOI": [
+ "Total weight cross coupled moment of inertia",
+ b"TOTAL WEIGHT CROSS COUPLED MOI",
+ b"slug feet squared",
+ "N",
+ ],
+ "WATER_BALLAST_VALVE": [
+ "True if water ballast valve is available",
+ b"WATER BALLAST VALVE",
+ b"Bool",
+ "N",
+ ],
+ "MAX_RATED_ENGINE_RPM": [
+ "Maximum rated rpm",
+ b"MAX RATED ENGINE RPM",
+ b"Rpm",
+ "N",
+ ],
+ "FULL_THROTTLE_THRUST_TO_WEIGHT_RATIO": [
+ "Full throttle thrust to weight ratio",
+ b"FULL THROTTLE THRUST TO WEIGHT RATIO",
+ b"Number",
+ "N",
+ ],
+ "PROP_AUTO_CRUISE_ACTIVE": [
+ "True if prop auto cruise active",
+ b"PROP AUTO CRUISE ACTIVE",
+ b"Bool",
+ "N",
+ ],
+ "PROP_ROTATION_ANGLE": [
+ "Prop rotation angle",
+ b"PROP ROTATION ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "PROP_BETA_MAX": ["Prop beta max", b"PROP BETA MAX", b"Radians", "N"],
+ "PROP_BETA_MIN": ["Prop beta min", b"PROP BETA MIN", b"Radians", "N"],
+ "PROP_BETA_MIN_REVERSE": [
+ "Prop beta min reverse",
+ b"PROP BETA MIN REVERSE",
+ b"Radians",
+ "N",
+ ],
+ "FUEL_SELECTED_TRANSFER_MODE": [
+ "One of:; -1: off; 0: auto; 1: forward; 2: aft; 3: manual",
+ b"FUEL SELECTED TRANSFER MODE",
+ b"Enum",
+ "N",
+ ],
+ "DROPPABLE_OBJECTS_UI_NAME": [
+ "Descriptive name, used in User Interface dialogs, of a droppable object",
+ b"DROPPABLE OBJECTS UI NAME",
+ b"String",
+ "N",
+ ],
+ "MANUAL_FUEL_PUMP_HANDLE": [
+ "Position of manual fuel pump handle. 100 is fully deployed.",
+ b"MANUAL FUEL PUMP HANDLE",
+ b"Percent over 100",
+ "N",
+ ],
+ "BLEED_AIR_SOURCE_CONTROL": [
+ "One of:; 0: min; 1: auto; 2: off; 3: apu; 4: engines",
+ b"BLEED AIR SOURCE CONTROL",
+ b"Enum",
+ "N",
+ ],
+ "ELECTRICAL_OLD_CHARGING_AMPS": [
+ "Legacy, use ELECTRICAL BATTERY LOAD",
+ b"ELECTRICAL OLD CHARGING AMPS",
+ b"Amps",
+ "N",
+ ],
+ "HYDRAULIC_SWITCH": [
+ "True if hydraulic switch is on",
+ b"HYDRAULIC SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "CONCORDE_VISOR_POSITION_PERCENT": [
+ "0 = up, 1.0 = extended/down",
+ b"CONCORDE VISOR POSITION PERCENT",
+ b"Percent over 100",
+ "N",
+ ],
+ "CONCORDE_NOSE_ANGLE": ["0 = up", b"CONCORDE NOSE ANGLE", b"Radians", "N"],
+ "REALISM_CRASH_WITH_OTHERS": [
+ "True indicates crashing with other aircraft is possible.",
+ b"REALISM CRASH WITH OTHERS",
+ b"Bool",
+ "N",
+ ],
+ "REALISM_CRASH_DETECTION": [
+ "True indicates crash detection is turned on.",
+ b"REALISM CRASH DETECTION",
+ b"Bool",
+ "N",
+ ],
+ "MANUAL_INSTRUMENT_LIGHTS": [
+ "True if instrument lights are set manually",
+ b"MANUAL INSTRUMENT LIGHTS",
+ b"Bool",
+ "N",
+ ],
+ "PITOT_ICE_PCT": [
+ "Amount of pitot ice. 100 is fully iced.",
+ b"PITOT ICE PCT",
+ b"Percent over 100",
+ "N",
+ ],
+ "SEMIBODY_LOADFACTOR_Y": [
+ "Semibody loadfactor x and z are not supported.",
+ b"SEMIBODY LOADFACTOR Y",
+ b"Number",
+ "N",
+ ],
+ "SEMIBODY_LOADFACTOR_YDOT": [
+ "Semibody loadfactory ydot",
+ b"SEMIBODY LOADFACTOR YDOT",
+ b"Per second",
+ "N",
+ ],
+ "RAD_INS_SWITCH": [
+ "True if Rad INS switch on",
+ b"RAD INS SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "SIMULATED_RADIUS": ["Simulated radius", b"SIMULATED RADIUS", b"Feet", "N"],
+ "STRUCTURAL_ICE_PCT": [
+ "Amount of ice on aircraft structure. 100 is fully iced.",
+ b"STRUCTURAL ICE PCT",
+ b"Percent over 100",
+ "N",
+ ],
+ "ARTIFICIAL_GROUND_ELEVATION": [
+ "In case scenery is not loaded for AI planes, this variable can be used to set a default surface elevation.",
+ b"ARTIFICIAL GROUND ELEVATION",
+ b"Feet",
+ "N",
+ ],
+ "SURFACE_INFO_VALID": [
+ "True indicates SURFACE CONDITION is meaningful.",
+ b"SURFACE INFO VALID",
+ b"Bool",
+ "N",
+ ],
+ "SURFACE_CONDITION": [
+ "One of:; 0: Normal; 1: Wet; 2: Icy; 3: Snow",
+ b"SURFACE CONDITION",
+ b"Enum",
+ "N",
+ ],
+ "PUSHBACK_ANGLE": [
+ "Pushback angle (the heading of the tug)",
+ b"PUSHBACK ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "PUSHBACK_CONTACTX": [
+ "The towpoint position, relative to the aircrafts datum reference point.",
+ b"PUSHBACK CONTACTX",
+ b"Feet",
+ "N",
+ ],
+ "PUSHBACK_CONTACTY": [
+ "Pushback contact position in vertical direction",
+ b"PUSHBACK CONTACTY",
+ b"Feet",
+ "N",
+ ],
+ "PUSHBACK_CONTACTZ": [
+ "Pushback contact position in fore/aft direction",
+ b"PUSHBACK CONTACTZ",
+ b"Feet",
+ "N",
+ ],
+ "PUSHBACK_WAIT": [
+ "True if waiting for pushback.",
+ b"PUSHBACK WAIT",
+ b"Bool",
+ "N",
+ ],
+ "YAW_STRING_ANGLE": [
+ "The yaw string angle. Yaw strings are attached to gliders as visible indicators of the yaw angle. An animation of this is not implemented in ESP.",
+ b"YAW STRING ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "YAW_STRING_PCT_EXTENDED": [
+ "Yaw string angle as a percentage",
+ b"YAW STRING PCT EXTENDED",
+ b"Percent over 100",
+ "N",
+ ],
+ "INDUCTOR_COMPASS_PERCENT_DEVIATION": [
+ "Inductor compass deviation reading",
+ b"INDUCTOR COMPASS PERCENT DEVIATION",
+ b"Percent over 100",
+ "N",
+ ],
+ "INDUCTOR_COMPASS_HEADING_REF": [
+ "Inductor compass heading",
+ b"INDUCTOR COMPASS HEADING REF",
+ b"Radians",
+ "N",
+ ],
+ "ANEMOMETER_PCT_RPM": [
+ "Anemometer rpm as a percentage",
+ b"ANEMOMETER PCT RPM",
+ b"Percent over 100",
+ "N",
+ ],
+ "ROTOR_ROTATION_ANGLE": [
+ "Main rotor rotation angle (helicopters only)",
+ b"ROTOR ROTATION ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "DISK_PITCH_ANGLE": [
+ "Main rotor pitch angle (helicopters only)",
+ b"DISK PITCH ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "DISK_BANK_ANGLE": [
+ "Main rotor bank angle (helicopters only)",
+ b"DISK BANK ANGLE",
+ b"Radians",
+ "N",
+ ],
+ "DISK_PITCH_PCT": [
+ "Main rotor pitch percent (helicopters only)",
+ b"DISK PITCH PCT",
+ b"Percent over 100",
+ "N",
+ ],
+ "DISK_BANK_PCT": [
+ "Main rotor bank percent (helicopters only)",
+ b"DISK BANK PCT",
+ b"Percent over 100",
+ "N",
+ ],
+ "DISK_CONING_PCT": [
+ "Main rotor coning percent (helicopters only)",
+ b"DISK CONING PCT",
+ b"Percent over 100",
+ "N",
+ ],
+ # "NAV_VOR_LLAF64": ["Nav VOR latitude, longitude, altitude", b'NAV VOR LLAF64', b'SIMCONNECT_DATA_LATLONALT', 'N'],
+ # "NAV_GS_LLAF64": ["Nav GS latitude, longitude, altitude", b'NAV GS LLAF64', b'SIMCONNECT_DATA_LATLONALT', 'N'],
+ "STATIC_CG_TO_GROUND": [
+ "Static CG to ground",
+ b"STATIC CG TO GROUND",
+ b"Feet",
+ "N",
+ ],
+ "STATIC_PITCH": ["Static pitch", b"STATIC PITCH", b"Radians", "N"],
+ "CRASH_SEQUENCE": [
+ "One of:; 0: off; 1: complete; 3: reset; 4: pause; 11: start",
+ b"CRASH SEQUENCE",
+ b"Enum",
+ "N",
+ ],
+ "CRASH_FLAG": [
+ "One of:; 0: None; 2: Mountain; 4: General; 6: Building; 8: Splash; 10: Gear up; 12: Overstress; 14: Building; 16: Aircraft; 18: Fuel Truck",
+ b"CRASH FLAG",
+ b"Enum",
+ "N",
+ ],
+ "TOW_RELEASE_HANDLE": [
+ "Position of tow release handle. 100 is fully deployed.",
+ b"TOW RELEASE HANDLE",
+ b"Percent over 100",
+ "N",
+ ],
+ "TOW_CONNECTION": [
+ "True if a towline is connected to both tow plane and glider.",
+ b"TOW CONNECTION",
+ b"Bool",
+ "N",
+ ],
+ "APU_PCT_RPM": [
+ "Auxiliary power unit rpm, as a percentage",
+ b"APU PCT RPM",
+ b"Percent over 100",
+ "N",
+ ],
+ "APU_PCT_STARTER": [
+ "Auxiliary power unit starter, as a percentage",
+ b"APU PCT STARTER",
+ b"Percent over 100",
+ "N",
+ ],
+ "APU_VOLTS": ["Auxiliary power unit voltage", b"APU VOLTS", b"Volts", "N"],
+ "APU_GENERATOR_SWITCH": [
+ "True if APU generator switch on",
+ b"APU GENERATOR SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "APU_GENERATOR_ACTIVE": [
+ "True if APU generator active",
+ b"APU GENERATOR ACTIVE",
+ b"Bool",
+ "N",
+ ],
+ "APU_ON_FIRE_DETECTED": [
+ "True if APU on fire",
+ b"APU ON FIRE DETECTED",
+ b"Bool",
+ "N",
+ ],
+ "PRESSURIZATION_CABIN_ALTITUDE": [
+ "The current altitude of the cabin pressurization..",
+ b"PRESSURIZATION CABIN ALTITUDE",
+ b"Feet",
+ "N",
+ ],
+ "PRESSURIZATION_CABIN_ALTITUDE_GOAL": [
+ "The set altitude of the cabin pressurization.",
+ b"PRESSURIZATION CABIN ALTITUDE GOAL",
+ b"Feet",
+ "N",
+ ],
+ "PRESSURIZATION_CABIN_ALTITUDE_RATE": [
+ "The rate at which cabin pressurization changes.",
+ b"PRESSURIZATION CABIN ALTITUDE RATE",
+ b"Feet per second",
+ "N",
+ ],
+ "PRESSURIZATION_PRESSURE_DIFFERENTIAL": [
+ "The difference in pressure between the set altitude pressurization and the current pressurization.",
+ b"PRESSURIZATION PRESSURE DIFFERENTIAL",
+ b"foot pounds",
+ "N",
+ ],
+ "PRESSURIZATION_DUMP_SWITCH": [
+ "True if the cabin pressurization dump switch is on.",
+ b"PRESSURIZATION DUMP SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "FIRE_BOTTLE_SWITCH": [
+ "True if the fire bottle switch is on.",
+ b"FIRE BOTTLE SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "FIRE_BOTTLE_DISCHARGED": [
+ "True if the fire bottle is discharged.",
+ b"FIRE BOTTLE DISCHARGED",
+ b"Bool",
+ "N",
+ ],
+ "CABIN_NO_SMOKING_ALERT_SWITCH": [
+ "True if the No Smoking switch is on.",
+ b"CABIN NO SMOKING ALERT SWITCH",
+ b"Bool",
+ "Y",
+ ],
+ "CABIN_SEATBELTS_ALERT_SWITCH": [
+ "True if the Seatbelts switch is on.",
+ b"CABIN SEATBELTS ALERT SWITCH",
+ b"Bool",
+ "Y",
+ ],
+ "GPWS_WARNING": [
+ "True if Ground Proximity Warning System installed.",
+ b"GPWS WARNING",
+ b"Bool",
+ "N",
+ ],
+ "GPWS_SYSTEM_ACTIVE": [
+ "True if the Ground Proximity Warning System is active",
+ b"GPWS SYSTEM ACTIVE",
+ b"Bool",
+ "Y",
+ ],
+ "IS_ALTITUDE_FREEZE_ON": [
+ "True if the altitude of the aircraft is frozen.",
+ b"IS ALTITUDE FREEZE ON",
+ b"Bool",
+ "N",
+ ],
+ "IS_ATTITUDE_FREEZE_ON": [
+ "True if the attitude (pitch, bank and heading) of the aircraft is frozen.",
+ b"IS ATTITUDE FREEZE ON",
+ b"Bool",
+ "N",
+ ],
+ # found in sdk
+ "SLING_HOOK_IN_PICKUP_MODE:index": [
+ " ",
+ b"SLING HOOK IN PICKUP MODE:index",
+ b"Bool",
+ "N",
+ ],
+ "AI_TRAFFIC_STATE": [" ", b"AI TRAFFIC STATE", b"String", "N"],
+ "AI_TRAFFIC_ASSIGNED_PARKING": [
+ " ",
+ b"AI TRAFFIC ASSIGNED PARKING",
+ b"String",
+ "N",
+ ],
+ "RECIP_ENG_FUEL_TANKS_USED:index": [
+ " ",
+ b"RECIP ENG FUEL TANKS USED:index",
+ b"Mask",
+ "Y",
+ ],
+ "TURB_ENG_TANKS_USED:index": [
+ " ",
+ b"TURB ENG TANKS USED:index",
+ b"Mask",
+ "N",
+ ],
+ "ENG_TURBINE_TEMPERATURE:index": [
+ " ",
+ b"ENG TURBINE TEMPERATURE:index",
+ b"Celsius",
+ "N",
+ ],
+ "ENG_ELECTRICAL_LOAD:index": [
+ " ",
+ b"ENG ELECTRICAL LOAD:index",
+ b"Percent",
+ "N",
+ ],
+ "ENG_TRANSMISSION_PRESSURE:index": [
+ " ",
+ b"ENG TRANSMISSION PRESSURE:index",
+ b"PSI",
+ "N",
+ ],
+ "ENG_TRANSMISSION_TEMPERATURE:index": [
+ " ",
+ b"ENG TRANSMISSION TEMPERATURE:index",
+ b"Celsius",
+ "N",
+ ],
+ "SURFACE_TYPE": [" ", b"SURFACE TYPE", b"Enum", "N"],
+ "COM_STATUS:index": [" ", b"COM STATUS:index", b"Enum", "N"],
+ "NAV_TOFROM:index": [" ", b"NAV TOFROM:index", b"Enum", "N"],
+ "GPS_APPROACH_MODE": [" ", b"GPS APPROACH MODE", b"Enum", "N"],
+ "GPS_APPROACH_WP_TYPE": [" ", b"GPS APPROACH WP TYPE", b"Enum", "N"],
+ "GPS_APPROACH_SEGMENT_TYPE": [
+ " ",
+ b"GPS APPROACH SEGMENT TYPE",
+ b"Enum",
+ "N",
+ ],
+ "GPS_APPROACH_APPROACH_TYPE": [
+ " ",
+ b"GPS APPROACH APPROACH TYPE",
+ b"Enum",
+ "N",
+ ],
+ "AMBIENT_PRECIP_STATE": [" ", b"AMBIENT PRECIP STATE", b"Mask", "N"],
+ "CATEGORY": [" ", b"CATEGORY", b"String", "N"],
+ "CONCORDE_VISOR_NOSE_HANDLE": [
+ " ",
+ b"CONCORDE VISOR NOSE HANDLE",
+ b"Enum",
+ "N",
+ ],
+ "IS_LATITUDE_LONGITUDE_FREEZE_ON": [
+ " ",
+ b"IS LATITUDE LONGITUDE FREEZE ON",
+ b"Bool",
+ "N",
+ ],
+ "TIME_OF_DAY": [" ", b"TIME OF DAY", b"Enum", "N"],
+ "SIMULATION_RATE": [" ", b"SIMULATION RATE", b"Number", "N"],
+ "UNITS_OF_MEASURE": [" ", b"UNITS OF MEASURE", b"Enum", "N"],
+ }
- class __AircraftStringData(RequestHelper):
- list = {
- "ATC_TYPE": ["Type used by ATC", b'ATC TYPE', b'String', 'N'],
- "ATC_MODEL": ["Model used by ATC", b'ATC MODEL', b'String', 'N'],
- "ATC_ID": ["ID used by ATC", b'ATC ID', b'String', 'Y'],
- "ATC_AIRLINE": ["Airline used by ATC", b'ATC AIRLINE', b'String', 'Y'],
- "ATC_FLIGHT_NUMBER": ["Flight Number used by ATC", b'ATC FLIGHT NUMBER', b'String', 'Y'],
- "TITLE": ["Title from aircraft.cfg", b'TITLE', b'String', 'N'],
- "HSI_STATION_IDENT": ["Tuned station identifier", b'HSI STATION IDENT', b'String', 'N'],
- "GPS_WP_PREV_ID": ["ID of previous GPS waypoint", b'GPS WP PREV ID', b'String', 'N'],
- "GPS_WP_NEXT_ID": ["ID of next GPS waypoint", b'GPS WP NEXT ID', b'String', 'N'],
- "GPS_APPROACH_AIRPORT_ID": ["ID of airport", b'GPS APPROACH AIRPORT ID', b'String', 'N'],
- "GPS_APPROACH_APPROACH_ID": ["ID of approach", b'GPS APPROACH APPROACH ID', b'String', 'N'],
- "GPS_APPROACH_TRANSITION_ID": ["ID of approach transition", b'GPS APPROACH TRANSITION ID', b'String', 'N'],
- }
+ class __AircraftStringData(RequestHelper):
+ list = {
+ "ATC_TYPE": ["Type used by ATC", b"ATC TYPE", b"String", "N"],
+ "ATC_MODEL": ["Model used by ATC", b"ATC MODEL", b"String", "N"],
+ "ATC_ID": ["ID used by ATC", b"ATC ID", b"String", "Y"],
+ "ATC_AIRLINE": ["Airline used by ATC", b"ATC AIRLINE", b"String", "Y"],
+ "ATC_FLIGHT_NUMBER": [
+ "Flight Number used by ATC",
+ b"ATC FLIGHT NUMBER",
+ b"String",
+ "Y",
+ ],
+ "TITLE": ["Title from aircraft.cfg", b"TITLE", b"String", "N"],
+ "HSI_STATION_IDENT": [
+ "Tuned station identifier",
+ b"HSI STATION IDENT",
+ b"String",
+ "N",
+ ],
+ "GPS_WP_PREV_ID": [
+ "ID of previous GPS waypoint",
+ b"GPS WP PREV ID",
+ b"String",
+ "N",
+ ],
+ "GPS_WP_NEXT_ID": [
+ "ID of next GPS waypoint",
+ b"GPS WP NEXT ID",
+ b"String",
+ "N",
+ ],
+ "GPS_APPROACH_AIRPORT_ID": [
+ "ID of airport",
+ b"GPS APPROACH AIRPORT ID",
+ b"String",
+ "N",
+ ],
+ "GPS_APPROACH_APPROACH_ID": [
+ "ID of approach",
+ b"GPS APPROACH APPROACH ID",
+ b"String",
+ "N",
+ ],
+ "GPS_APPROACH_TRANSITION_ID": [
+ "ID of approach transition",
+ b"GPS APPROACH TRANSITION ID",
+ b"String",
+ "N",
+ ],
+ }
- class __AIControlledAircraft(RequestHelper):
- list = {
- "AI_DESIRED_SPEED": ["Desired speed of the AI object.", b'AI DESIRED SPEED', b'Knots', 'Y'],
- # "AI_WAYPOINT_LIST": ["List of waypoints that an AI controlled object should follow.", b'AI WAYPOINT LIST', b'SIMCONNECT_DATA_WAYPOINT', 'Y'],
- "AI_CURRENT_WAYPOINT": ["Current waypoint in the list", b'AI CURRENT WAYPOINT', b'Number', 'Y'],
- "AI_DESIRED_HEADING": ["Desired heading of the AI object.", b'AI DESIRED HEADING', b'Degrees', 'Y'],
- "AI_GROUNDTURNTIME": ["Time to make a 90 degree turn.", b'AI GROUNDTURNTIME', b'Seconds', 'Y'],
- "AI_GROUNDCRUISESPEED": ["Cruising speed.", b'AI GROUNDCRUISESPEED', b'Knots', 'Y'],
- "AI_GROUNDTURNSPEED": ["Turning speed.", b'AI GROUNDTURNSPEED', b'Knots', 'Y'],
- "AI_TRAFFIC_ISIFR": ["Request whether this aircraft is IFR or VFR See Note 1.", b'AI TRAFFIC ISIFR', b'Boolean', 'N'],
- "AI_TRAFFIC_CURRENT_AIRPORT": ["ICAO code of current airport. See Note 1.", b'AI TRAFFIC CURRENT AIRPORT', b'String', 'N'],
- "AI_TRAFFIC_ASSIGNED_RUNWAY": ["Assigned runway name (for example: \"32R\"). See Note 1.", b'AI TRAFFIC ASSIGNED RUNWAY', b'String', 'N'],
- "AI_TRAFFIC_FROMAIRPORT": ["ICAO code of the departure airport in the current schedule. See Note 2.", b'AI TRAFFIC FROMAIRPORT', b'String', 'N'],
- "AI_TRAFFIC_TOAIRPORT": ["ICAO code of the destination airport in the current schedule. See Note 2.", b'AI TRAFFIC TOAIRPORT', b'String', 'N'],
- "AI_TRAFFIC_ETD": ["Estimated time of departure for the current schedule entry, given as the number of seconds difference from the current simulation time. This can be negative if ETD is earlier than the current simulation time. See Note 2.", b'AI TRAFFIC ETD', b'Seconds', 'N'],
- "AI_TRAFFIC_ETA": ["Estimated time of arrival for the current schedule entry, given as the number of seconds difference from the current simulated time. This can be negative if ETA is earlier than the current simulated time. See Note 2.", b'AI TRAFFIC ETA', b'Seconds', 'N'],
- }
+ class __AIControlledAircraft(RequestHelper):
+ list = {
+ "AI_DESIRED_SPEED": [
+ "Desired speed of the AI object.",
+ b"AI DESIRED SPEED",
+ b"Knots",
+ "Y",
+ ],
+ # "AI_WAYPOINT_LIST": ["List of waypoints that an AI controlled object should follow.", b'AI WAYPOINT LIST', b'SIMCONNECT_DATA_WAYPOINT', 'Y'],
+ "AI_CURRENT_WAYPOINT": [
+ "Current waypoint in the list",
+ b"AI CURRENT WAYPOINT",
+ b"Number",
+ "Y",
+ ],
+ "AI_DESIRED_HEADING": [
+ "Desired heading of the AI object.",
+ b"AI DESIRED HEADING",
+ b"Degrees",
+ "Y",
+ ],
+ "AI_GROUNDTURNTIME": [
+ "Time to make a 90 degree turn.",
+ b"AI GROUNDTURNTIME",
+ b"Seconds",
+ "Y",
+ ],
+ "AI_GROUNDCRUISESPEED": [
+ "Cruising speed.",
+ b"AI GROUNDCRUISESPEED",
+ b"Knots",
+ "Y",
+ ],
+ "AI_GROUNDTURNSPEED": [
+ "Turning speed.",
+ b"AI GROUNDTURNSPEED",
+ b"Knots",
+ "Y",
+ ],
+ "AI_TRAFFIC_ISIFR": [
+ "Request whether this aircraft is IFR or VFR See Note 1.",
+ b"AI TRAFFIC ISIFR",
+ b"Boolean",
+ "N",
+ ],
+ "AI_TRAFFIC_CURRENT_AIRPORT": [
+ "ICAO code of current airport. See Note 1.",
+ b"AI TRAFFIC CURRENT AIRPORT",
+ b"String",
+ "N",
+ ],
+ "AI_TRAFFIC_ASSIGNED_RUNWAY": [
+ 'Assigned runway name (for example: "32R"). See Note 1.',
+ b"AI TRAFFIC ASSIGNED RUNWAY",
+ b"String",
+ "N",
+ ],
+ "AI_TRAFFIC_FROMAIRPORT": [
+ "ICAO code of the departure airport in the current schedule. See Note 2.",
+ b"AI TRAFFIC FROMAIRPORT",
+ b"String",
+ "N",
+ ],
+ "AI_TRAFFIC_TOAIRPORT": [
+ "ICAO code of the destination airport in the current schedule. See Note 2.",
+ b"AI TRAFFIC TOAIRPORT",
+ b"String",
+ "N",
+ ],
+ "AI_TRAFFIC_ETD": [
+ "Estimated time of departure for the current schedule entry, given as the number of seconds difference from the current simulation time. This can be negative if ETD is earlier than the current simulation time. See Note 2.",
+ b"AI TRAFFIC ETD",
+ b"Seconds",
+ "N",
+ ],
+ "AI_TRAFFIC_ETA": [
+ "Estimated time of arrival for the current schedule entry, given as the number of seconds difference from the current simulated time. This can be negative if ETA is earlier than the current simulated time. See Note 2.",
+ b"AI TRAFFIC ETA",
+ b"Seconds",
+ "N",
+ ],
+ }
- class __CarrierOperations(RequestHelper):
- list = {
- "LAUNCHBAR_POSITION": ["Installed on aircraft before takeoff from a carrier catapult. Note that gear cannot retract with this extended. 100 = fully extended. Refer to the document Notes on Aircraft Systems.", b'LAUNCHBAR POSITION', b'Percent over 100', 'N'],
- "LAUNCHBAR_SWITCH": ["If this is set to True the launch bar switch has been engaged.", b'LAUNCHBAR SWITCH', b'Bool', 'N'],
- "LAUNCHBAR_HELD_EXTENDED": ["This will be True if the launchbar is fully extended, and can be used, for example, to change the color of an instrument light.", b'LAUNCHBAR HELD EXTENDED', b'Bool', 'N'],
- "NUMBER_OF_CATAPULTS": ["Maximum of 4. A model can contain more than 4 catapults, but only the first four will be read and recognized by the simulation.", b'NUMBER OF CATAPULTS', b'Number', 'N'],
- "CATAPULT_STROKE_POSITION:index": ["Catapults are indexed from 1. This value will be 0 before the catapult fires, and then up to 100 as the aircraft is propelled down the catapult. The aircraft may takeoff before the value reaches 100 (depending on the aircraft weight, power applied, and other factors), in which case this value will not be further updated. This value could be used to drive a bogie animation.", b'CATAPULT STROKE POSITION:index', b'Number', 'N'],
- "HOLDBACK_BAR_INSTALLED": ["Holdback bars allow build up of thrust before takeoff from a catapult, and are installed by the deck crew of an aircraft carrier.", b'HOLDBACK BAR INSTALLED', b'Bool', 'N'],
- "BLAST_SHIELD_POSITION:index": ["Indexed from 1, 100 is fully deployed, 0 flat on deck", b'BLAST SHIELD POSITION:index', b'Percent over 100', 'N'],
- "CABLE_CAUGHT_BY_TAILHOOK": ["A number 1 through 4 for the cable number caught by the tailhook. Cable 1 is the one closest to the stern of the carrier. A value of 0 indicates no cable was caught.", b'CABLE CAUGHT BY TAILHOOK', b'Number', 'N'],
- "TAILHOOK_HANDLE": ["True if the tailhook handle is engaged.", b'TAILHOOK HANDLE', b'Bool', 'N'],
- "SURFACE_RELATIVE_GROUND_SPEED": ["The speed of the aircraft relative to the speed of the first surface directly underneath it. Use this to retrieve, for example, an aircraft's taxiing speed while it is moving on a moving carrier. It also applies to airborne aircraft, for example when a helicopter is successfully hovering above a moving ship, this value should be zero. The returned value will be the same as GROUND VELOCITY if the first surface beneath it is not moving.", b'SURFACE RELATIVE GROUND SPEED', b'Feet per second', 'N'],
- }
+ class __CarrierOperations(RequestHelper):
+ list = {
+ "LAUNCHBAR_POSITION": [
+ "Installed on aircraft before takeoff from a carrier catapult. Note that gear cannot retract with this extended. 100 = fully extended. Refer to the document Notes on Aircraft Systems.",
+ b"LAUNCHBAR POSITION",
+ b"Percent over 100",
+ "N",
+ ],
+ "LAUNCHBAR_SWITCH": [
+ "If this is set to True the launch bar switch has been engaged.",
+ b"LAUNCHBAR SWITCH",
+ b"Bool",
+ "N",
+ ],
+ "LAUNCHBAR_HELD_EXTENDED": [
+ "This will be True if the launchbar is fully extended, and can be used, for example, to change the color of an instrument light.",
+ b"LAUNCHBAR HELD EXTENDED",
+ b"Bool",
+ "N",
+ ],
+ "NUMBER_OF_CATAPULTS": [
+ "Maximum of 4. A model can contain more than 4 catapults, but only the first four will be read and recognized by the simulation.",
+ b"NUMBER OF CATAPULTS",
+ b"Number",
+ "N",
+ ],
+ "CATAPULT_STROKE_POSITION:index": [
+ "Catapults are indexed from 1. This value will be 0 before the catapult fires, and then up to 100 as the aircraft is propelled down the catapult. The aircraft may takeoff before the value reaches 100 (depending on the aircraft weight, power applied, and other factors), in which case this value will not be further updated. This value could be used to drive a bogie animation.",
+ b"CATAPULT STROKE POSITION:index",
+ b"Number",
+ "N",
+ ],
+ "HOLDBACK_BAR_INSTALLED": [
+ "Holdback bars allow build up of thrust before takeoff from a catapult, and are installed by the deck crew of an aircraft carrier.",
+ b"HOLDBACK BAR INSTALLED",
+ b"Bool",
+ "N",
+ ],
+ "BLAST_SHIELD_POSITION:index": [
+ "Indexed from 1, 100 is fully deployed, 0 flat on deck",
+ b"BLAST SHIELD POSITION:index",
+ b"Percent over 100",
+ "N",
+ ],
+ "CABLE_CAUGHT_BY_TAILHOOK": [
+ "A number 1 through 4 for the cable number caught by the tailhook. Cable 1 is the one closest to the stern of the carrier. A value of 0 indicates no cable was caught.",
+ b"CABLE CAUGHT BY TAILHOOK",
+ b"Number",
+ "N",
+ ],
+ "TAILHOOK_HANDLE": [
+ "True if the tailhook handle is engaged.",
+ b"TAILHOOK HANDLE",
+ b"Bool",
+ "N",
+ ],
+ "SURFACE_RELATIVE_GROUND_SPEED": [
+ "The speed of the aircraft relative to the speed of the first surface directly underneath it. Use this to retrieve, for example, an aircraft's taxiing speed while it is moving on a moving carrier. It also applies to airborne aircraft, for example when a helicopter is successfully hovering above a moving ship, this value should be zero. The returned value will be the same as GROUND VELOCITY if the first surface beneath it is not moving.",
+ b"SURFACE RELATIVE GROUND SPEED",
+ b"Feet per second",
+ "N",
+ ],
+ }
- class __Racing(RequestHelper):
- list = {
- "RECIP_ENG_DETONATING:index": ["Indexed from 1. Set to True if the engine is detonating.", b'RECIP ENG DETONATING:index', b'Bool', 'N'],
- "RECIP_ENG_CYLINDER_HEALTH:index": ["Index high 16 bits is engine number, low 16 cylinder number, both indexed from 1.", b'RECIP ENG CYLINDER HEALTH:index', b'Percent over 100', 'N'],
- "RECIP_ENG_NUM_CYLINDERS": ["Indexed from 1. The number of engine cylinders.", b'RECIP ENG NUM CYLINDERS', b'Number', 'N'],
- "RECIP_ENG_NUM_CYLINDERS_FAILED": ["Indexed from 1. The number of cylinders that have failed.", b'RECIP ENG NUM CYLINDERS FAILED', b'Number', 'N'],
- "RECIP_ENG_ANTIDETONATION_TANK_VALVE:index": ["Indexed from 1, each engine can have one antidetonation tank. Installed on racing aircraft. Refer to the document Notes on Aircraft Systems.", b'RECIP ENG ANTIDETONATION TANK VALVE:index', b'Bool', 'Y'],
- "RECIP_ENG_ANTIDETONATION_TANK_QUANTITY:index": ["Indexed from 1. Refer to the Mission Creation documentationfor the procedure for refilling tanks.", b'RECIP ENG ANTIDETONATION TANK QUANTITY:index', b'Gallons', 'Y'],
- "RECIP_ENG_ANTIDETONATION_TANK_MAX_QUANTITY:index": ["Indexed from 1. This value set in the Aircraft Configuration File.", b'RECIP ENG ANTIDETONATION TANK MAX QUANTITY:index', b'Gallons', 'N'],
- "RECIP_ENG_NITROUS_TANK_VALVE:index": ["Indexed from 1. Each engine can have one Nitrous fuel tank installed.", b'RECIP ENG NITROUS TANK VALVE:index', b'Bool', 'Y'],
- "RECIP_ENG_NITROUS_TANK_QUANTITY:index": ["Indexed from 1. Refer to the Mission Creation documentationfor the procedure for refilling tanks.", b'RECIP ENG NITROUS TANK QUANTITY:index', b'Gallons', 'Y'],
- "RECIP_ENG_NITROUS_TANK_MAX_QUANTITY:index": ["Indexed from 1. This value set in the Aircraft Configuration File.", b'RECIP ENG NITROUS TANK MAX QUANTITY:index', b'Gallons', 'N'],
- }
+ class __Racing(RequestHelper):
+ list = {
+ "RECIP_ENG_DETONATING:index": [
+ "Indexed from 1. Set to True if the engine is detonating.",
+ b"RECIP ENG DETONATING:index",
+ b"Bool",
+ "N",
+ ],
+ "RECIP_ENG_CYLINDER_HEALTH:index": [
+ "Index high 16 bits is engine number, low 16 cylinder number, both indexed from 1.",
+ b"RECIP ENG CYLINDER HEALTH:index",
+ b"Percent over 100",
+ "N",
+ ],
+ "RECIP_ENG_NUM_CYLINDERS": [
+ "Indexed from 1. The number of engine cylinders.",
+ b"RECIP ENG NUM CYLINDERS",
+ b"Number",
+ "N",
+ ],
+ "RECIP_ENG_NUM_CYLINDERS_FAILED": [
+ "Indexed from 1. The number of cylinders that have failed.",
+ b"RECIP ENG NUM CYLINDERS FAILED",
+ b"Number",
+ "N",
+ ],
+ "RECIP_ENG_ANTIDETONATION_TANK_VALVE:index": [
+ "Indexed from 1, each engine can have one antidetonation tank. Installed on racing aircraft. Refer to the document Notes on Aircraft Systems.",
+ b"RECIP ENG ANTIDETONATION TANK VALVE:index",
+ b"Bool",
+ "Y",
+ ],
+ "RECIP_ENG_ANTIDETONATION_TANK_QUANTITY:index": [
+ "Indexed from 1. Refer to the Mission Creation documentationfor the procedure for refilling tanks.",
+ b"RECIP ENG ANTIDETONATION TANK QUANTITY:index",
+ b"Gallons",
+ "Y",
+ ],
+ "RECIP_ENG_ANTIDETONATION_TANK_MAX_QUANTITY:index": [
+ "Indexed from 1. This value set in the Aircraft Configuration File.",
+ b"RECIP ENG ANTIDETONATION TANK MAX QUANTITY:index",
+ b"Gallons",
+ "N",
+ ],
+ "RECIP_ENG_NITROUS_TANK_VALVE:index": [
+ "Indexed from 1. Each engine can have one Nitrous fuel tank installed.",
+ b"RECIP ENG NITROUS TANK VALVE:index",
+ b"Bool",
+ "Y",
+ ],
+ "RECIP_ENG_NITROUS_TANK_QUANTITY:index": [
+ "Indexed from 1. Refer to the Mission Creation documentationfor the procedure for refilling tanks.",
+ b"RECIP ENG NITROUS TANK QUANTITY:index",
+ b"Gallons",
+ "Y",
+ ],
+ "RECIP_ENG_NITROUS_TANK_MAX_QUANTITY:index": [
+ "Indexed from 1. This value set in the Aircraft Configuration File.",
+ b"RECIP ENG NITROUS TANK MAX QUANTITY:index",
+ b"Gallons",
+ "N",
+ ],
+ }
- class __EnvironmentData(RequestHelper):
- list = {
- "ABSOLUTE_TIME": ["Time, as referenced from 12:00 AM January 1, 0000", b'ABSOLUTE TIME', b'Seconds', 'N'],
- "ZULU_TIME": ["Greenwich Mean Time (GMT)", b'ZULU TIME', b'Seconds', 'N'],
- "ZULU_DAY_OF_WEEK": ["GMT day of week", b'ZULU DAY OF WEEK', b'Number', 'N'],
- "ZULU_DAY_OF_MONTH": ["GMT day of month", b'ZULU DAY OF MONTH', b'Number', 'N'],
- "ZULU_MONTH_OF_YEAR": ["GMT month of year", b'ZULU MONTH OF YEAR', b'Number', 'N'],
- "ZULU_DAY_OF_YEAR": ["GMT day of year", b'ZULU DAY OF YEAR', b'Number', 'N'],
- "ZULU_YEAR": ["GMT year", b'ZULU YEAR', b'Number', 'N'],
- "LOCAL_TIME": ["Local time", b'LOCAL TIME', b'Seconds', 'N'],
- "LOCAL_DAY_OF_WEEK": ["Local day of week", b'LOCAL DAY OF WEEK', b'Number', 'N'],
- "LOCAL_DAY_OF_MONTH": ["Local day of month", b'LOCAL DAY OF MONTH', b'Number', 'N'],
- "LOCAL_MONTH_OF_YEAR": ["Local month of year", b'LOCAL MONTH OF YEAR', b'Number', 'N'],
- "LOCAL_DAY_OF_YEAR": ["Local day of year", b'LOCAL DAY OF YEAR', b'Number', 'N'],
- "LOCAL_YEAR": ["Local year", b'LOCAL YEAR', b'Number', 'N'],
- "TIME_ZONE_OFFSET": ["Local time difference from GMT", b'TIME ZONE OFFSET', b'Seconds', 'N'],
- }
+ class __EnvironmentData(RequestHelper):
+ list = {
+ "ABSOLUTE_TIME": [
+ "Time, as referenced from 12:00 AM January 1, 0000",
+ b"ABSOLUTE TIME",
+ b"Seconds",
+ "N",
+ ],
+ "ZULU_TIME": ["Greenwich Mean Time (GMT)", b"ZULU TIME", b"Seconds", "N"],
+ "ZULU_DAY_OF_WEEK": [
+ "GMT day of week",
+ b"ZULU DAY OF WEEK",
+ b"Number",
+ "N",
+ ],
+ "ZULU_DAY_OF_MONTH": [
+ "GMT day of month",
+ b"ZULU DAY OF MONTH",
+ b"Number",
+ "N",
+ ],
+ "ZULU_MONTH_OF_YEAR": [
+ "GMT month of year",
+ b"ZULU MONTH OF YEAR",
+ b"Number",
+ "N",
+ ],
+ "ZULU_DAY_OF_YEAR": [
+ "GMT day of year",
+ b"ZULU DAY OF YEAR",
+ b"Number",
+ "N",
+ ],
+ "ZULU_YEAR": ["GMT year", b"ZULU YEAR", b"Number", "N"],
+ "LOCAL_TIME": ["Local time", b"LOCAL TIME", b"Seconds", "N"],
+ "LOCAL_DAY_OF_WEEK": [
+ "Local day of week",
+ b"LOCAL DAY OF WEEK",
+ b"Number",
+ "N",
+ ],
+ "LOCAL_DAY_OF_MONTH": [
+ "Local day of month",
+ b"LOCAL DAY OF MONTH",
+ b"Number",
+ "N",
+ ],
+ "LOCAL_MONTH_OF_YEAR": [
+ "Local month of year",
+ b"LOCAL MONTH OF YEAR",
+ b"Number",
+ "N",
+ ],
+ "LOCAL_DAY_OF_YEAR": [
+ "Local day of year",
+ b"LOCAL DAY OF YEAR",
+ b"Number",
+ "N",
+ ],
+ "LOCAL_YEAR": ["Local year", b"LOCAL YEAR", b"Number", "N"],
+ "TIME_ZONE_OFFSET": [
+ "Local time difference from GMT",
+ b"TIME ZONE OFFSET",
+ b"Seconds",
+ "N",
+ ],
+ }
diff --git a/action_plugins/map_to_simconnect/SimConnect/SimConnect.py b/action_plugins/map_to_simconnect/SimConnect/SimConnect.py
index c824f6c4..5785552d 100644
--- a/action_plugins/map_to_simconnect/SimConnect/SimConnect.py
+++ b/action_plugins/map_to_simconnect/SimConnect/SimConnect.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -36,1484 +36,1514 @@
from gremlin.singleton_decorator import SingletonDecorator
-_library_path = os.path.splitext(os.path.abspath(__file__))[0] + '.dll'
+_library_path = os.path.splitext(os.path.abspath(__file__))[0] + ".dll"
syslog = logging.getLogger("system")
def millis():
- return int(round(time.time() * 1000))
+ return int(round(time.time() * 1000))
class Request(object):
-
-
- def __init__(self, definitions, sm, time=10, dec=None, settable=False, attempts=10, callback = None, is_client_data = False):
- ''' request
-
- :param deff: definitions (command : str, datatype : SIMCONNECT_DATATYPE)
- :callback : callback to call when the value of this request is set by the simulator
-
- '''
- self.is_client_data = is_client_data
- self.DATA_DEFINITION_ID = None
- self.CLIENT_DATA_ID = None
- self.definitions = []
- self.description = dec
- self._name = None
- if definitions:
- self.definitions.append(definitions)
- self.buffer = None
- self.attempts = attempts
- self._sm : SimConnect = sm
- self.time = time
- self.defined = False
- self.settable = settable
- self.LastData = 0
- self.LastID = 0
- self._callback = callback # callback to call when data changes
- if ':index' in str(self.definitions[0][0]):
- self.lastIndex = b':index'
-
- @property
- def addCommand(self, command, datatype):
- self.definitions.append((command, datatype))
-
- @property
- def callback(self):
- return self._callback
-
- @callback.setter
- def callback(self, value):
- self._callback = value
-
- def get(self):
- return self.value
-
- def set(self, _value):
- self.value = _value
-
- @property
- def value(self):
- if self._ensure_def():
- if (self.LastData + self.time) < millis():
- if self._sm.get_data(self):
- self.LastData = millis()
- else:
- return None
- return self.buffer
- else:
- return None
-
- @value.setter
- def value(self, val):
- if self._ensure_def():
- if not self.settable:
- syslog.warning(f"Value is not settable: {self.definitions} {self.description}")
- return
- self.buffer = val
-
- def transmit(self):
- ''' sends the data to simconnect '''
- if self._ensure_def():
- self._sm.set_data(self)
-
- def setIndex(self, index):
- if not hasattr(self, "lastIndex"):
- return False
- (dec, stype) = self.definitions[0]
- newindex = str(":" + str(index)).encode()
- if newindex == self.lastIndex:
- return
- dec = dec.replace(self.lastIndex, newindex)
- self.lastIndex = newindex
- self.definitions[0] = (dec, stype)
- self.redefine()
- return True
-
- def redefine(self):
- if self.DATA_DEFINITION_ID is not None:
- self._sm._dll.ClearDataDefinition(
- self._sm._hSimConnect,
- self.DATA_DEFINITION_ID.value,
- )
- self.defined = False
- # self.sm.run()
- if self._ensure_def():
- # self.sm.run()
- self._sm.get_data(self)
-
- def Register(self):
- ''' ensures the request is registered with SimConnect '''
- self._ensure_def()
-
-
- def _ensure_def(self):
- if not self._sm.ok:
- # auto connect
- self._sm.connect()
- if not self._sm.ok:
- return False
-
- if self.defined is True:
- return True
-
- # syslog = logging.getLogger("system")
- verbose = gremlin.config.Configuration().verbose_mode_simconnect
-
- data_type = SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_FLOAT64
- if ':index' in str(self.definitions[0][0]):
- self.lastIndex = b':index'
- return False
-
-
- rtype = self.definitions[0][1]
- s_rtype = rtype.decode()
-
- if s_rtype.casefold() == 'string':
- rtype = None
- data_type = SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_STRINGV
-
- command = self.definitions[0][0]
-
- if self.is_client_data and self.CLIENT_DATA_ID is None:
- self.CLIENT_DATA_ID = self._sm.new_client_data_id()
-
- if self.DATA_DEFINITION_ID is None:
- self.DATA_DEFINITION_ID = self._sm.new_def_id()
- self.DATA_REQUEST_ID = self._sm.new_request_id()
- self.buffer = None
- self._sm.Requests[self.DATA_REQUEST_ID.value] = self
-
- if self.is_client_data:
- # string definition
- err = self._sm._dll.AddToClientDataDefinition(
- self._sm._hSimConnect,
- self.DATA_DEFINITION_ID.value,
- 0, # offset
- 4096, # buffer size
- 0, # epsilon
- SIMCONNECT_UNUSED, # datum
- )
-
- else:
- # floating point definition
- err = self._sm._dll.AddToDataDefinition(
- self._sm._hSimConnect,
- self.DATA_DEFINITION_ID.value,
- command,
- rtype,
- data_type,
- 0,
- SIMCONNECT_UNUSED,
- )
- if self._sm.IsHR(err, 0):
- self.defined = True
- temp = DWORD(0)
- self._sm._dll.GetLastSentPacketID(self._sm._hSimConnect, temp)
- self.LastID = temp.value
- if verbose: syslog.info(f"SIMCONNECT: created request defintion OK: {command} definition ID: {self.DATA_DEFINITION_ID.value}")
- return True
- else:
- syslog.error(f"SIMCONNECT:request defintion error: {command} ")
- return False
-
-
-
-
+ def __init__(
+ self,
+ definitions,
+ sm,
+ time=10,
+ dec=None,
+ settable=False,
+ attempts=10,
+ callback=None,
+ is_client_data=False,
+ ):
+ """request
+
+ :param deff: definitions (command : str, datatype : SIMCONNECT_DATATYPE)
+ :callback : callback to call when the value of this request is set by the simulator
+
+ """
+ self.is_client_data = is_client_data
+ self.DATA_DEFINITION_ID = None
+ self.CLIENT_DATA_ID = None
+ self.definitions = []
+ self.description = dec
+ self._name = None
+ if definitions:
+ self.definitions.append(definitions)
+ self.buffer = None
+ self.attempts = attempts
+ self._sm: SimConnect = sm
+ self.time = time
+ self.defined = False
+ self.settable = settable
+ self.LastData = 0
+ self.LastID = 0
+ self._callback = callback # callback to call when data changes
+ if ":index" in str(self.definitions[0][0]):
+ self.lastIndex = b":index"
+
+ @property
+ def addCommand(self, command, datatype):
+ self.definitions.append((command, datatype))
+
+ @property
+ def callback(self):
+ return self._callback
+
+ @callback.setter
+ def callback(self, value):
+ self._callback = value
+
+ def get(self):
+ return self.value
+
+ def set(self, _value):
+ self.value = _value
+
+ @property
+ def value(self):
+ if self._ensure_def():
+ if (self.LastData + self.time) < millis():
+ if self._sm.get_data(self):
+ self.LastData = millis()
+ else:
+ return None
+ return self.buffer
+ else:
+ return None
+
+ @value.setter
+ def value(self, val):
+ if self._ensure_def():
+ if not self.settable:
+ syslog.warning(
+ f"Value is not settable: {self.definitions} {self.description}"
+ )
+ return
+ self.buffer = val
+
+ def transmit(self):
+ """sends the data to simconnect"""
+ if self._ensure_def():
+ self._sm.set_data(self)
+
+ def setIndex(self, index):
+ if not hasattr(self, "lastIndex"):
+ return False
+ (dec, stype) = self.definitions[0]
+ newindex = str(":" + str(index)).encode()
+ if newindex == self.lastIndex:
+ return
+ dec = dec.replace(self.lastIndex, newindex)
+ self.lastIndex = newindex
+ self.definitions[0] = (dec, stype)
+ self.redefine()
+ return True
+
+ def redefine(self):
+ if self.DATA_DEFINITION_ID is not None:
+ self._sm._dll.ClearDataDefinition(
+ self._sm._hSimConnect,
+ self.DATA_DEFINITION_ID.value,
+ )
+ self.defined = False
+ # self.sm.run()
+ if self._ensure_def():
+ # self.sm.run()
+ self._sm.get_data(self)
+
+ def Register(self):
+ """ensures the request is registered with SimConnect"""
+ self._ensure_def()
+
+ def _ensure_def(self):
+ if not self._sm.ok:
+ # auto connect
+ self._sm.connect()
+ if not self._sm.ok:
+ return False
+
+ if self.defined is True:
+ return True
+
+ # syslog = logging.getLogger("system")
+ verbose = gremlin.config.Configuration().verbose_mode_simconnect
+
+ data_type = SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_FLOAT64
+ if ":index" in str(self.definitions[0][0]):
+ self.lastIndex = b":index"
+ return False
+
+ rtype = self.definitions[0][1]
+ s_rtype = rtype.decode()
+
+ if s_rtype.casefold() == "string":
+ rtype = None
+ data_type = SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_STRINGV
+
+ command = self.definitions[0][0]
+
+ if self.is_client_data and self.CLIENT_DATA_ID is None:
+ self.CLIENT_DATA_ID = self._sm.new_client_data_id()
+
+ if self.DATA_DEFINITION_ID is None:
+ self.DATA_DEFINITION_ID = self._sm.new_def_id()
+ self.DATA_REQUEST_ID = self._sm.new_request_id()
+ self.buffer = None
+ self._sm.Requests[self.DATA_REQUEST_ID.value] = self
+
+ if self.is_client_data:
+ # string definition
+ err = self._sm._dll.AddToClientDataDefinition(
+ self._sm._hSimConnect,
+ self.DATA_DEFINITION_ID.value,
+ 0, # offset
+ 4096, # buffer size
+ 0, # epsilon
+ SIMCONNECT_UNUSED, # datum
+ )
+
+ else:
+ # floating point definition
+ err = self._sm._dll.AddToDataDefinition(
+ self._sm._hSimConnect,
+ self.DATA_DEFINITION_ID.value,
+ command,
+ rtype,
+ data_type,
+ 0,
+ SIMCONNECT_UNUSED,
+ )
+ if self._sm.IsHR(err, 0):
+ self.defined = True
+ temp = DWORD(0)
+ self._sm._dll.GetLastSentPacketID(self._sm._hSimConnect, temp)
+ self.LastID = temp.value
+ if verbose:
+ syslog.info(
+ f"SIMCONNECT: created request defintion OK: {command} definition ID: {self.DATA_DEFINITION_ID.value}"
+ )
+ return True
+ else:
+ syslog.error(f"SIMCONNECT:request defintion error: {command} ")
+ return False
class RequestHelper:
- def __init__(self, sm, time=10, attempts=10, on_change = False):
- self.sm = sm
- self.dic = []
- self.time = time
- self.attempts = attempts
- self.on_change = on_change # if set, ask to trigger whenever the data changes
-
- def __getattr__(self, _name):
- if _name in self.list:
- key = self.list.get(_name)
- setable = False
- if key[3] == 'Y':
- setable = True
- ne = Request((key[1], key[2]), self.sm, dec=key[0], settable=setable, time=self.time, attempts=self.attempts)
- setattr(self, _name, ne)
- return ne
- return None
-
- def get(self, _name):
- if getattr(self, _name) is None:
- return None
- return getattr(self, _name).value
-
- def set(self, _name, _value=0):
- temp = getattr(self, _name)
- if temp is None:
- return False
- if not getattr(temp, "settable"):
- return False
-
- setattr(temp, "value", _value)
- return True
-
- def json(self):
- map = {}
- for att in self.list:
- val = self.get(att)
- if val is not None:
- try:
- map[att] = val.value
- except AttributeError:
- map[att] = val
- return map
-
-
-
-class RangeEvent():
+ def __init__(self, sm, time=10, attempts=10, on_change=False):
+ self.sm = sm
+ self.dic = []
+ self.time = time
+ self.attempts = attempts
+ self.on_change = on_change # if set, ask to trigger whenever the data changes
+
+ def __getattr__(self, _name):
+ if _name in self.list:
+ key = self.list.get(_name)
+ setable = False
+ if key[3] == "Y":
+ setable = True
+ ne = Request(
+ (key[1], key[2]),
+ self.sm,
+ dec=key[0],
+ settable=setable,
+ time=self.time,
+ attempts=self.attempts,
+ )
+ setattr(self, _name, ne)
+ return ne
+ return None
+
+ def get(self, _name):
+ if getattr(self, _name) is None:
+ return None
+ return getattr(self, _name).value
+
+ def set(self, _name, _value=0):
+ temp = getattr(self, _name)
+ if temp is None:
+ return False
+ if not getattr(temp, "settable"):
+ return False
+
+ setattr(temp, "value", _value)
+ return True
+
+ def json(self):
+ map = {}
+ for att in self.list:
+ val = self.get(att)
+ if val is not None:
+ try:
+ map[att] = val.value
+ except AttributeError:
+ map[att] = val
+ return map
+
+
+class RangeEvent:
def __init__(self):
-
self.min = 0
self.min_custom = 0
self.max = 0
self.max_custom = 0
+
class SimConnectEvent:
- ''' holds event data for the simconnect_event signal '''
- def __init__(self, command: str = None, value = None):
+ """holds event data for the simconnect_event signal"""
+
+ def __init__(self, command: str = None, value=None):
self.command = command
self.value = value
-
@SingletonDecorator
class SimConnectEventHandler(QtCore.QObject):
- ''' handles events related to simconnect '''
- range_changed = QtCore.Signal(object, RangeEvent) # fires when the block range values change (block, event)
- value_changed = QtCore.Signal(object) # fires when the block output value changes (block)
- request_connect = QtCore.Signal() # request simconnect to connect
- request_disconnect = QtCore.Signal() # request simconnect to disconnect
- simconnect_connected = QtCore.Signal() # sim connected
- simconnect_disconnected = QtCore.Signal() # sim disconnected
- simconnect_sim_start = QtCore.Signal() # sim started
- simconnect_sim_stop = QtCore.Signal() # sim stopped
- simconnect_sim_paused = QtCore.Signal(bool) # sim pause (state)
- simconnect_sim_running = QtCore.Signal(bool) # sim running (state)
- simconnect_aircraft_loaded = QtCore.Signal(str, str) # sim aircraft loaded (folder, name)
- simconnect_event = QtCore.Signal(SimConnectEvent) # fires when we get a Simconnect data value notice
- simconnect_state_changed = QtCore.Signal(int, float, str) # state change data (int, float, str)
- status_callback_clicked = QtCore.Signal() # fires when the status button is clicked
- simconnect_AircraftLiveriesReceived = QtCore.Signal(object) # fires when a list of user flyable aircraft is received (AicraftLiveries dict keyed by sim name)
- simconnect_FacilitiesReceived = QtCore.Signal(list) # fires when a list of facilities is received
- AircraftDefinitionsChanged = QtCore.Signal() # indicates the aircraft definitions have been updated in options
+ """handles events related to simconnect"""
+
+ range_changed = QtCore.Signal(
+ object, RangeEvent
+ ) # fires when the block range values change (block, event)
+ value_changed = QtCore.Signal(
+ object
+ ) # fires when the block output value changes (block)
+ request_connect = QtCore.Signal() # request simconnect to connect
+ request_disconnect = QtCore.Signal() # request simconnect to disconnect
+ simconnect_connected = QtCore.Signal() # sim connected
+ simconnect_disconnected = QtCore.Signal() # sim disconnected
+ simconnect_sim_start = QtCore.Signal() # sim started
+ simconnect_sim_stop = QtCore.Signal() # sim stopped
+ simconnect_sim_paused = QtCore.Signal(bool) # sim pause (state)
+ simconnect_sim_running = QtCore.Signal(bool) # sim running (state)
+ simconnect_aircraft_loaded = QtCore.Signal(
+ str, str
+ ) # sim aircraft loaded (folder, name)
+ simconnect_event = QtCore.Signal(
+ SimConnectEvent
+ ) # fires when we get a Simconnect data value notice
+ simconnect_state_changed = QtCore.Signal(
+ int, float, str
+ ) # state change data (int, float, str)
+ status_callback_clicked = QtCore.Signal() # fires when the status button is clicked
+ simconnect_AircraftLiveriesReceived = QtCore.Signal(
+ object
+ ) # fires when a list of user flyable aircraft is received (AicraftLiveries dict keyed by sim name)
+ simconnect_FacilitiesReceived = QtCore.Signal(
+ list
+ ) # fires when a list of facilities is received
+ AircraftDefinitionsChanged = (
+ QtCore.Signal()
+ ) # indicates the aircraft definitions have been updated in options
@SingletonDecorator
-class SimConnect():
- ''' MSFS simconnect interface '''
-
-
-
- def __init__(self, handler : SimConnectEventHandler, auto_connect=True):
- ''' initializes sim connect
-
- :param handler: SimConnectEventHandler - the handler to signals
-
- '''
- super().__init__()
-
- self.Requests = {}
- self.Facilities = []
- self.AicraftLiveries = {} # data returned for user flyable aicraft livery pairs [sim_name] = list[str] (liveries)
- self.verbose = gremlin.config.Configuration().verbose_mode_simconnect
- self.verbose_details = False
- self._connecting = False # true if connecting
- self._hSimConnect = HANDLE()
- self._quit = 0
- self._ok = False
- self._request_abort = False # true if we're aborting and should stop running
- self.running = False
- self._is_loop_running = False # true if the DLL event listen loop is running
- self._is_connected = False # true if the DLL is hooked
- self._request_busy = False
- self.paused = False
- self.DEFINITION_POS = None
- self.DEFINITION_WAYPOINT = None
- self._my_dispatch_proc_rd = None
- self._aircraft_loaded_event = threading.Event() # locks the aircraft process thread in case we get multiple concurrent calls
- self._last_loaded_aircraft = None
- self._last_loaded_aircraft_cfg = None
-
- self.handler : SimConnectEventHandler = handler # used to trigger various events
- self.client_data_handlers = [] # client data handlers - for when client data is received
-
- self._win_dll = None # reference to the loaded DLL
- self._dll = None # simconnect class
- self._dll_path = None # path to the dll located in the distribution
- self._runThread = None
- self._library_path = None
- self._critical = False
- self._state_handled = {} # tracks state change messages
-
- def reset(self):
- ''' resets abort flag set due to a load error - this is necessary upon reconnect'''
- self._request_abort = False
- self._state_handled = {}
-
-
-
- @QtCore.Slot()
- def _shutdown(self):
- ''' called when the app is shutting down '''
- # syslog = logging.getLogger("system")
- if self._dll:
- try:
- syslog.info("SIMCONNECT: shutdown ")
- self._dll.close()
- except:
- pass
- self._dll = None
- if self._win_dll:
- self._win_dll = None
-
-
- @property
- def ok(self) -> bool:
- return self._ok and self._dll is not None
-
- @property
- def handle(self):
- return self._hSimConnect
-
- @property
- def last_loaded_aircraft(self) -> str:
- ''' gets the name of the last loaded aircraft, None if not set'''
- return self._last_loaded_aircraft
-
- @property
- def last_loaded_aircraft_cfg(self) -> str:
- ''' gets the folder of the last loaded aircraft, None if not set'''
- return self._last_loaded_aircraft_cfg
-
-
- def register_client_data_handler(self, handler):
- ''' registers a client data area handler '''
- if not handler in self.client_data_handlers:
- verbose = gremlin.config.Configuration().verbose_mode_simconnect
- if verbose:
- # syslog = logging.getLogger("system")
- syslog.info("SIMCONNECT: register new client data handler")
- self.client_data_handlers.append(handler)
-
-
- def unregister_client_data_handler(self, handler):
- ''' unregisters a client data area handler '''
- if handler in self.client_data_handlers:
- verbose = gremlin.config.Configuration().verbose_mode_simconnect
- if verbose:
- # syslog = logging.getLogger("system")
- syslog.info("SIMCONNECT: unregister new client data handler")
- self.client_data_handlers.remove(handler)
-
-
-
- @QtCore.Slot()
- def _connected(self):
- # mark completed
- self._request_busy = False
- # syslog = logging.getLogger("system")
- syslog.info(f"SIMCONNECT: connect request completed")
-
- @QtCore.Slot()
- def _disconnected(self):
- # marke completed
- self._request_busy = False
- # syslog = logging.getLogger("system")
- syslog.info(f"SIMCONNECT: disconnect request completed")
-
-
- @QtCore.Slot()
- def _connect_request(self):
- ''' connection request received '''
- if not self._request_busy:
- self._request_busy = True
- if not self._is_connected:
- try:
- self.connect()
- except:
- # syslog = logging.getLogger("system")
- syslog.warning("SIMCONNECT: simulator not running yet - unable to connect.")
- return
-
- while not self._is_loop_running:
- time.sleep(0.1)
- else:
- # already running
- self._request_busy = False
-
-
-
-
- @QtCore.Slot()
- def _disconnect_request(self):
- ''' connection disconnect received '''
- if not self._request_busy:
- self._request_busy = True
- if self._is_loop_running:
- self.exit()
- else:
- # not running
- self._request_busy = False
-
-
- # events fired by SimConnect
-
- def IsHR(self, hr, value):
- _hr = ctypes.HRESULT(hr)
- return ctypes.c_ulong(_hr.value).value == value
-
- def handle_id_event(self, event : SIMCONNECT_RECV_EVENT):
- # syslog = logging.getLogger("system")
- uEventID = event.uEventID
- if uEventID == self._dll.EventID.EVENT_SIM_START.value:
- self.running = True
- self.handler.simconnect_sim_start.emit()
- syslog.info("SIMCONNECT: event: SIM START")
- elif uEventID == self._dll.EventID.EVENT_SIM_REQUEST_AIRCRAFT.value:
- # aircraft request
- pass
- elif uEventID == self._dll.EventID.EVENT_SIM_STOP.value:
- if self.verbose:
- syslog.info("SIMCONNECT:event: SIM Stop")
- self.running = False
- self.handler.simconnect_sim_stop.emit()
- elif uEventID == self._dll.EventID.EVENT_SIM_PAUSE_STATE.value:
- paused = event.dwData == 1
- if self.verbose:
- syslog.info(f"SIMCONNECT: event: SIM Pause State {paused}")
- self.paused = paused
- self.handler.simconnect_sim_paused.emit(paused)
- # elif uEventID == self._dll.EventID.EVENT_SIM_PAUSED.value:
- # self.handler.simconnect_sim_paused.emit()
- # if self.verbose:
- # syslog.info("SIMCONNECT:event: SIM Paused")
- # self.paused = True
- # self.handler.simconnect_sim_paused.emit(self.paused)
- # elif uEventID == self._dll.EventID.EVENT_SIM_UNPAUSED.value:
- # self.handler.simconnect_sim_unpaused.emit()
- # if self.verbose:
- # syslog.info("SIMCONNECT:event: SIM Unpaused")
- # self.paused = False
- # self.handler.simconnect_sim_paused.emit(self.paused)
- elif uEventID == self._dll.EventID.EVENT_SIM_RUNNING.value:
- if self.verbose:
- syslog.info("SIMCONNECT: event: SIM Running")
- state = event.dwData != 0
- self.running = state
- self.handler.simconnect_sim_running.emit(state)
-
- elif uEventID == self._dll.EventID.EVENT_SIM_AIRCRAFT_LOADED.value:
- aircraft_cfg = event.dwData # air file loaded
- if self.verbose:
- syslog.info(f"SIMCONNECT: event: AIRCRAFT LOADED: {aircraft_cfg}")
- self.handle_folder_event(aircraft_cfg.decode())
-
- # else:
- # syslog.error(f"SIMCONNECT:received event {uEventID} - don't know how to handle")
-
-
-
- def _process_aircraft_string(self, aircraft_cfg):
- ''' processes an aircraft string - could be a load event or a folder event '''
- # syslog = logging.getLogger("system")
- verbose = gremlin.config.Configuration().verbose_mode_simconnect
- name = self._read_aicraft_config(aircraft_cfg)
- if verbose: syslog.info(f"SIMCONNECT: aircraft string: {name}")
- if name:
- self.handler.simconnect_aircraft_loaded.emit(aircraft_cfg, name)
- self._last_loaded_aircraft = name
- self._last_loaded_aircraft_cfg = aircraft_cfg
- self._aircraft_loaded_event.clear()
-
-
-
- def _read_aicraft_config(self, aircraft_cfg):
- ''' reads a configuration folder and extracts a configuration object '''
- import re
- # syslog = logging.getLogger("system")
- verbose = gremlin.config.Configuration().verbose_mode_simconnect
- if verbose: syslog.info(f"SIMCONNECT:parsing: {aircraft_cfg}")
-
- if not aircraft_cfg:
- syslog.error(f"SIMCONNECT:error - invalid aircraft string: {aircraft_cfg}")
- return
-
- # Example 1: SimObjects\Airplanes\a400m\presets\inibuilds\a400m_cargo\config\aircraft.CFG
- # Example 2: c:\users\XXXX\appdata\local\packages\microsoft.limitless_8wekyb3d8bbwe\localstate\streamedpackages\asobo-aircraft-h125\content\simobjects\airplanes\asobo_h125\presets\asobo\h125_rescue\config
- # Example 3: c:\users\XXXX\appdata\local\packages\microsoft.limitless_8wekyb3d8bbwe\localcache\packages\community\bksq-aircraft-turbineduke\simobjects\airplanes\bksq-aircraft-turbineduke
-
- work_cfg = aircraft_cfg.replace("/", os.sep).casefold()
- splits = work_cfg.split(os.sep)
- max_index = len(splits)
- index = 0
- if "airplanes" in splits:
- index = splits.index("airplanes")
- index +=1
-
- # while splits[index] != "simobjects" and index < max_index:
- # index+=1
- # index+=1
- # if index < max_index:
- # while splits[index] != "airplanes" and index < max_index:
- # index+=1
- # index+=1
- if index >= 0 and index < max_index:
- aircraft_name = splits[index]
- if verbose: syslog.info(f"SIMCONNECT:found {aircraft_name}")
- return aircraft_name
-
-
- return None
-
- def handle_simobject_event(self, ObjData):
-
- dwRequestID = ObjData.dwRequestID
- if dwRequestID in self.Requests:
- _request = self.Requests[dwRequestID]
- rtype = _request.definitions[0][1].decode()
- if 'string' in rtype.lower():
- pS = cast(ObjData.dwData, c_char_p)
- _request.buffer = pS.value
- else:
- _request.buffer = cast(
- ObjData.dwData, POINTER(c_double * len(_request.definitions))
- ).contents[0]
-
- if _request.callback is not None:
- ''' run the request callback '''
- _request.callback()
-
- return True
-
- # not one of ours
- return False
- # # syslog = logging.getLogger("system")
- # syslog.warning(f"SIMCONNECT:Event ID: {dwRequestID} is not handled")
-
-
-
-
- def handle_clientdata_event(self, pData):
- ''' handles client data receipt '''
- # pObjData = cast(pData, POINTER(SIMCONNECT_RECV_CLIENT_DATA)).contents
- # if not self.handle_simobject_event(pObjData):
- # # handle non requests
- for handle in self.client_data_handlers:
- handle(pData)
-
-
-
-
-
- def handle_exception_event(self, exc):
- _exception = SIMCONNECT_EXCEPTION(exc.dwException).name
- _unsendid = exc.UNKNOWN_SENDID
- # _sendid = exc.dwSendID
- # _unindex = exc.UNKNOWN_INDEX
- # _index = exc.dwIndex
-
- # request exceptions
- # syslog = logging.getLogger("system")
- for _reqin in self.Requests:
- _request = self.Requests[_reqin]
- if _request.LastID == _unsendid:
-
- syslog.warning(f"SIMCONNECT:error: {_exception} {_request.definitions[0]}")
- return
-
- syslog.warning(_exception)
-
- def handle_state_event(self, pData : SIMCONNECT_RECV_SYSTEM_STATE):
- ''' handles simconnect state changes - this receives the aicraft data '''
- int_data = pData.dwInteger
- float_data = pData.fFloat
- str_data = pData.szString
- key = (int_data, float_data, str_data)
- if not key in self._state_handled:
-
- if self.verbose:
- # syslog = logging.getLogger("system")
- syslog.info(f"SIMCONNECT:state event: int: {pData.dwInteger} float: {pData.fFloat} str: {pData.szString}")
-
- if str_data:
- aircraft_cfg = str_data.decode()
- if not self._aircraft_loaded_event.is_set():
- self._aircraft_loaded_event.set()
- self._process_aircraft_string(aircraft_cfg)
- self.handler.simconnect_state_changed.emit(int_data, float_data, str_data)
- self._state_handled[key] = True
-
-
-
- def handle_folder_event(self, aircraft_cfg):
- ''' folder received '''
- if not self._aircraft_loaded_event.is_set():
- self._aircraft_loaded_event.set()
- thread = threading.Thread(target = self._process_aircraft_string, args = [aircraft_cfg], daemon=True)
- thread.setName("process aircraft file thread")
- thread.start()
-
-
-
- # TODO: update callbackfunction to expand functions.
- def simconnect_dispatch_proc(self, pData, cbData, pContext):
- # print("my_dispatch_proc")
- verbose = gremlin.config.Configuration().verbose_mode_simconnect
- dwID = pData.contents.dwID
- # syslog = logging.getLogger("system")
- # if dwID == 16:
- # syslog.info(f"dispatch: client data ")
-
- if dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_EVENT:
- if verbose: syslog.info("dispatch: receive event")
- evt = cast(pData, POINTER(SIMCONNECT_RECV_EVENT)).contents
- self.handle_id_event(evt)
-
-
- elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_SYSTEM_STATE:
- #if verbose: syslog.info("dispatch: receive state")
- data = cast(pData, POINTER(SIMCONNECT_RECV_SYSTEM_STATE)).contents
- self.handle_state_event(data)
-
- elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE:
- pObjData = cast(
- pData, POINTER(SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE)
- ).contents
- self.handle_simobject_event(pObjData)
-
- elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_OPEN.value:
-
- if verbose: syslog.info("SIMCONNECT:SIM OPEN")
- self._ok = True
-
- elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_EXCEPTION:
- exc = cast(pData, POINTER(SIMCONNECT_RECV_EXCEPTION)).contents
- self.handle_exception_event(exc)
-
- elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID:
- pObjData = cast(
- pData, POINTER(SIMCONNECT_RECV_ASSIGNED_OBJECT_ID)
- ).contents
- objectId = pObjData.dwObjectID
- os.environ["SIMCONNECT_OBJECT_ID"] = str(objectId)
-
- elif (dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_AIRPORT_LIST) or (
- dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_WAYPOINT_LIST) or (
- dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_NDB_LIST) or (
- dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_VOR_LIST):
- pObjData = cast(
- pData, POINTER(SIMCONNECT_RECV_FACILITIES_LIST)
- ).contents
- dwRequestID = pObjData.dwRequestID
- dwEntryNumber = pObjData.dwEntryNumber
- dwOutof = pObjData.dwOutOf
- if verbose: syslog.info("dispatch: receive facility")
- # for _facility in self.Facilities:
- # if dwRequestID == _facility.REQUEST_ID.value:
- # _facility.parent.dump(pData)
- # _facility.dump(pData)
-
- # fire an update event new data was received
- if dwEntryNumber == dwOutof:
- self.handler.simconnect_FacilitiesReceived.emit(self.Facilities)
-
- elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST:
- pObjData = cast(pData, POINTER(SIMCONNECT_RECV_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST)).contents
- dwRequestID = pObjData.dwRequestID
- dwEntryNumber = pObjData.dwEntryNumber
- dwOutof = pObjData.dwOutOf
- count = pObjData.dwArraySize
- if verbose: syslog.info(f"dispatch: received {count} aicraft livery item(s)")
- items = SIMCONNECT_RECV_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST_FACTORY(count)
- pObjData = cast(pData, POINTER(items)).contents
- for item in pObjData.rgData:
- aircraft = item.AircraftTitle.decode()
- livery = item.LiveryName.decode()
- if not aircraft in self.AicraftLiveries:
- self.AicraftLiveries[aircraft] = []
- self.AicraftLiveries[aircraft].append(livery)
- if verbose: syslog.info(f"\t{aircraft} / {livery}")
-
- # fire an update event new data was received
- if dwEntryNumber + 1 == dwOutof:
- # all items received - fire the update
- self.handler.simconnect_AircraftLiveriesReceived.emit(self.AicraftLiveries)
-
-
- elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_QUIT:
- self._quit = 1
- elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_EVENT_FILENAME:
- # file name
-
- pObjData = cast(pData, POINTER(SIMCONNECT_RECV_EVENT_FILENAME)).contents
- file = pObjData.zFileName.decode()
- folder = os.path.dirname(file)
- if verbose: syslog.info(f"dispatch: receive filename: {folder}")
- # example: c:\users\XXX\appdata\local\packages\microsoft.limitless_8wekyb3d8bbwe\localstate\streamedpackages\asobo-aircraft-h125\content\simobjects\airplanes\asobo_h125\presets\asobo\h125_rescue\config
- self.handle_folder_event(folder)
-
- elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_SIMOBJECT_DATA:
- # data
- if verbose: syslog.info("dispatch: receive simobject data")
- pObjData = cast(pData, POINTER(SIMCONNECT_RECV_SIMOBJECT_DATA)).contents
- self.handle_simobject_event(pObjData)
- elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_CLIENT_DATA:
- # client data
- #syslog.info("dispatch: received client data")
- pObjData = cast(pData, POINTER(SIMCONNECT_RECV_CLIENT_DATA)).contents
- #syslog.info(f"received client data: request ID: {pObjData.dwRequestID} define ID: {pObjData.dwDefineID} ")
- self.handle_clientdata_event(pData)
-
-
- else:
- if verbose:
- # syslog = logging.getLogger("system")
- syslog.warning(f"SIMCONNECT:Received: id {dwID}")
- return
-
-
- def _find_dll(self):
- ''' finds the simconnect DLL '''
- dll_file = "SimConnect.dll"
-
- # executable path
- dll_folder = gremlin.shared_state.root_path
- dll_test = os.path.join(dll_folder, dll_file)
- if os.path.isfile(dll_test):
- return dll_test
-
- # current .py folder
- dll_folder = os.path.dirname(__file__)
- dll_test = os.path.join(dll_folder, dll_file)
- if os.path.isfile(dll_test):
- return dll_test
-
- # one level up
- dll_folder = os.path.dirname(dll_folder)
- dll_test = os.path.join(dll_folder, dll_file)
- if os.path.isfile(dll_test):
- return dll_test
-
-
- return None
-
- @QtCore.Slot()
- def _sync_callback(self):
- handler = SimConnectEventHandler()
- handler.status_callback_clicked.emit()
-
- def connect(self):
- from pathlib import Path
- from gremlin.util import display_error, get_dll_version
-
- if self._connecting:
- return
-
-
- # syslog = logging.getLogger("system")
- verbose = gremlin.config.Configuration().verbose_mode_simconnect
- if verbose: syslog.info("SIMCONNECT: connect...")
-
- self._connecting = True
- self._request_abort = False
-
- try:
-
- if self._is_connected:
- # already connected
- if verbose: syslog.info("\talready connected")
- return True
-
-
- el = gremlin.event_handler.EventListener()
- el.module_state_register.emit("simconnect","SimConnect",None, self._sync_callback)
- if not self._dll_path or not os.path.isfile(self._dll_path):
-
- if not self._library_path:
- # locate the simconnect dll file based on the distro
- self._dll_path = self._find_dll()
- if not os.path.isfile(self._dll_path):
- msg = f"Unable to continue - missing dll: {self._dll_path}"
- display_error(msg)
- syslog.critical(msg) # issue a critical error because the DLL should be with the distribution
- os._exit(1)
-
- if verbose: syslog.info(f"\tUsing dll : {self._dll_path}")
- self._library_path = self._dll_path
-
- self._is_loop_running = False
- self._quit = 0
-
-
-
- if not self._win_dll:
- # dll is not loaded
-
- try:
- self._win_dll = windll.LoadLibrary(self._dll_path)
- syslog.error(f"\tDLL load: ok")
- except Exception as err:
- self._request_abort = True
- syslog.error(f"SIMCONNECT:DLL load error: {err}")
- self._quit = 1
-
- el.request_profile_stop.emit("Error loading DLL")
- return False
-
- if self._win_dll:
-
- if verbose: syslog.info(f"\tUsing win dll : {self._win_dll}")
-
-
- self._dll = SimConnectDll(self._win_dll)
-
- self._my_dispatch_proc_rd = self._dll.DispatchProc(self.simconnect_dispatch_proc)
- try:
- if verbose: syslog.info(f"\tloading dll...")
- err = self._dll.Open(byref(self._hSimConnect), LPCSTR(b"GremlinEx"), None, 0, 0, 0)
- except:
- # likely not running
- syslog.warning("SIMCONNECT: MSFS not running or missing DLL")
- err = 1
- if self.IsHR(err, 0):
- syslog.info("\tConnected to MSFS - hr code 0")
- # Request an event when the simulation starts
-
- self._is_connected = True
-
-
- # The user is in control of the aircraft
- self._dll.SubscribeToSystemEvent(
- self._hSimConnect, self._dll.EventID.EVENT_SIM_START.value, b"SimStart"
- )
- self._dll.SetSystemEventState(self._hSimConnect, self._dll.EventID.EVENT_SIM_START, SIMCONNECT_STATE.SIMCONNECT_STATE_ON)
-
- # The user is navigating the UI.
- self._dll.SubscribeToSystemEvent(
- self._hSimConnect, self._dll.EventID.EVENT_SIM_STOP.value, b"SimStop"
- )
- self._dll.SetSystemEventState(self._hSimConnect, self._dll.EventID.EVENT_SIM_STOP.value, SIMCONNECT_STATE.SIMCONNECT_STATE_ON)
-
- # Request a notification when the flight is paused
- self._dll.SubscribeToSystemEvent(
- self._hSimConnect, self._dll.EventID.EVENT_SIM_PAUSE_STATE.value, b"Pause"
- )
- self._dll.SetSystemEventState(self._hSimConnect, self._dll.EventID.EVENT_SIM_PAUSE_STATE.value, SIMCONNECT_STATE.SIMCONNECT_STATE_ON)
-
- # Request a notification when the flight is un-paused.
- self._dll.SubscribeToSystemEvent(
- self._hSimConnect, self._dll.EventID.EVENT_SIM_RUNNING.value, b"Sim"
- )
- # aircraft loaded data
- self._dll.SubscribeToSystemEvent(
- self._hSimConnect, self._dll.EventID.EVENT_SIM_REQUEST_AIRCRAFT.value, b"AircraftLoaded"
- )
-
- syslog.info(f"\tinterface connected")
- self.run()
-
- else:
- # error
- self._request_abort = True
- return False
-
-
- el.module_state_change.emit("simconnect", True)
- return True
- finally:
- self._connecting = False
-
-
- def run(self):
- if not self._is_loop_running and self._runThread is None:
- self._runThread = threading.Thread(target=self._run)
- self._runThread.setName("Simconnect Run")
- self._runThread.daemon = True
- self._runThread.start()
- while self.ok is False:
- pass
-
-
-
- def _run(self):
- self.handler.simconnect_connected.emit()
- self._is_loop_running = True
- # syslog = logging.getLogger("system")
- verbose = gremlin.config.Configuration().verbose_mode_simconnect
- if verbose: syslog.info("SIMCONNECT: run loop start")
- error_count = 5
- self._quit = 0 # keep on running until stop
- while self._quit == 0:
- try:
- self._dll.CallDispatch(self._hSimConnect, self._my_dispatch_proc_rd, None)
- time.sleep(.002)
-
- except:
- error_count -=1
- if error_count == 0:
- self._quit = True
- # close the connection
- try:
- if self._dll:
- self._dll.Close(self._hSimConnect)
- except:
- pass
- self._dll = None
- self._my_dispatch_proc_rd = None
- try:
- self.handler.simconnect_disconnected.emit()
- except:
- pass
-
- self._is_connected = False
- if verbose: syslog.info("SIMCONNECT: run loop end")
- self._is_loop_running = False
-
-
- def disconnect(self):
- ''' disconnects from the sim '''
- self.exit()
- self._last_loaded_aircraft = None
- self._last_loaded_aircraft_cfg = None
-
- def exit(self):
- ''' disconnects from the sim '''
- if self._is_loop_running:
- # syslog = logging.getLogger("system")
- verbose = gremlin.config.Configuration().verbose_mode_simconnect
- if verbose: syslog.info("SIMCONNECT: exit requested")
- self._quit = 1 # kill the thread loop
- self._runThread.join()
- # this also resets the flags
- self._runThread = None
- self._is_loop_running = False
- el = gremlin.event_handler.EventListener()
- el.module_state_change.emit("simconnect", None)
-
-
- def is_connected(self, auto_connect = True):
- ''' determines if connected and optionally starts the connection '''
- if self._request_abort:
- return False
- return self.ok
-
- def is_running(self, auto_connect = True):
- ''' determines if the event loop is running or not '''
- if self._request_abort:
- return False
- if not self._is_loop_running and auto_connect:
- self.connect()
-
- return self._is_loop_running
-
-
- def map_to_sim_event(self, name):
- # syslog = logging.getLogger("system")
- if self._dll is not None:
- for m in self._dll.EventID:
- if name.decode() == m.name:
- if self.verbose_details:
- syslog.debug(f"SIMCONNECT: already have event: {name} {m}")
- return m
-
- names = [m.name for m in self._dll.EventID] + [name.decode()]
- self._dll.EventID = Enum(self._dll.EventID.__name__, names)
- evnt = list(self._dll.EventID)[-1]
- try:
- err = self._dll.MapClientEventToSimEvent(self._hSimConnect, evnt.value, name)
- if self.IsHR(err, 0):
- return evnt
- except:
- syslog.error(f"SIMCONNECT:Error: MapToSimEvent error: event: {err}")
- pass
-
-
- syslog.error(f"SIMCONNECT:Error: MapToSimEvent: not connected event: {name}")
- return None
-
- def add_to_notification_group(self, group, event, maskable : bool =False):
- if self._dll is not None:
- self._dll.AddClientEventToNotificationGroup(
- self._hSimConnect, group, event, maskable
- )
-
-
-
-
- def _request_data(self, request : Request):
- if self._dll is not None:
- request.buffer = None
- if request.is_client_data:
- self._dll.RequestClientData(
- self._hSimConnect,
- request.CLIENT_DATA_ID.value,
- request.DATA_REQUEST_ID.value,
- request.DATA_DEFINITION_ID.value,
- SIMCONNECT_PERIOD.SIMCONNECT_PERIOD_ONCE,
- SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT,
- )
- else:
- self._dll.RequestDataOnSimObjectType(
- self._hSimConnect,
- request.DATA_REQUEST_ID.value,
- request.DATA_DEFINITION_ID.value,
- 0,
- SIMCONNECT_SIMOBJECT_TYPE.SIMCONNECT_SIMOBJECT_TYPE_USER,
- )
- temp = DWORD(0)
- self._dll.GetLastSentPacketID(self._hSimConnect, temp)
- request.LastID = temp.value
-
- def _request_periodic_data(self, request : Request):
- if self._dll is not None:
- request.buffer = None
- if request.is_client_data:
- self._dll.RequestClientData(
- self._hSimConnect,
- request.CLIENT_DATA_ID.value,
- request.DATA_REQUEST_ID.value,
- request.DATA_DEFINITION_ID.value,
- SIMCONNECT_PERIOD.SIMCONNECT_PERIOD_VISUAL_FRAME,
- SIMCONNECT_DATA_REQUEST_FLAG.SIMCONNECT_DATA_REQUEST_FLAG_CHANGED,
- 0, # origin The number of times the data should be transmitted before this communication is ended. The default is zero, which means the data should be transmitted endlessly.
- 0, # interval The number of times the data should be transmitted before this communication is ended. The default is zero, which means the data should be transmitted endlessly.
- 0 # limit The number of times the data should be transmitted before this communication is ended. The default is zero, which means the data should be transmitted endlessly.
- )
- else:
- self._dll.RequestDataOnSimObject(
- self._hSimConnect,
- request.DATA_REQUEST_ID.value,
- request.DATA_DEFINITION_ID.value,
- 0, # object ID 0 = aircraft
- SIMCONNECT_PERIOD.SIMCONNECT_PERIOD_VISUAL_FRAME, # specifies how often the data is to be sent by the server and received by the client
- SIMCONNECT_DATA_REQUEST_FLAG.SIMCONNECT_DATA_REQUEST_FLAG_CHANGED, # 0 or changed or tagged
- 0, # origin The number of times the data should be transmitted before this communication is ended. The default is zero, which means the data should be transmitted endlessly.
- 0, # interval The number of times the data should be transmitted before this communication is ended. The default is zero, which means the data should be transmitted endlessly.
- 0 # limit The number of times the data should be transmitted before this communication is ended. The default is zero, which means the data should be transmitted endlessly.
- )
- temp = DWORD(0)
- self._dll.GetLastSentPacketID(self._hSimConnect, temp)
- request.LastID = temp.value
-
- def stop_periodic_data(self, request):
- ''' stops a request for periodic data setup with request_periodic_data'''
- if self._dll is not None:
- request.buffer = None
- if request.is_client_data:
- self._dll.RequestClientData(
- self._hSimConnect,
- request.CLIENT_DATA_ID.value,
- request.DATA_REQUEST_ID.value,
- request.DATA_DEFINITION_ID.value,
- SIMCONNECT_PERIOD.SIMCONNECT_PERIOD_NEVER,
- SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT,
- )
-
- else:
- self._dll.RequestDataOnSimObject(
- self._hSimConnect,
- request.DATA_REQUEST_ID.value,
- request.DATA_DEFINITION_ID.value,
- 0,
- SIMCONNECT_PERIOD.SIMCONNECT_PERIOD_NEVER,
- )
-
- def clear(self, request):
- ''' clears a request '''
- if self._dll is not None:
- self._dll.ClearClientDataDefinition(
- self._hSimConnect,
- request.DATA_DEFINITION_ID.value
- )
-
- def set_data(self, request : Request):
- if self._dll is None:
- self.connect()
- if self._dll is None:
- syslog.warning(f"SIMCONNECT:Setdata: not connected - request : {request.definitions}")
- return False
- if request.buffer is None:
- return False
- rtype = request.definitions[0][1].decode()
- if 'string' in rtype.lower():
- pyarr = bytearray(request.buffer)
- dataarray = (ctypes.c_char * len(pyarr))(*pyarr)
- else:
- pyarr = list([request.buffer])
- dataarray = (ctypes.c_double * len(pyarr))(*pyarr)
-
- pObjData = cast(
- dataarray, c_void_p
- )
- err = self._dll.SetDataOnSimObject(
- self._hSimConnect,
- request.DATA_DEFINITION_ID.value,
- SIMCONNECT_SIMOBJECT_TYPE.SIMCONNECT_SIMOBJECT_TYPE_USER,
- SIMCONNECT_DATA_SET_FLAG.SIMCONNECT_DATA_SET_FLAG_DEFAULT,
- 1, # one element
- sizeof(ctypes.c_double) * len(pyarr), # size of the element in bytes
- pObjData
- )
- if self.IsHR(err, 0):
- # LOGGER.debug("Request Sent")
- return True
- else:
- return False
-
-
- def get_data(self, request):
- self._request_data(request)
- retries = 0
- while request.buffer is None and retries < request.attempts:
- time.sleep(.01)
- retries += 1
- if request.buffer is None:
- if self.verbose:
- syslog.warning(f"SIMCONNECT:warning: timeout in request {request}")
- return False
- return True
-
- def get_periodic_data(self, request):
- ''' requests periodic data (data will be sent on change via the event system) '''
- self._request_periodic_data(request)
-
-
- def send_event(self, evnt, data=DWORD(0)):
- if self._dll is None:
- return False
- try:
- err = self._dll.TransmitClientEvent(
- self._hSimConnect,
- SIMCONNECT_OBJECT_ID_USER,
- evnt.value,
- data,
- SIMCONNECT_GROUP_PRIORITY_HIGHEST,
- SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY #DWORD(16),
-
- )
-
- if self.IsHR(err, 0):
- # LOGGER.debug("Event Sent")
- return True
- else:
- return False
- except:
- pass
- return False
-
-
-
- def new_client_data_definition_id(self):
- if self._dll is None:
- return None
-
- _name = "ClientDataDef_" + str(len(list(self._dll.CLIENT_DATA_DEFINITION_ID)))
- names = [m.name for m in self._dll.CLIENT_DATA_DEFINITION_ID] + [_name]
- self._dll.CLIENT_DATA_DEFINITION_ID = Enum(self._dll.CLIENT_DATA_DEFINITION_ID.__name__, names)
- CLIENT_DATA_DEFINITION_ID = list(self._dll.CLIENT_DATA_DEFINITION_ID)[-1]
- return CLIENT_DATA_DEFINITION_ID
-
- def new_def_id(self):
- if self._dll is None:
- return None
-
- _name = "Definition_" + str(len(list(self._dll.DATA_DEFINITION_ID)))
- names = [m.name for m in self._dll.DATA_DEFINITION_ID] + [_name]
-
- self._dll.DATA_DEFINITION_ID = Enum(self._dll.DATA_DEFINITION_ID.__name__, names)
- DEFINITION_ID = list(self._dll.DATA_DEFINITION_ID)[-1]
- return DEFINITION_ID
-
- def new_client_data_id(self):
- if self._dll is None:
- return None
-
- _name = "ClientData_" + str(len(list(self._dll.CLIENT_DATA_ID)))
- names = [m.name for m in self._dll.CLIENT_DATA_ID] + [_name]
-
- self._dll.CLIENT_DATA_ID = Enum(self._dll.CLIENT_DATA_ID.__name__, names)
- CLIENT_ID = list(self._dll.CLIENT_DATA_ID)[-1]
- return CLIENT_ID
-
-
- def new_request_id(self):
- if self._dll is None:
- return None
-
- name = "Request_" + str(len(self._dll.DATA_REQUEST_ID))
- names = [m.name for m in self._dll.DATA_REQUEST_ID] + [name]
- self._dll.DATA_REQUEST_ID = Enum(self._dll.DATA_REQUEST_ID.__name__, names)
- REQUEST_ID = list(self._dll.DATA_REQUEST_ID)[-1]
-
- return REQUEST_ID
-
- def add_waypoints(self, _waypointlist):
- if self._dll is None:
- return
- if self.DEFINITION_WAYPOINT is None:
- self.DEFINITION_WAYPOINT = self.new_def_id()
- err = self._dll.AddToDataDefinition(
- self._hSimConnect,
- self.DEFINITION_WAYPOINT.value,
- b'AI WAYPOINT LIST',
- b'number',
- SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_WAYPOINT,
- 0,
- SIMCONNECT_UNUSED,
- )
- pyarr = []
- for waypt in _waypointlist:
- for e in waypt._fields_:
- pyarr.append(getattr(waypt, e[0]))
- dataarray = (ctypes.c_double * len(pyarr))(*pyarr)
- pObjData = cast(
- dataarray, c_void_p
- )
- sx = int(sizeof(ctypes.c_double) * (len(pyarr) / len(_waypointlist)))
- return
-
-
- def set_pos(
- self,
- _Altitude,
- _Latitude,
- _Longitude,
- _Airspeed,
- _Pitch=0.0,
- _Bank=0.0,
- _Heading=0,
- _OnGround=0,
- ):
-
- if self._dll is None:
- return False
-
- Init = SIMCONNECT_DATA_INITPOSITION()
- Init.Altitude = _Altitude
- Init.Latitude = _Latitude
- Init.Longitude = _Longitude
- Init.Pitch = _Pitch
- Init.Bank = _Bank
- Init.Heading = _Heading
- Init.OnGround = _OnGround
- Init.Airspeed = _Airspeed
-
- if self.DEFINITION_POS is None:
- self.DEFINITION_POS = self.new_def_id()
- err = self._dll.AddToDataDefinition(
- self._hSimConnect,
- self.DEFINITION_POS.value,
- b'Initial Position',
- b'',
- SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_INITPOSITION,
- 0,
- SIMCONNECT_UNUSED,
- )
-
- hr = self._dll.SetDataOnSimObject(
- self._hSimConnect,
- self.DEFINITION_POS.value,
- SIMCONNECT_OBJECT_ID_USER,
- 0,
- 0,
- sizeof(Init),
- pointer(Init)
- )
- if self.IsHR(hr, 0):
- return True
- else:
- return False
-
- def load_flight_plan(self, pln_path):
- if self._dll is None:
- return False
- hr = self._dll.FlightPlanLoad(self._hSimConnect, pln_path.encode())
- if self.IsHR(hr, 0):
- return True
- else:
- return False
-
-
- # def save_flight(
- # self,
- # flt_path,
- # flt_title,
- # flt_description,
- # flt_mission_type='FreeFlight',
- # flt_mission_location='Custom departure',
- # flt_original_flight='',
- # flt_flight_type='NORMAL'):
-
- # if self._dll is None:
- # return False
- # hr = self._dll.FlightSave(self._hSimConnect, flt_path.encode(), flt_title.encode(), flt_description.encode(), 0)
- # if not self.IsHR(hr, 0):
- # return False
-
- # dicp = self.flight_to_dic(flt_path)
- # if 'MissionType' not in dicp['Main']:
- # dicp['Main']['MissionType'] = flt_mission_type
-
- # if 'MissionLocation' not in dicp['Main']:
- # dicp['Main']['MissionLocation'] = flt_mission_location
-
- # if 'FlightType' not in dicp['Main']:
- # dicp['Main']['FlightType'] = flt_flight_type
-
- # if 'OriginalFlight' not in dicp['Main']:
- # dicp['Main']['OriginalFlight'] = flt_original_flight
- # self.dic_to_flight(dicp, flt_path)
-
- # return False
-
- def get_paused(self):
- if self._dll is None:
- return False
- hr = self._dll.RequestSystemState(
- self._hSimConnect,
- self._dll.EventID.EVENT_SIM_PAUSED,
- b"Sim"
- )
-
-
-
-
-
- def dic_to_flight(self, dic, fpath):
- with open(fpath, "w") as tempfile:
- for root in dic:
- tempfile.write(f"\n[{root}]\n")
- for member in dic[root]:
- tempfile.write(f"{member}={dic[root][member]}\n")
-
- def flight_to_dic(self, fpath):
- while not os.path.isfile(fpath):
- pass
- time.sleep(0.5)
- dic = {}
- index = ""
- with open(fpath, "r") as tempfile:
- for line in tempfile.readlines():
- if line[0] == '[':
- index = line[1:-2]
- dic[index] = {}
- else:
- if index != "" and line != '\n':
- temp = line.split("=")
- dic[index][temp[0]] = temp[1].strip()
- return dic
-
- def sendText(self, text, timeSeconds=5, TEXT_TYPE=SIMCONNECT_TEXT_TYPE.SIMCONNECT_TEXT_TYPE_PRINT_WHITE):
- if self._dll is None:
- return False
- pyarr = bytearray(text.encode())
- dataarray = (ctypes.c_char * len(pyarr))(*pyarr)
- pObjData = cast(dataarray, c_void_p)
- self._dll.Text(
- self._hSimConnect,
- TEXT_TYPE,
- timeSeconds,
- 0,
- sizeof(ctypes.c_double) * len(pyarr),
- pObjData
- )
-
- def createSimulatedObject(self, name, lat, lon, rqst, hdg=0, gnd=1, alt=0, pitch=0, bank=0, speed=0):
- if self._dll is None:
- return False
- simInitPos = SIMCONNECT_DATA_INITPOSITION()
- simInitPos.Altitude = alt
- simInitPos.Latitude = lat
- simInitPos.Longitude = lon
- simInitPos.Pitch = pitch
- simInitPos.Bank = bank
- simInitPos.Heading = hdg
- simInitPos.OnGround = gnd
- simInitPos.Airspeed = speed
- self._dll.AICreateSimulatedObject(
- self._hSimConnect,
- name.encode(),
- simInitPos,
- rqst.value
- )
-
- def createClientData(self, request_id, size = 4096, flags = 0):
- ''' creates a user data area
- https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_CreateClientData.htm
- '''
- if self._dll is None:
- return False
- hr = self._dll.CreateClientData(self._hSimConnect, request_id, size, flags)
- if not self.IsHR(hr, 0):
- return False
- return True
-
-
- def addToClientDataDefinition(self, definition_id, offset, size):
- ''' adds to the client data definition, returns true on success '''
- if self._dll is None:
- return False
- hr = self._dll.AddToClientDataDefinition(self._hSimConnect, definition_id, offset, size, 0, 0)
- if not self.IsHR(hr, 0):
- return False
- return True
-
- def requestAircraftLoaded(self):
- ''' makes a request for the current loaded aircraft '''
- if self._dll:
- hr = self._dll.RequestSystemState(self._hSimConnect,
- self._dll.EventID.EVENT_SIM_REQUEST_AIRCRAFT.value,
- b"AircraftLoaded"
- )
- if not self.IsHR(hr, 0):
- return False
- return True
- return False
-
- def requestSimObjectsAndLiveries(self):
- ''' makes a request for the user flyable aircraft list '''
- if self._dll:
- self.AicraftLiveries.clear()
- hr = self._dll.EnumerateSimObjectsAndLiveries(self._hSimConnect,
- self._dll.EventID.EVENT_SIM_REQUEST_ENUMERATE_SIM_OBJECTS_AND_LIVERIES.value,
- SIMCONNECT_SIMOBJECT_TYPE.SIMCONNECT_SIMOBJECT_TYPE_USER)
- if not self.IsHR(hr, 0):
- return False
- return True
- return False
-
- def getSimObjects(self) -> list[str]:
- ''' gets the current list of aircraft - gets the results of requestSimObjectsAndLiveries() '''
- return list(self.AicraftLiveries.keys())
-
- def save_flight(self, the_path : str, title: str = "", description : str = ""):
- ''' save the current flight '''
- if self._dll:
- b_the_path = the_path.encode()
- b_title = title.encode()
- b_description = description.encode()
- hr = self._dll.FlightSave(self._hSimConnect, b_title, b_the_path, b_description, 0)
- if not self.IsHR(hr, 0):
- return False
- return True
- return False
-
-
- def load_flight(self, the_path : str):
- ''' loads a saved flight '''
- if self._dll:
- b_the_path = the_path.encode()
- hr = self._dll.FlightLoad(self._hSimConnect, b_the_path)
- if not self.IsHR(hr, 0):
- return False
- return True
- return False
\ No newline at end of file
+class SimConnect:
+ """MSFS simconnect interface"""
+
+ def __init__(self, handler: SimConnectEventHandler, auto_connect=True):
+ """initializes sim connect
+
+ :param handler: SimConnectEventHandler - the handler to signals
+
+ """
+ super().__init__()
+
+ self.Requests = {}
+ self.Facilities = []
+ self.AicraftLiveries = {} # data returned for user flyable aicraft livery pairs [sim_name] = list[str] (liveries)
+ self.verbose = gremlin.config.Configuration().verbose_mode_simconnect
+ self.verbose_details = False
+ self._connecting = False # true if connecting
+ self._hSimConnect = HANDLE()
+ self._quit = 0
+ self._ok = False
+ self._request_abort = False # true if we're aborting and should stop running
+ self.running = False
+ self._is_loop_running = False # true if the DLL event listen loop is running
+ self._is_connected = False # true if the DLL is hooked
+ self._request_busy = False
+ self.paused = False
+ self.DEFINITION_POS = None
+ self.DEFINITION_WAYPOINT = None
+ self._my_dispatch_proc_rd = None
+ self._aircraft_loaded_event = (
+ threading.Event()
+ ) # locks the aircraft process thread in case we get multiple concurrent calls
+ self._last_loaded_aircraft = None
+ self._last_loaded_aircraft_cfg = None
+
+ self.handler: SimConnectEventHandler = handler # used to trigger various events
+ self.client_data_handlers = [] # client data handlers - for when client data is received
+
+ self._win_dll = None # reference to the loaded DLL
+ self._dll = None # simconnect class
+ self._dll_path = None # path to the dll located in the distribution
+ self._runThread = None
+ self._library_path = None
+ self._critical = False
+ self._state_handled = {} # tracks state change messages
+
+ def reset(self):
+ """resets abort flag set due to a load error - this is necessary upon reconnect"""
+ self._request_abort = False
+ self._state_handled = {}
+
+ @QtCore.Slot()
+ def _shutdown(self):
+ """called when the app is shutting down"""
+ # syslog = logging.getLogger("system")
+ if self._dll:
+ try:
+ syslog.info("SIMCONNECT: shutdown ")
+ self._dll.close()
+ except:
+ pass
+ self._dll = None
+ if self._win_dll:
+ self._win_dll = None
+
+ @property
+ def ok(self) -> bool:
+ return self._ok and self._dll is not None
+
+ @property
+ def handle(self):
+ return self._hSimConnect
+
+ @property
+ def last_loaded_aircraft(self) -> str:
+ """gets the name of the last loaded aircraft, None if not set"""
+ return self._last_loaded_aircraft
+
+ @property
+ def last_loaded_aircraft_cfg(self) -> str:
+ """gets the folder of the last loaded aircraft, None if not set"""
+ return self._last_loaded_aircraft_cfg
+
+ def register_client_data_handler(self, handler):
+ """registers a client data area handler"""
+ if handler not in self.client_data_handlers:
+ verbose = gremlin.config.Configuration().verbose_mode_simconnect
+ if verbose:
+ # syslog = logging.getLogger("system")
+ syslog.info("SIMCONNECT: register new client data handler")
+ self.client_data_handlers.append(handler)
+
+ def unregister_client_data_handler(self, handler):
+ """unregisters a client data area handler"""
+ if handler in self.client_data_handlers:
+ verbose = gremlin.config.Configuration().verbose_mode_simconnect
+ if verbose:
+ # syslog = logging.getLogger("system")
+ syslog.info("SIMCONNECT: unregister new client data handler")
+ self.client_data_handlers.remove(handler)
+
+ @QtCore.Slot()
+ def _connected(self):
+ # mark completed
+ self._request_busy = False
+ # syslog = logging.getLogger("system")
+ syslog.info("SIMCONNECT: connect request completed")
+
+ @QtCore.Slot()
+ def _disconnected(self):
+ # marke completed
+ self._request_busy = False
+ # syslog = logging.getLogger("system")
+ syslog.info("SIMCONNECT: disconnect request completed")
+
+ @QtCore.Slot()
+ def _connect_request(self):
+ """connection request received"""
+ if not self._request_busy:
+ self._request_busy = True
+ if not self._is_connected:
+ try:
+ self.connect()
+ except:
+ # syslog = logging.getLogger("system")
+ syslog.warning(
+ "SIMCONNECT: simulator not running yet - unable to connect."
+ )
+ return
+
+ while not self._is_loop_running:
+ time.sleep(0.1)
+ else:
+ # already running
+ self._request_busy = False
+
+ @QtCore.Slot()
+ def _disconnect_request(self):
+ """connection disconnect received"""
+ if not self._request_busy:
+ self._request_busy = True
+ if self._is_loop_running:
+ self.exit()
+ else:
+ # not running
+ self._request_busy = False
+
+ # events fired by SimConnect
+
+ def IsHR(self, hr, value):
+ _hr = ctypes.HRESULT(hr)
+ return ctypes.c_ulong(_hr.value).value == value
+
+ def handle_id_event(self, event: SIMCONNECT_RECV_EVENT):
+ # syslog = logging.getLogger("system")
+ uEventID = event.uEventID
+ if uEventID == self._dll.EventID.EVENT_SIM_START.value:
+ self.running = True
+ self.handler.simconnect_sim_start.emit()
+ syslog.info("SIMCONNECT: event: SIM START")
+ elif uEventID == self._dll.EventID.EVENT_SIM_REQUEST_AIRCRAFT.value:
+ # aircraft request
+ pass
+ elif uEventID == self._dll.EventID.EVENT_SIM_STOP.value:
+ if self.verbose:
+ syslog.info("SIMCONNECT:event: SIM Stop")
+ self.running = False
+ self.handler.simconnect_sim_stop.emit()
+ elif uEventID == self._dll.EventID.EVENT_SIM_PAUSE_STATE.value:
+ paused = event.dwData == 1
+ if self.verbose:
+ syslog.info(f"SIMCONNECT: event: SIM Pause State {paused}")
+ self.paused = paused
+ self.handler.simconnect_sim_paused.emit(paused)
+ # elif uEventID == self._dll.EventID.EVENT_SIM_PAUSED.value:
+ # self.handler.simconnect_sim_paused.emit()
+ # if self.verbose:
+ # syslog.info("SIMCONNECT:event: SIM Paused")
+ # self.paused = True
+ # self.handler.simconnect_sim_paused.emit(self.paused)
+ # elif uEventID == self._dll.EventID.EVENT_SIM_UNPAUSED.value:
+ # self.handler.simconnect_sim_unpaused.emit()
+ # if self.verbose:
+ # syslog.info("SIMCONNECT:event: SIM Unpaused")
+ # self.paused = False
+ # self.handler.simconnect_sim_paused.emit(self.paused)
+ elif uEventID == self._dll.EventID.EVENT_SIM_RUNNING.value:
+ if self.verbose:
+ syslog.info("SIMCONNECT: event: SIM Running")
+ state = event.dwData != 0
+ self.running = state
+ self.handler.simconnect_sim_running.emit(state)
+
+ elif uEventID == self._dll.EventID.EVENT_SIM_AIRCRAFT_LOADED.value:
+ aircraft_cfg = event.dwData # air file loaded
+ if self.verbose:
+ syslog.info(f"SIMCONNECT: event: AIRCRAFT LOADED: {aircraft_cfg}")
+ self.handle_folder_event(aircraft_cfg.decode())
+
+ # else:
+ # syslog.error(f"SIMCONNECT:received event {uEventID} - don't know how to handle")
+
+ def _process_aircraft_string(self, aircraft_cfg):
+ """processes an aircraft string - could be a load event or a folder event"""
+ # syslog = logging.getLogger("system")
+ verbose = gremlin.config.Configuration().verbose_mode_simconnect
+ name = self._read_aicraft_config(aircraft_cfg)
+ if verbose:
+ syslog.info(f"SIMCONNECT: aircraft string: {name}")
+ if name:
+ self.handler.simconnect_aircraft_loaded.emit(aircraft_cfg, name)
+ self._last_loaded_aircraft = name
+ self._last_loaded_aircraft_cfg = aircraft_cfg
+ self._aircraft_loaded_event.clear()
+
+ def _read_aicraft_config(self, aircraft_cfg):
+ """reads a configuration folder and extracts a configuration object"""
+
+ # syslog = logging.getLogger("system")
+ verbose = gremlin.config.Configuration().verbose_mode_simconnect
+ if verbose:
+ syslog.info(f"SIMCONNECT:parsing: {aircraft_cfg}")
+
+ if not aircraft_cfg:
+ syslog.error(f"SIMCONNECT:error - invalid aircraft string: {aircraft_cfg}")
+ return
+
+ # Example 1: SimObjects\Airplanes\a400m\presets\inibuilds\a400m_cargo\config\aircraft.CFG
+ # Example 2: c:\users\XXXX\appdata\local\packages\microsoft.limitless_8wekyb3d8bbwe\localstate\streamedpackages\asobo-aircraft-h125\content\simobjects\airplanes\asobo_h125\presets\asobo\h125_rescue\config
+ # Example 3: c:\users\XXXX\appdata\local\packages\microsoft.limitless_8wekyb3d8bbwe\localcache\packages\community\bksq-aircraft-turbineduke\simobjects\airplanes\bksq-aircraft-turbineduke
+
+ work_cfg = aircraft_cfg.replace("/", os.sep).casefold()
+ splits = work_cfg.split(os.sep)
+ max_index = len(splits)
+ index = 0
+ if "airplanes" in splits:
+ index = splits.index("airplanes")
+ index += 1
+
+ # while splits[index] != "simobjects" and index < max_index:
+ # index+=1
+ # index+=1
+ # if index < max_index:
+ # while splits[index] != "airplanes" and index < max_index:
+ # index+=1
+ # index+=1
+ if index >= 0 and index < max_index:
+ aircraft_name = splits[index]
+ if verbose:
+ syslog.info(f"SIMCONNECT:found {aircraft_name}")
+ return aircraft_name
+
+ return None
+
+ def handle_simobject_event(self, ObjData):
+ dwRequestID = ObjData.dwRequestID
+ if dwRequestID in self.Requests:
+ _request = self.Requests[dwRequestID]
+ rtype = _request.definitions[0][1].decode()
+ if "string" in rtype.lower():
+ pS = cast(ObjData.dwData, c_char_p)
+ _request.buffer = pS.value
+ else:
+ _request.buffer = cast(
+ ObjData.dwData, POINTER(c_double * len(_request.definitions))
+ ).contents[0]
+
+ if _request.callback is not None:
+ """ run the request callback """
+ _request.callback()
+
+ return True
+
+ # not one of ours
+ return False
+ # # syslog = logging.getLogger("system")
+ # syslog.warning(f"SIMCONNECT:Event ID: {dwRequestID} is not handled")
+
+ def handle_clientdata_event(self, pData):
+ """handles client data receipt"""
+ # pObjData = cast(pData, POINTER(SIMCONNECT_RECV_CLIENT_DATA)).contents
+ # if not self.handle_simobject_event(pObjData):
+ # # handle non requests
+ for handle in self.client_data_handlers:
+ handle(pData)
+
+ def handle_exception_event(self, exc):
+ _exception = SIMCONNECT_EXCEPTION(exc.dwException).name
+ _unsendid = exc.UNKNOWN_SENDID
+ # _sendid = exc.dwSendID
+ # _unindex = exc.UNKNOWN_INDEX
+ # _index = exc.dwIndex
+
+ # request exceptions
+ # syslog = logging.getLogger("system")
+ for _reqin in self.Requests:
+ _request = self.Requests[_reqin]
+ if _request.LastID == _unsendid:
+ syslog.warning(
+ f"SIMCONNECT:error: {_exception} {_request.definitions[0]}"
+ )
+ return
+
+ syslog.warning(_exception)
+
+ def handle_state_event(self, pData: SIMCONNECT_RECV_SYSTEM_STATE):
+ """handles simconnect state changes - this receives the aicraft data"""
+ int_data = pData.dwInteger
+ float_data = pData.fFloat
+ str_data = pData.szString
+ key = (int_data, float_data, str_data)
+ if key not in self._state_handled:
+ if self.verbose:
+ # syslog = logging.getLogger("system")
+ syslog.info(
+ f"SIMCONNECT:state event: int: {pData.dwInteger} float: {pData.fFloat} str: {pData.szString}"
+ )
+
+ if str_data:
+ aircraft_cfg = str_data.decode()
+ if not self._aircraft_loaded_event.is_set():
+ self._aircraft_loaded_event.set()
+ self._process_aircraft_string(aircraft_cfg)
+ self.handler.simconnect_state_changed.emit(int_data, float_data, str_data)
+ self._state_handled[key] = True
+
+ def handle_folder_event(self, aircraft_cfg):
+ """folder received"""
+ if not self._aircraft_loaded_event.is_set():
+ self._aircraft_loaded_event.set()
+ thread = threading.Thread(
+ target=self._process_aircraft_string, args=[aircraft_cfg], daemon=True
+ )
+ thread.setName("process aircraft file thread")
+ thread.start()
+
+ # TODO: update callbackfunction to expand functions.
+ def simconnect_dispatch_proc(self, pData, cbData, pContext):
+ # print("my_dispatch_proc")
+ verbose = gremlin.config.Configuration().verbose_mode_simconnect
+ dwID = pData.contents.dwID
+ # syslog = logging.getLogger("system")
+ # if dwID == 16:
+ # syslog.info(f"dispatch: client data ")
+
+ if dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_EVENT:
+ if verbose:
+ syslog.info("dispatch: receive event")
+ evt = cast(pData, POINTER(SIMCONNECT_RECV_EVENT)).contents
+ self.handle_id_event(evt)
+
+ elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_SYSTEM_STATE:
+ # if verbose: syslog.info("dispatch: receive state")
+ data = cast(pData, POINTER(SIMCONNECT_RECV_SYSTEM_STATE)).contents
+ self.handle_state_event(data)
+
+ elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE:
+ pObjData = cast(
+ pData, POINTER(SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE)
+ ).contents
+ self.handle_simobject_event(pObjData)
+
+ elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_OPEN.value:
+ if verbose:
+ syslog.info("SIMCONNECT:SIM OPEN")
+ self._ok = True
+
+ elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_EXCEPTION:
+ exc = cast(pData, POINTER(SIMCONNECT_RECV_EXCEPTION)).contents
+ self.handle_exception_event(exc)
+
+ elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID:
+ pObjData = cast(pData, POINTER(SIMCONNECT_RECV_ASSIGNED_OBJECT_ID)).contents
+ objectId = pObjData.dwObjectID
+ os.environ["SIMCONNECT_OBJECT_ID"] = str(objectId)
+
+ elif (
+ (dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_AIRPORT_LIST)
+ or (dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_WAYPOINT_LIST)
+ or (dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_NDB_LIST)
+ or (dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_VOR_LIST)
+ ):
+ pObjData = cast(pData, POINTER(SIMCONNECT_RECV_FACILITIES_LIST)).contents
+ dwEntryNumber = pObjData.dwEntryNumber
+ dwOutof = pObjData.dwOutOf
+ if verbose:
+ syslog.info("dispatch: receive facility")
+ # for _facility in self.Facilities:
+ # if dwRequestID == _facility.REQUEST_ID.value:
+ # _facility.parent.dump(pData)
+ # _facility.dump(pData)
+
+ # fire an update event new data was received
+ if dwEntryNumber == dwOutof:
+ self.handler.simconnect_FacilitiesReceived.emit(self.Facilities)
+
+ elif (
+ dwID
+ == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST
+ ):
+ pObjData = cast(
+ pData, POINTER(SIMCONNECT_RECV_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST)
+ ).contents
+ dwEntryNumber = pObjData.dwEntryNumber
+ dwOutof = pObjData.dwOutOf
+ count = pObjData.dwArraySize
+ if verbose:
+ syslog.info(f"dispatch: received {count} aicraft livery item(s)")
+ items = SIMCONNECT_RECV_ENUMERATE_SIMOBJECT_AND_LIVERY_LIST_FACTORY(count)
+ pObjData = cast(pData, POINTER(items)).contents
+ for item in pObjData.rgData:
+ aircraft = item.AircraftTitle.decode()
+ livery = item.LiveryName.decode()
+ if aircraft not in self.AicraftLiveries:
+ self.AicraftLiveries[aircraft] = []
+ self.AicraftLiveries[aircraft].append(livery)
+ if verbose:
+ syslog.info(f"\t{aircraft} / {livery}")
+
+ # fire an update event new data was received
+ if dwEntryNumber + 1 == dwOutof:
+ # all items received - fire the update
+ self.handler.simconnect_AircraftLiveriesReceived.emit(
+ self.AicraftLiveries
+ )
+
+ elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_QUIT:
+ self._quit = 1
+ elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_EVENT_FILENAME:
+ # file name
+
+ pObjData = cast(pData, POINTER(SIMCONNECT_RECV_EVENT_FILENAME)).contents
+ file = pObjData.zFileName.decode()
+ folder = os.path.dirname(file)
+ if verbose:
+ syslog.info(f"dispatch: receive filename: {folder}")
+ # example: c:\users\XXX\appdata\local\packages\microsoft.limitless_8wekyb3d8bbwe\localstate\streamedpackages\asobo-aircraft-h125\content\simobjects\airplanes\asobo_h125\presets\asobo\h125_rescue\config
+ self.handle_folder_event(folder)
+
+ elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_SIMOBJECT_DATA:
+ # data
+ if verbose:
+ syslog.info("dispatch: receive simobject data")
+ pObjData = cast(pData, POINTER(SIMCONNECT_RECV_SIMOBJECT_DATA)).contents
+ self.handle_simobject_event(pObjData)
+ elif dwID == SIMCONNECT_RECV_ID.SIMCONNECT_RECV_ID_CLIENT_DATA:
+ # client data
+ # syslog.info("dispatch: received client data")
+ pObjData = cast(pData, POINTER(SIMCONNECT_RECV_CLIENT_DATA)).contents
+ # syslog.info(f"received client data: request ID: {pObjData.dwRequestID} define ID: {pObjData.dwDefineID} ")
+ self.handle_clientdata_event(pData)
+
+ else:
+ if verbose:
+ # syslog = logging.getLogger("system")
+ syslog.warning(f"SIMCONNECT:Received: id {dwID}")
+ return
+
+ def _find_dll(self):
+ """finds the simconnect DLL"""
+ dll_file = "SimConnect.dll"
+
+ # executable path
+ dll_folder = gremlin.shared_state.root_path
+ dll_test = os.path.join(dll_folder, dll_file)
+ if os.path.isfile(dll_test):
+ return dll_test
+
+ # current .py folder
+ dll_folder = os.path.dirname(__file__)
+ dll_test = os.path.join(dll_folder, dll_file)
+ if os.path.isfile(dll_test):
+ return dll_test
+
+ # one level up
+ dll_folder = os.path.dirname(dll_folder)
+ dll_test = os.path.join(dll_folder, dll_file)
+ if os.path.isfile(dll_test):
+ return dll_test
+
+ return None
+
+ @QtCore.Slot()
+ def _sync_callback(self):
+ handler = SimConnectEventHandler()
+ handler.status_callback_clicked.emit()
+
+ def connect(self):
+ from gremlin.util import display_error
+
+ if self._connecting:
+ return
+
+ # syslog = logging.getLogger("system")
+ verbose = gremlin.config.Configuration().verbose_mode_simconnect
+ if verbose:
+ syslog.info("SIMCONNECT: connect...")
+
+ self._connecting = True
+ self._request_abort = False
+
+ try:
+ if self._is_connected:
+ # already connected
+ if verbose:
+ syslog.info("\talready connected")
+ return True
+
+ el = gremlin.event_handler.EventListener()
+ el.module_state_register.emit(
+ "simconnect", "SimConnect", None, self._sync_callback
+ )
+ if not self._dll_path or not os.path.isfile(self._dll_path):
+ if not self._library_path:
+ # locate the simconnect dll file based on the distro
+ self._dll_path = self._find_dll()
+ if not os.path.isfile(self._dll_path):
+ msg = f"Unable to continue - missing dll: {self._dll_path}"
+ display_error(msg)
+ syslog.critical(
+ msg
+ ) # issue a critical error because the DLL should be with the distribution
+ os._exit(1)
+
+ if verbose:
+ syslog.info(f"\tUsing dll : {self._dll_path}")
+ self._library_path = self._dll_path
+
+ self._is_loop_running = False
+ self._quit = 0
+
+ if not self._win_dll:
+ # dll is not loaded
+
+ try:
+ self._win_dll = windll.LoadLibrary(self._dll_path)
+ syslog.error("\tDLL load: ok")
+ except Exception as err:
+ self._request_abort = True
+ syslog.error(f"SIMCONNECT:DLL load error: {err}")
+ self._quit = 1
+
+ el.request_profile_stop.emit("Error loading DLL")
+ return False
+
+ if self._win_dll:
+ if verbose:
+ syslog.info(f"\tUsing win dll : {self._win_dll}")
+
+ self._dll = SimConnectDll(self._win_dll)
+
+ self._my_dispatch_proc_rd = self._dll.DispatchProc(
+ self.simconnect_dispatch_proc
+ )
+ try:
+ if verbose:
+ syslog.info("\tloading dll...")
+ err = self._dll.Open(
+ byref(self._hSimConnect), LPCSTR(b"GremlinEx"), None, 0, 0, 0
+ )
+ except:
+ # likely not running
+ syslog.warning("SIMCONNECT: MSFS not running or missing DLL")
+ err = 1
+ if self.IsHR(err, 0):
+ syslog.info("\tConnected to MSFS - hr code 0")
+ # Request an event when the simulation starts
+
+ self._is_connected = True
+
+ # The user is in control of the aircraft
+ self._dll.SubscribeToSystemEvent(
+ self._hSimConnect,
+ self._dll.EventID.EVENT_SIM_START.value,
+ b"SimStart",
+ )
+ self._dll.SetSystemEventState(
+ self._hSimConnect,
+ self._dll.EventID.EVENT_SIM_START,
+ SIMCONNECT_STATE.SIMCONNECT_STATE_ON,
+ )
+
+ # The user is navigating the UI.
+ self._dll.SubscribeToSystemEvent(
+ self._hSimConnect,
+ self._dll.EventID.EVENT_SIM_STOP.value,
+ b"SimStop",
+ )
+ self._dll.SetSystemEventState(
+ self._hSimConnect,
+ self._dll.EventID.EVENT_SIM_STOP.value,
+ SIMCONNECT_STATE.SIMCONNECT_STATE_ON,
+ )
+
+ # Request a notification when the flight is paused
+ self._dll.SubscribeToSystemEvent(
+ self._hSimConnect,
+ self._dll.EventID.EVENT_SIM_PAUSE_STATE.value,
+ b"Pause",
+ )
+ self._dll.SetSystemEventState(
+ self._hSimConnect,
+ self._dll.EventID.EVENT_SIM_PAUSE_STATE.value,
+ SIMCONNECT_STATE.SIMCONNECT_STATE_ON,
+ )
+
+ # Request a notification when the flight is un-paused.
+ self._dll.SubscribeToSystemEvent(
+ self._hSimConnect,
+ self._dll.EventID.EVENT_SIM_RUNNING.value,
+ b"Sim",
+ )
+ # aircraft loaded data
+ self._dll.SubscribeToSystemEvent(
+ self._hSimConnect,
+ self._dll.EventID.EVENT_SIM_REQUEST_AIRCRAFT.value,
+ b"AircraftLoaded",
+ )
+
+ syslog.info("\tinterface connected")
+ self.run()
+
+ else:
+ # error
+ self._request_abort = True
+ return False
+
+ el.module_state_change.emit("simconnect", True)
+ return True
+ finally:
+ self._connecting = False
+
+ def run(self):
+ if not self._is_loop_running and self._runThread is None:
+ self._runThread = threading.Thread(target=self._run)
+ self._runThread.setName("Simconnect Run")
+ self._runThread.daemon = True
+ self._runThread.start()
+ while self.ok is False:
+ pass
+
+ def _run(self):
+ self.handler.simconnect_connected.emit()
+ self._is_loop_running = True
+ # syslog = logging.getLogger("system")
+ verbose = gremlin.config.Configuration().verbose_mode_simconnect
+ if verbose:
+ syslog.info("SIMCONNECT: run loop start")
+ error_count = 5
+ self._quit = 0 # keep on running until stop
+ while self._quit == 0:
+ try:
+ self._dll.CallDispatch(
+ self._hSimConnect, self._my_dispatch_proc_rd, None
+ )
+ time.sleep(0.002)
+
+ except:
+ error_count -= 1
+ if error_count == 0:
+ self._quit = True
+ # close the connection
+ try:
+ if self._dll:
+ self._dll.Close(self._hSimConnect)
+ except:
+ pass
+ self._dll = None
+ self._my_dispatch_proc_rd = None
+ try:
+ self.handler.simconnect_disconnected.emit()
+ except:
+ pass
+
+ self._is_connected = False
+ if verbose:
+ syslog.info("SIMCONNECT: run loop end")
+ self._is_loop_running = False
+
+ def disconnect(self):
+ """disconnects from the sim"""
+ self.exit()
+ self._last_loaded_aircraft = None
+ self._last_loaded_aircraft_cfg = None
+
+ def exit(self):
+ """disconnects from the sim"""
+ if self._is_loop_running:
+ # syslog = logging.getLogger("system")
+ verbose = gremlin.config.Configuration().verbose_mode_simconnect
+ if verbose:
+ syslog.info("SIMCONNECT: exit requested")
+ self._quit = 1 # kill the thread loop
+ self._runThread.join()
+ # this also resets the flags
+ self._runThread = None
+ self._is_loop_running = False
+ el = gremlin.event_handler.EventListener()
+ el.module_state_change.emit("simconnect", None)
+
+ def is_connected(self, auto_connect=True):
+ """determines if connected and optionally starts the connection"""
+ if self._request_abort:
+ return False
+ return self.ok
+
+ def is_running(self, auto_connect=True):
+ """determines if the event loop is running or not"""
+ if self._request_abort:
+ return False
+ if not self._is_loop_running and auto_connect:
+ self.connect()
+
+ return self._is_loop_running
+
+ def map_to_sim_event(self, name):
+ # syslog = logging.getLogger("system")
+ if self._dll is not None:
+ for m in self._dll.EventID:
+ if name.decode() == m.name:
+ if self.verbose_details:
+ syslog.debug(f"SIMCONNECT: already have event: {name} {m}")
+ return m
+
+ names = [m.name for m in self._dll.EventID] + [name.decode()]
+ self._dll.EventID = Enum(self._dll.EventID.__name__, names)
+ evnt = list(self._dll.EventID)[-1]
+ try:
+ err = self._dll.MapClientEventToSimEvent(
+ self._hSimConnect, evnt.value, name
+ )
+ if self.IsHR(err, 0):
+ return evnt
+ except:
+ syslog.error(f"SIMCONNECT:Error: MapToSimEvent error: event: {err}")
+ pass
+
+ syslog.error(f"SIMCONNECT:Error: MapToSimEvent: not connected event: {name}")
+ return None
+
+ def add_to_notification_group(self, group, event, maskable: bool = False):
+ if self._dll is not None:
+ self._dll.AddClientEventToNotificationGroup(
+ self._hSimConnect, group, event, maskable
+ )
+
+ def _request_data(self, request: Request):
+ if self._dll is not None:
+ request.buffer = None
+ if request.is_client_data:
+ self._dll.RequestClientData(
+ self._hSimConnect,
+ request.CLIENT_DATA_ID.value,
+ request.DATA_REQUEST_ID.value,
+ request.DATA_DEFINITION_ID.value,
+ SIMCONNECT_PERIOD.SIMCONNECT_PERIOD_ONCE,
+ SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT,
+ )
+ else:
+ self._dll.RequestDataOnSimObjectType(
+ self._hSimConnect,
+ request.DATA_REQUEST_ID.value,
+ request.DATA_DEFINITION_ID.value,
+ 0,
+ SIMCONNECT_SIMOBJECT_TYPE.SIMCONNECT_SIMOBJECT_TYPE_USER,
+ )
+ temp = DWORD(0)
+ self._dll.GetLastSentPacketID(self._hSimConnect, temp)
+ request.LastID = temp.value
+
+ def _request_periodic_data(self, request: Request):
+ if self._dll is not None:
+ request.buffer = None
+ if request.is_client_data:
+ self._dll.RequestClientData(
+ self._hSimConnect,
+ request.CLIENT_DATA_ID.value,
+ request.DATA_REQUEST_ID.value,
+ request.DATA_DEFINITION_ID.value,
+ SIMCONNECT_PERIOD.SIMCONNECT_PERIOD_VISUAL_FRAME,
+ SIMCONNECT_DATA_REQUEST_FLAG.SIMCONNECT_DATA_REQUEST_FLAG_CHANGED,
+ 0, # origin The number of times the data should be transmitted before this communication is ended. The default is zero, which means the data should be transmitted endlessly.
+ 0, # interval The number of times the data should be transmitted before this communication is ended. The default is zero, which means the data should be transmitted endlessly.
+ 0, # limit The number of times the data should be transmitted before this communication is ended. The default is zero, which means the data should be transmitted endlessly.
+ )
+ else:
+ self._dll.RequestDataOnSimObject(
+ self._hSimConnect,
+ request.DATA_REQUEST_ID.value,
+ request.DATA_DEFINITION_ID.value,
+ 0, # object ID 0 = aircraft
+ SIMCONNECT_PERIOD.SIMCONNECT_PERIOD_VISUAL_FRAME, # specifies how often the data is to be sent by the server and received by the client
+ SIMCONNECT_DATA_REQUEST_FLAG.SIMCONNECT_DATA_REQUEST_FLAG_CHANGED, # 0 or changed or tagged
+ 0, # origin The number of times the data should be transmitted before this communication is ended. The default is zero, which means the data should be transmitted endlessly.
+ 0, # interval The number of times the data should be transmitted before this communication is ended. The default is zero, which means the data should be transmitted endlessly.
+ 0, # limit The number of times the data should be transmitted before this communication is ended. The default is zero, which means the data should be transmitted endlessly.
+ )
+ temp = DWORD(0)
+ self._dll.GetLastSentPacketID(self._hSimConnect, temp)
+ request.LastID = temp.value
+
+ def stop_periodic_data(self, request):
+ """stops a request for periodic data setup with request_periodic_data"""
+ if self._dll is not None:
+ request.buffer = None
+ if request.is_client_data:
+ self._dll.RequestClientData(
+ self._hSimConnect,
+ request.CLIENT_DATA_ID.value,
+ request.DATA_REQUEST_ID.value,
+ request.DATA_DEFINITION_ID.value,
+ SIMCONNECT_PERIOD.SIMCONNECT_PERIOD_NEVER,
+ SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT,
+ )
+
+ else:
+ self._dll.RequestDataOnSimObject(
+ self._hSimConnect,
+ request.DATA_REQUEST_ID.value,
+ request.DATA_DEFINITION_ID.value,
+ 0,
+ SIMCONNECT_PERIOD.SIMCONNECT_PERIOD_NEVER,
+ )
+
+ def clear(self, request):
+ """clears a request"""
+ if self._dll is not None:
+ self._dll.ClearClientDataDefinition(
+ self._hSimConnect, request.DATA_DEFINITION_ID.value
+ )
+
+ def set_data(self, request: Request):
+ if self._dll is None:
+ self.connect()
+ if self._dll is None:
+ syslog.warning(
+ f"SIMCONNECT:Setdata: not connected - request : {request.definitions}"
+ )
+ return False
+ if request.buffer is None:
+ return False
+ rtype = request.definitions[0][1].decode()
+ if "string" in rtype.lower():
+ pyarr = bytearray(request.buffer)
+ dataarray = (ctypes.c_char * len(pyarr))(*pyarr)
+ else:
+ pyarr = list([request.buffer])
+ dataarray = (ctypes.c_double * len(pyarr))(*pyarr)
+
+ pObjData = cast(dataarray, c_void_p)
+ err = self._dll.SetDataOnSimObject(
+ self._hSimConnect,
+ request.DATA_DEFINITION_ID.value,
+ SIMCONNECT_SIMOBJECT_TYPE.SIMCONNECT_SIMOBJECT_TYPE_USER,
+ SIMCONNECT_DATA_SET_FLAG.SIMCONNECT_DATA_SET_FLAG_DEFAULT,
+ 1, # one element
+ sizeof(ctypes.c_double) * len(pyarr), # size of the element in bytes
+ pObjData,
+ )
+ if self.IsHR(err, 0):
+ # LOGGER.debug("Request Sent")
+ return True
+ else:
+ return False
+
+ def get_data(self, request):
+ self._request_data(request)
+ retries = 0
+ while request.buffer is None and retries < request.attempts:
+ time.sleep(0.01)
+ retries += 1
+ if request.buffer is None:
+ if self.verbose:
+ syslog.warning(f"SIMCONNECT:warning: timeout in request {request}")
+ return False
+ return True
+
+ def get_periodic_data(self, request):
+ """requests periodic data (data will be sent on change via the event system)"""
+ self._request_periodic_data(request)
+
+ def send_event(self, evnt, data=DWORD(0)):
+ if self._dll is None:
+ return False
+ try:
+ err = self._dll.TransmitClientEvent(
+ self._hSimConnect,
+ SIMCONNECT_OBJECT_ID_USER,
+ evnt.value,
+ data,
+ SIMCONNECT_GROUP_PRIORITY_HIGHEST,
+ SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY, # DWORD(16),
+ )
+
+ if self.IsHR(err, 0):
+ # LOGGER.debug("Event Sent")
+ return True
+ else:
+ return False
+ except:
+ pass
+ return False
+
+ def new_client_data_definition_id(self):
+ if self._dll is None:
+ return None
+
+ _name = "ClientDataDef_" + str(len(list(self._dll.CLIENT_DATA_DEFINITION_ID)))
+ names = [m.name for m in self._dll.CLIENT_DATA_DEFINITION_ID] + [_name]
+ self._dll.CLIENT_DATA_DEFINITION_ID = Enum(
+ self._dll.CLIENT_DATA_DEFINITION_ID.__name__, names
+ )
+ CLIENT_DATA_DEFINITION_ID = list(self._dll.CLIENT_DATA_DEFINITION_ID)[-1]
+ return CLIENT_DATA_DEFINITION_ID
+
+ def new_def_id(self):
+ if self._dll is None:
+ return None
+
+ _name = "Definition_" + str(len(list(self._dll.DATA_DEFINITION_ID)))
+ names = [m.name for m in self._dll.DATA_DEFINITION_ID] + [_name]
+
+ self._dll.DATA_DEFINITION_ID = Enum(
+ self._dll.DATA_DEFINITION_ID.__name__, names
+ )
+ DEFINITION_ID = list(self._dll.DATA_DEFINITION_ID)[-1]
+ return DEFINITION_ID
+
+ def new_client_data_id(self):
+ if self._dll is None:
+ return None
+
+ _name = "ClientData_" + str(len(list(self._dll.CLIENT_DATA_ID)))
+ names = [m.name for m in self._dll.CLIENT_DATA_ID] + [_name]
+
+ self._dll.CLIENT_DATA_ID = Enum(self._dll.CLIENT_DATA_ID.__name__, names)
+ CLIENT_ID = list(self._dll.CLIENT_DATA_ID)[-1]
+ return CLIENT_ID
+
+ def new_request_id(self):
+ if self._dll is None:
+ return None
+
+ name = "Request_" + str(len(self._dll.DATA_REQUEST_ID))
+ names = [m.name for m in self._dll.DATA_REQUEST_ID] + [name]
+ self._dll.DATA_REQUEST_ID = Enum(self._dll.DATA_REQUEST_ID.__name__, names)
+ REQUEST_ID = list(self._dll.DATA_REQUEST_ID)[-1]
+
+ return REQUEST_ID
+
+ def add_waypoints(self, _waypointlist):
+ if self._dll is None:
+ return
+ if self.DEFINITION_WAYPOINT is None:
+ self.DEFINITION_WAYPOINT = self.new_def_id()
+ self._dll.AddToDataDefinition(
+ self._hSimConnect,
+ self.DEFINITION_WAYPOINT.value,
+ b"AI WAYPOINT LIST",
+ b"number",
+ SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_WAYPOINT,
+ 0,
+ SIMCONNECT_UNUSED,
+ )
+ pyarr = []
+ for waypt in _waypointlist:
+ for e in waypt._fields_:
+ pyarr.append(getattr(waypt, e[0]))
+ dataarray = (ctypes.c_double * len(pyarr))(*pyarr)
+ cast(dataarray, c_void_p)
+ int(sizeof(ctypes.c_double) * (len(pyarr) / len(_waypointlist)))
+ return
+
+ def set_pos(
+ self,
+ _Altitude,
+ _Latitude,
+ _Longitude,
+ _Airspeed,
+ _Pitch=0.0,
+ _Bank=0.0,
+ _Heading=0,
+ _OnGround=0,
+ ):
+ if self._dll is None:
+ return False
+
+ Init = SIMCONNECT_DATA_INITPOSITION()
+ Init.Altitude = _Altitude
+ Init.Latitude = _Latitude
+ Init.Longitude = _Longitude
+ Init.Pitch = _Pitch
+ Init.Bank = _Bank
+ Init.Heading = _Heading
+ Init.OnGround = _OnGround
+ Init.Airspeed = _Airspeed
+
+ if self.DEFINITION_POS is None:
+ self.DEFINITION_POS = self.new_def_id()
+ self._dll.AddToDataDefinition(
+ self._hSimConnect,
+ self.DEFINITION_POS.value,
+ b"Initial Position",
+ b"",
+ SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_INITPOSITION,
+ 0,
+ SIMCONNECT_UNUSED,
+ )
+
+ hr = self._dll.SetDataOnSimObject(
+ self._hSimConnect,
+ self.DEFINITION_POS.value,
+ SIMCONNECT_OBJECT_ID_USER,
+ 0,
+ 0,
+ sizeof(Init),
+ pointer(Init),
+ )
+ if self.IsHR(hr, 0):
+ return True
+ else:
+ return False
+
+ def load_flight_plan(self, pln_path):
+ if self._dll is None:
+ return False
+ hr = self._dll.FlightPlanLoad(self._hSimConnect, pln_path.encode())
+ if self.IsHR(hr, 0):
+ return True
+ else:
+ return False
+
+ # def save_flight(
+ # self,
+ # flt_path,
+ # flt_title,
+ # flt_description,
+ # flt_mission_type='FreeFlight',
+ # flt_mission_location='Custom departure',
+ # flt_original_flight='',
+ # flt_flight_type='NORMAL'):
+
+ # if self._dll is None:
+ # return False
+ # hr = self._dll.FlightSave(self._hSimConnect, flt_path.encode(), flt_title.encode(), flt_description.encode(), 0)
+ # if not self.IsHR(hr, 0):
+ # return False
+
+ # dicp = self.flight_to_dic(flt_path)
+ # if 'MissionType' not in dicp['Main']:
+ # dicp['Main']['MissionType'] = flt_mission_type
+
+ # if 'MissionLocation' not in dicp['Main']:
+ # dicp['Main']['MissionLocation'] = flt_mission_location
+
+ # if 'FlightType' not in dicp['Main']:
+ # dicp['Main']['FlightType'] = flt_flight_type
+
+ # if 'OriginalFlight' not in dicp['Main']:
+ # dicp['Main']['OriginalFlight'] = flt_original_flight
+ # self.dic_to_flight(dicp, flt_path)
+
+ # return False
+
+ def get_paused(self):
+ if self._dll is None:
+ return False
+ self._dll.RequestSystemState(
+ self._hSimConnect, self._dll.EventID.EVENT_SIM_PAUSED, b"Sim"
+ )
+
+ def dic_to_flight(self, dic, fpath):
+ with open(fpath, "w") as tempfile:
+ for root in dic:
+ tempfile.write(f"\n[{root}]\n")
+ for member in dic[root]:
+ tempfile.write(f"{member}={dic[root][member]}\n")
+
+ def flight_to_dic(self, fpath):
+ while not os.path.isfile(fpath):
+ pass
+ time.sleep(0.5)
+ dic = {}
+ index = ""
+ with open(fpath, "r") as tempfile:
+ for line in tempfile.readlines():
+ if line[0] == "[":
+ index = line[1:-2]
+ dic[index] = {}
+ else:
+ if index != "" and line != "\n":
+ temp = line.split("=")
+ dic[index][temp[0]] = temp[1].strip()
+ return dic
+
+ def sendText(
+ self,
+ text,
+ timeSeconds=5,
+ TEXT_TYPE=SIMCONNECT_TEXT_TYPE.SIMCONNECT_TEXT_TYPE_PRINT_WHITE,
+ ):
+ if self._dll is None:
+ return False
+ pyarr = bytearray(text.encode())
+ dataarray = (ctypes.c_char * len(pyarr))(*pyarr)
+ pObjData = cast(dataarray, c_void_p)
+ self._dll.Text(
+ self._hSimConnect,
+ TEXT_TYPE,
+ timeSeconds,
+ 0,
+ sizeof(ctypes.c_double) * len(pyarr),
+ pObjData,
+ )
+
+ def createSimulatedObject(
+ self, name, lat, lon, rqst, hdg=0, gnd=1, alt=0, pitch=0, bank=0, speed=0
+ ):
+ if self._dll is None:
+ return False
+ simInitPos = SIMCONNECT_DATA_INITPOSITION()
+ simInitPos.Altitude = alt
+ simInitPos.Latitude = lat
+ simInitPos.Longitude = lon
+ simInitPos.Pitch = pitch
+ simInitPos.Bank = bank
+ simInitPos.Heading = hdg
+ simInitPos.OnGround = gnd
+ simInitPos.Airspeed = speed
+ self._dll.AICreateSimulatedObject(
+ self._hSimConnect, name.encode(), simInitPos, rqst.value
+ )
+
+ def createClientData(self, request_id, size=4096, flags=0):
+ """creates a user data area
+ https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_CreateClientData.htm
+ """
+ if self._dll is None:
+ return False
+ hr = self._dll.CreateClientData(self._hSimConnect, request_id, size, flags)
+ if not self.IsHR(hr, 0):
+ return False
+ return True
+
+ def addToClientDataDefinition(self, definition_id, offset, size):
+ """adds to the client data definition, returns true on success"""
+ if self._dll is None:
+ return False
+ hr = self._dll.AddToClientDataDefinition(
+ self._hSimConnect, definition_id, offset, size, 0, 0
+ )
+ if not self.IsHR(hr, 0):
+ return False
+ return True
+
+ def requestAircraftLoaded(self):
+ """makes a request for the current loaded aircraft"""
+ if self._dll:
+ hr = self._dll.RequestSystemState(
+ self._hSimConnect,
+ self._dll.EventID.EVENT_SIM_REQUEST_AIRCRAFT.value,
+ b"AircraftLoaded",
+ )
+ if not self.IsHR(hr, 0):
+ return False
+ return True
+ return False
+
+ def requestSimObjectsAndLiveries(self):
+ """makes a request for the user flyable aircraft list"""
+ if self._dll:
+ self.AicraftLiveries.clear()
+ hr = self._dll.EnumerateSimObjectsAndLiveries(
+ self._hSimConnect,
+ self._dll.EventID.EVENT_SIM_REQUEST_ENUMERATE_SIM_OBJECTS_AND_LIVERIES.value,
+ SIMCONNECT_SIMOBJECT_TYPE.SIMCONNECT_SIMOBJECT_TYPE_USER,
+ )
+ if not self.IsHR(hr, 0):
+ return False
+ return True
+ return False
+
+ def getSimObjects(self) -> list[str]:
+ """gets the current list of aircraft - gets the results of requestSimObjectsAndLiveries()"""
+ return list(self.AicraftLiveries.keys())
+
+ def save_flight(self, the_path: str, title: str = "", description: str = ""):
+ """save the current flight"""
+ if self._dll:
+ b_the_path = the_path.encode()
+ b_title = title.encode()
+ b_description = description.encode()
+ hr = self._dll.FlightSave(
+ self._hSimConnect, b_title, b_the_path, b_description, 0
+ )
+ if not self.IsHR(hr, 0):
+ return False
+ return True
+ return False
+
+ def load_flight(self, the_path: str):
+ """loads a saved flight"""
+ if self._dll:
+ b_the_path = the_path.encode()
+ hr = self._dll.FlightLoad(self._hSimConnect, b_the_path)
+ if not self.IsHR(hr, 0):
+ return False
+ return True
+ return False
diff --git a/action_plugins/map_to_simconnect/SimConnect/SimConnectBridge.py b/action_plugins/map_to_simconnect/SimConnect/SimConnectBridge.py
index 39d096f2..50b9d4a1 100644
--- a/action_plugins/map_to_simconnect/SimConnect/SimConnectBridge.py
+++ b/action_plugins/map_to_simconnect/SimConnect/SimConnectBridge.py
@@ -1,4 +1,3 @@
-
# -*- coding: utf-8; -*-
# Based on example at: https://github.com/theomessin/jetbridge
# (C) EMCS 2024 and other contributors
@@ -20,22 +19,15 @@
from __future__ import annotations
import logging
-import struct
-from time import sleep
from ctypes import *
-from ctypes.wintypes import FLOAT
import gremlin.event_handler
import gremlin.shared_state
from .Enum import *
import gremlin.config
from .SimConnect import SimConnect, SimConnectEventHandler
-from PySide6 import QtWidgets, QtCore, QtGui
+from PySide6 import QtCore
import threading
-import os
-import glob
-from gremlin.singleton_decorator import SingletonDecorator
-import copy
import time
kPacketDefinition = 6124
@@ -47,14 +39,14 @@
syslog = logging.getLogger("system")
+
class BridgeCommands(IntEnum):
ExecuteCalculatorCode = 0
GetNamedVariable = 1
GetVariableList = 2
Ping = 3
GetAircraftList = 4
- SimConnectError = 5 # occurs when a simconnect error occurs inside the WASM module
-
+ SimConnectError = 5 # occurs when a simconnect error occurs inside the WASM module
kPublicDownlinkChannel = b"muchimi.gremlinex.downlink"
@@ -62,31 +54,34 @@ class BridgeCommands(IntEnum):
class BRIDGE_PACKET(Structure):
- _fields_ = [
- ("id", INT), # id - integer
- ("code", INT), # command code
- ("data", c_char * kPacketDataSize), # string max KPacketSize
- ]
+ _fields_ = [
+ ("id", INT), # id - integer
+ ("code", INT), # command code
+ ("data", c_char * kPacketDataSize), # string max KPacketSize
+ ]
class BRIDGE_PACKET_DOUBLE(Structure):
- _fields_ = [
- ("id", INT), # id - integer
- ("code", INT), # command code
+ _fields_ = [
+ ("id", INT), # id - integer
+ ("code", INT), # command code
("data", DOUBLE), # floating point value return data
- ]
+ ]
kPacketSize = sizeof(BRIDGE_PACKET)
+
class SimConnectBridge(QtCore.QObject):
- ''' Simconnect bridge for GremlinEx '''
+ """Simconnect bridge for GremlinEx"""
- lvars_loaded = QtCore.Signal(object) # sent when lvars are received
- aircraft_list_loaded = QtCore.Signal(object) # sends the aircraft list (map of [aircraft][list of liveries] )
- alive = QtCore.Signal() # sent when pong is received (alive signal)
+ lvars_loaded = QtCore.Signal(object) # sent when lvars are received
+ aircraft_list_loaded = QtCore.Signal(
+ object
+ ) # sends the aircraft list (map of [aircraft][list of liveries] )
+ alive = QtCore.Signal() # sent when pong is received (alive signal)
- def __init__(self, sm : SimConnect):
+ def __init__(self, sm: SimConnect):
super().__init__()
self.sm = sm
@@ -94,13 +89,15 @@ def __init__(self, sm : SimConnect):
handler = SimConnectEventHandler()
handler.status_callback_clicked.connect(self._sync_bridge)
self._started = False
- self._alive = False # true if alive (pong command received)
- self._id = 0
- self._lvars = [] # list of received lvars
- self._aircraft_map = {} # list of aicrafts keyed by aircraft name, holds liveries as a list
- self._state = None # response state
- self._wait_event = threading.Event() # wait event
- self._wait_alive_event = threading.Event() # alive wait event when sending a ping
+ self._alive = False # true if alive (pong command received)
+ self._id = 0
+ self._lvars = [] # list of received lvars
+ self._aircraft_map = {} # list of aicrafts keyed by aircraft name, holds liveries as a list
+ self._state = None # response state
+ self._wait_event = threading.Event() # wait event
+ self._wait_alive_event = (
+ threading.Event()
+ ) # alive wait event when sending a ping
el = gremlin.event_handler.EventListener()
self._alive_thread = None
self._connect_in_progress = False
@@ -112,42 +109,52 @@ def start(self):
# attempt a ping in case the first one didn' work
self.ping()
return
-
+
# syslog = logging.getLogger("system")
try:
- syslog.info(f"SIMCONNECT BRIDGE: starting...")
+ syslog.info("SIMCONNECT BRIDGE: starting...")
self.sm.register_client_data_handler(self.client_data_callback_handler)
- self.sm._dll.AddToClientDataDefinition(self.sm._hSimConnect, kPacketDefinition, 0, kPacketSize, 0.0, SIMCONNECT_UNUSED)
- self.sm._dll.MapClientDataNameToID(self.sm._hSimConnect, kPublicDownlinkChannel, kPublicDownlinkArea)
- self.sm._dll.MapClientDataNameToID(self.sm._hSimConnect, kPublicUplinkChannel, kPublicUplinkArea)
- self.sm._dll.RequestClientData(self.sm._hSimConnect,
- kPublicDownlinkArea,
- kDownlinkRequest,
- kPacketDefinition,
- SIMCONNECT_CLIENT_DATA_PERIOD.SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET,
- SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_CHANGED,
- 0,
- 0,
- 0)
-
-
+ self.sm._dll.AddToClientDataDefinition(
+ self.sm._hSimConnect,
+ kPacketDefinition,
+ 0,
+ kPacketSize,
+ 0.0,
+ SIMCONNECT_UNUSED,
+ )
+ self.sm._dll.MapClientDataNameToID(
+ self.sm._hSimConnect, kPublicDownlinkChannel, kPublicDownlinkArea
+ )
+ self.sm._dll.MapClientDataNameToID(
+ self.sm._hSimConnect, kPublicUplinkChannel, kPublicUplinkArea
+ )
+ self.sm._dll.RequestClientData(
+ self.sm._hSimConnect,
+ kPublicDownlinkArea,
+ kDownlinkRequest,
+ kPacketDefinition,
+ SIMCONNECT_CLIENT_DATA_PERIOD.SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET,
+ SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_CHANGED,
+ 0,
+ 0,
+ 0,
+ )
+
self._started = True
- # send the ping command
+ # send the ping command
self._alive = False
self.ping()
-
except Exception as err:
syslog.error(f"SIMCONNECT BRIDGE: start error: {err}")
pass
@property
- def is_alive(self)->bool:
- ''' true if connnected '''
+ def is_alive(self) -> bool:
+ """true if connnected"""
return self._alive
-
def stop(self):
if not self._started:
return
@@ -155,17 +162,27 @@ def stop(self):
# syslog = logging.getLogger("system")
syslog.info("SIMCONNECT BRIDGE: stop")
try:
- self.sm.unregister_client_data_handler(self.client_data_callback_handler)
+ self.sm.unregister_client_data_handler(
+ self.client_data_callback_handler
+ )
if self.sm._dll:
- self.sm._dll.RequestClientData(self.sm._hSimConnect, kPublicDownlinkArea, kDownlinkRequest, kPacketDefinition,
- SIMCONNECT_CLIENT_DATA_PERIOD.SIMCONNECT_CLIENT_DATA_PERIOD_NEVER,
- SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT, 0,0,0)
+ self.sm._dll.RequestClientData(
+ self.sm._hSimConnect,
+ kPublicDownlinkArea,
+ kDownlinkRequest,
+ kPacketDefinition,
+ SIMCONNECT_CLIENT_DATA_PERIOD.SIMCONNECT_CLIENT_DATA_PERIOD_NEVER,
+ SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT,
+ 0,
+ 0,
+ 0,
+ )
except:
pass
-
+
self._started = False
- self._connect_in_progress = False # if we are aborting
-
+ self._connect_in_progress = False # if we are aborting
+
def _get_next_id(self):
# gets the next packet ID
id = self._id
@@ -176,65 +193,63 @@ def _get_next_id(self):
@QtCore.Slot()
def _shutdown(self):
- ''' terminate issued '''
+ """terminate issued"""
self.stop()
@QtCore.Slot()
def _sync_bridge(self):
- ''' request to sync the bridge '''
+ """request to sync the bridge"""
# syslog = logging.getLogger("system")
syslog.info("SIMCONNECT BRIDGE: sync requested")
if not self.connected:
# not started or alive
self.start()
-
@property
def connected(self):
return self._started and self._alive
# simconnect library callback
def client_data_callback_handler(self, pData):
- ''' processes received simconnect data to see if it came from mobiflight '''
+ """processes received simconnect data to see if it came from mobiflight"""
# syslog = logging.getLogger("system")
client_data = cast(pData, POINTER(SIMCONNECT_RECV_CLIENT_BYTE_DATA)).contents
- #client_data = copy.deepcopy(data)
+ # client_data = copy.deepcopy(data)
-
- #syslog.info(f"client data callback: define id: {client_data.dwDefineID}")
- if client_data.dwRequestID == kDownlinkRequest:
+ # syslog.info(f"client data callback: define id: {client_data.dwDefineID}")
+ if client_data.dwRequestID == kDownlinkRequest:
# mobiflight core client data received on MobiFlight client registration
packet = cast(client_data.dwData, POINTER(BRIDGE_PACKET)).contents
match packet.code:
case BridgeCommands.SimConnectError:
- data = packet.data.decode('ascii',errors='replace')
- data = data.replace('\ufffd','')
+ data = packet.data.decode("ascii", errors="replace")
+ data = data.replace("\ufffd", "")
syslog.error(f"WASM: error: {data}")
case BridgeCommands.Ping:
- data = packet.data.decode('ascii',errors='replace')
- data = data.replace('\ufffd','') # remove junk characters
+ data = packet.data.decode("ascii", errors="replace")
+ data = data.replace("\ufffd", "") # remove junk characters
if data == "#pong#":
- syslog.info(f"SIMCONNECT BRIDGE: received pong alive")
+ syslog.info("SIMCONNECT BRIDGE: received pong alive")
if self._alive_thread and self._alive_thread.is_alive():
self._connect_in_progress = False
- self._alive_thread.join() # wait for it to finish
+ self._alive_thread.join() # wait for it to finish
self._alive_thread = None
self._alive = True
- self.alive.emit() # report the bridge is alive
+ self.alive.emit() # report the bridge is alive
case BridgeCommands.GetNamedVariable:
# named variable
- packet = cast(client_data.dwData, POINTER(BRIDGE_PACKET_DOUBLE)).contents
- value = packet.data # double
- #syslog.info(f"SIMCONNECT BRIDGE: received value: {value}")
-
+ packet = cast(
+ client_data.dwData, POINTER(BRIDGE_PACKET_DOUBLE)
+ ).contents
+ # syslog.info(f"SIMCONNECT BRIDGE: received value: {value}")
+
case BridgeCommands.GetVariableList:
data = packet.data.decode()
-
if data == "#lvar_begin#":
self._lvars.clear()
self._state = "loading"
@@ -243,173 +258,189 @@ def client_data_callback_handler(self, pData):
self._state = "complete"
elif self._state == "loading":
- self._lvars.append(data)
-
+ self._lvars.append(data)
+
if self._state == "complete":
- thread = threading.Thread(target = lambda: self.lvars_loaded.emit(self._lvars), daemon=True)
+ thread = threading.Thread(
+ target=lambda: self.lvars_loaded.emit(self._lvars),
+ daemon=True,
+ )
thread.start()
self._state = None
-
+
case BridgeCommands.ExecuteCalculatorCode:
# mark done executing the command
- self._wait_event.set()
-
+ self._wait_event.set()
case BridgeCommands.GetAircraftList:
# gets aircraft data in the format aircraft_name###livery
- data = packet.data.decode('ascii',errors='replace')
+ data = packet.data.decode("ascii", errors="replace")
if data == "#ac_begin#":
self._aircraft_map.clear()
self._state = "loading"
elif data == "#ac_end#":
self._state = "complete"
- thread = threading.Thread(target = lambda: self.aircraft_list_loaded.emit(self._aircraft_map), daemon=True)
+ thread = threading.Thread(
+ target=lambda: self.aircraft_list_loaded.emit(
+ self._aircraft_map
+ ),
+ daemon=True,
+ )
thread.start()
elif self._state == "loading":
splits = data.split("###")
aircraft = splits[0]
livery = splits[1]
- if not aircraft in self._aircraft_map:
+ if aircraft not in self._aircraft_map:
self._aircraft_map[aircraft] = []
self._aircraft_map[aircraft].append(livery)
-
-
-
-
# find the terminating zero
data = packet.data
- #syslog.info(f"SIMCONNECT BRIDGE: received data: {data}")
-
-
+ # syslog.info(f"SIMCONNECT BRIDGE: received data: {data}")
-
-
def execute_calculator_code(self, command):
- ''' executes an RPN expression '''
+ """executes an RPN expression"""
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_simconnect
- #verbose = False
+ # verbose = False
# if self._wait_event.is_set():
# # currently executing another command - ignore
# if verbose: syslog.info("execute: already executing")
# return
try:
- id = self._get_next_id() # id is sequential so it's unique for each call and will roundrobin
+ id = (
+ self._get_next_id()
+ ) # id is sequential so it's unique for each call and will roundrobin
data = command.encode("ascii")
-
packet = BRIDGE_PACKET(id, BridgeCommands.ExecuteCalculatorCode, data)
packet_pointer = cast(pointer(packet), c_void_p)
-
- if verbose: syslog.info(f"SIMCONNECT BRIDGE: exec calculator code: [{command}]")
+
+ if verbose:
+ syslog.info(f"SIMCONNECT BRIDGE: exec calculator code: [{command}]")
self._wait_event.clear()
self.sm._dll.SetClientData(
self.sm._hSimConnect,
- kPublicUplinkArea,
+ kPublicUplinkArea,
kPacketDefinition,
SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT,
- 0, # dwReserved
- kPacketSize,
- packet_pointer)
-
+ 0, # dwReserved
+ kPacketSize,
+ packet_pointer,
+ )
+
# wait for the event
# self._wait_event.wait(0.1)
- if verbose: syslog.info(f"SIMCONNECT BRIDGE: {command} sent")
- #self._wait_event.clear()
+ if verbose:
+ syslog.info(f"SIMCONNECT BRIDGE: {command} sent")
+ # self._wait_event.clear()
except:
- syslog.error(f"SIMCONNECT BRIDGE: error executing calculator code: {command}")
+ syslog.error(
+ f"SIMCONNECT BRIDGE: error executing calculator code: {command}"
+ )
def get_variable(self, command):
- ''' gets a named variables '''
+ """gets a named variables"""
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_simconnect
try:
- id = self._get_next_id() # id is sequential so it's unique for each call and will roundrobin
+ id = (
+ self._get_next_id()
+ ) # id is sequential so it's unique for each call and will roundrobin
data = command.encode("ascii")
packet = BRIDGE_PACKET(id, BridgeCommands.GetNamedVariable, data)
packet_pointer = cast(pointer(packet), c_void_p)
self.sm._dll.SetClientData(
self.sm._hSimConnect,
- kPublicUplinkArea,
+ kPublicUplinkArea,
kPacketDefinition,
SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT,
- 0, # dwReserved
- kPacketSize,
- packet_pointer)
-
- if verbose: syslog.info(f"SIMCONNECT BRIDGE: get named variable: {command}")
+ 0, # dwReserved
+ kPacketSize,
+ packet_pointer,
+ )
+
+ if verbose:
+ syslog.info(f"SIMCONNECT BRIDGE: get named variable: {command}")
except:
syslog.error(f"SIMCONNECT BRIDGE: error getting named variable: {command}")
def ping(self):
- ''' sends the ping command '''
+ """sends the ping command"""
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_simconnect
if self._alive:
# already alive, ignore
return
if not self._connect_in_progress:
- if verbose: syslog.info("SIMCONNECT BRIDGE: handshake initiated...")
+ if verbose:
+ syslog.info("SIMCONNECT BRIDGE: handshake initiated...")
self._connect_in_progress = True
- self._alive_thread = threading.Thread(target = self._ping_runner, daemon=True)
+ self._alive_thread = threading.Thread(target=self._ping_runner, daemon=True)
self._alive_thread.start()
def _ping_runner(self):
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_simconnect
try:
- while self._connect_in_progress:
- if verbose: syslog.info(f"SIMCONNECT BRIDGE: (alive thread) send ping request")
- id = self._get_next_id() # id is sequential so it's unique for each call and will roundrobin
+ while self._connect_in_progress:
+ if verbose:
+ syslog.info("SIMCONNECT BRIDGE: (alive thread) send ping request")
+ id = (
+ self._get_next_id()
+ ) # id is sequential so it's unique for each call and will roundrobin
packet = BRIDGE_PACKET(id, BridgeCommands.Ping)
packet_pointer = cast(pointer(packet), c_void_p)
self.sm._dll.SetClientData(
self.sm._hSimConnect,
- kPublicUplinkArea,
+ kPublicUplinkArea,
kPacketDefinition,
SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT,
- 0, # dwReserved
- kPacketSize,
- packet_pointer)
- time.sleep(0.1) # give it time to respond
+ 0, # dwReserved
+ kPacketSize,
+ packet_pointer,
+ )
+ time.sleep(0.1) # give it time to respond
if verbose:
- if verbose: syslog.info(f"SIMCONNECT BRIDGE: handhake completed")
+ if verbose:
+ syslog.info("SIMCONNECT BRIDGE: handhake completed")
except:
- syslog.error(f"SIMCONNECT BRIDGE: error sending ping")
+ syslog.error("SIMCONNECT BRIDGE: error sending ping")
self._connect_in_progress = False
self._alive = False
-
-
- def _request_data(self, bridge_command : BridgeCommands):
- verbose = gremlin.config.Configuration().verbose_mode_simconnect
+ def _request_data(self, bridge_command: BridgeCommands):
+ gremlin.config.Configuration().verbose_mode_simconnect
try:
- id = self._get_next_id() # id is sequential so it's unique for each call and will roundrobin
+ id = (
+ self._get_next_id()
+ ) # id is sequential so it's unique for each call and will roundrobin
packet = BRIDGE_PACKET(id, bridge_command, b"")
packet_pointer = cast(pointer(packet), c_void_p)
self.sm._dll.SetClientData(
self.sm._hSimConnect,
- kPublicUplinkArea,
+ kPublicUplinkArea,
kPacketDefinition,
SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT,
- 0, # dwReserved
- kPacketSize,
- packet_pointer)
+ 0, # dwReserved
+ kPacketSize,
+ packet_pointer,
+ )
syslog.info(f"SIMCONNECT BRIDGE: get bridge data: {bridge_command.name}")
except:
- syslog.error(f"SIMCONNECT BRIDGE: error getting variable list: {bridge_command.name}")
-
+ syslog.error(
+ f"SIMCONNECT BRIDGE: error getting variable list: {bridge_command.name}"
+ )
def get_lvars(self):
- ''' gets the list of lvars from the sim '''
+ """gets the list of lvars from the sim"""
self._request_data(BridgeCommands.GetVariableList)
-
def getAircraftList(self):
- ''' gets the list of aircraft and liveries available to the user '''
- self._request_data(BridgeCommands.GetAircraftList)
\ No newline at end of file
+ """gets the list of aircraft and liveries available to the user"""
+ self._request_data(BridgeCommands.GetAircraftList)
diff --git a/action_plugins/map_to_simconnect/SimConnect/__init__.py b/action_plugins/map_to_simconnect/SimConnect/__init__.py
index ea8fd2ac..92a2bbbf 100644
--- a/action_plugins/map_to_simconnect/SimConnect/__init__.py
+++ b/action_plugins/map_to_simconnect/SimConnect/__init__.py
@@ -5,13 +5,22 @@
def int_or_str(value):
- try:
- return int(value)
- except TypeError:
- return value
+ try:
+ return int(value)
+ except TypeError:
+ return value
__version__ = "0.4.26"
VERSION = tuple(map(int_or_str, __version__.split(".")))
-__all__ = ["SimConnect", "Request", "Event", "millis", "DWORD", "AircraftRequests", "AircraftEvents", "FacilitiesRequests"]
+__all__ = [
+ "SimConnect",
+ "Request",
+ "Event",
+ "millis",
+ "DWORD",
+ "AircraftRequests",
+ "AircraftEvents",
+ "FacilitiesRequests",
+]
diff --git a/action_plugins/map_to_simconnect/SimConnectManager.py b/action_plugins/map_to_simconnect/SimConnectManager.py
index 98f95c4e..53f49e44 100644
--- a/action_plugins/map_to_simconnect/SimConnectManager.py
+++ b/action_plugins/map_to_simconnect/SimConnectManager.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -20,7 +20,7 @@
import shutil
from lxml import etree
-from PySide6 import QtWidgets, QtCore, QtGui
+from PySide6 import QtCore
import gremlin.base_classes
import gremlin.base_profile
@@ -31,11 +31,11 @@
import gremlin.ui.ui_common
import gremlin.ui.input_item
import gremlin.util
-import gremlin.util
from .SimConnect import *
from .SimConnect.SimConnect import *
from .SimConnect.Enum import *
-#from .SimConnect.MobiFlight import *
+
+# from .SimConnect.MobiFlight import *
from .SimConnect.SimConnectBridge import *
from gremlin.singleton_decorator import SingletonDecorator
import enum
@@ -45,32 +45,35 @@
syslog = logging.getLogger("system")
+
class DataThreadingEvent(threading.Event):
- ''' a threading event that can support a data value'''
+ """a threading event that can support a data value"""
+
def __init__(self):
super().__init__()
self.data = None
class SimconnectCommand(enum.IntEnum):
- ''' internal commands '''
- SyncAircraftMode = auto() # synchronizes the profile mode with the current aircraft
+ """internal commands"""
+
+ SyncAircraftMode = auto() # synchronizes the profile mode with the current aircraft
@staticmethod
def to_enum(value):
return _simconnect_command_to_enum[value]
-
+
@staticmethod
def to_string(value):
return _simconnect_command_to_string[value]
-
+
@staticmethod
def to_display_name(value):
return _simconnect_command_description[value]
-
+
_simconnect_command_to_enum = {
- "sync_aicraft" : SimconnectCommand.SyncAircraftMode,
+ "sync_aicraft": SimconnectCommand.SyncAircraftMode,
}
_simconnect_command_description = {
@@ -82,130 +85,136 @@ def to_display_name(value):
}
-''' full axis range commands -16883 to + 16383 '''
+""" full axis range commands -16883 to + 16383 """
_simconnect_full_range = [
- "AXIS_THROTTLE_SET",
- "AXIS_THROTTLE1_SET",
- "AXIS_THROTTLE2_SET",
- "AXIS_THROTTLE3_SET",
- "AXIS_THROTTLE4_SET",
- "AXIS_LEFT_BRAKE_SET",
- "AXIS_RIGHT_BRAKE_SET",
- "AXIS_MIXTURE_SET",
- "AXIS_MIXTURE1_SET",
- "AXIS_MIXTURE2_SET",
- "AXIS_MIXTURE3_SET",
- "AXIS_MIXTURE4_SET",
- "AXIS_PROPELLER_SET",
- "AXIS_PROPELLER1_SET",
- "AXIS_PROPELLER2_SET",
- "AXIS_PROPELLER3_SET",
- "AXIS_PROPELLER4_SET",
- "AXIS_ELEVATOR_SET",
- "AXIS_AILERONS_SET",
- "AXIS_RUDDER_SET",
- "AXIS_ELEV_TRIM_SET",
- "AXIS_SPOILER_SET",
- "AXIS_FLAPS_SET",
- "AXIS_SLEW_AHEAD_SET",
- "AXIS_SLEW_SIDEWAYS_SET",
- "AXIS_SLEW_HEADING_SET",
- "AXIS_SLEW_ALT_SET",
- "AXIS_SLEW_BANK_SET",
- "AXIS_SLEW_PITCH_SET",
- "AXIS_PAN_PITCH",
- "AXIS_PAN_HEADING",
- "AXIS_PAN_TILT",
+ "AXIS_THROTTLE_SET",
+ "AXIS_THROTTLE1_SET",
+ "AXIS_THROTTLE2_SET",
+ "AXIS_THROTTLE3_SET",
+ "AXIS_THROTTLE4_SET",
+ "AXIS_LEFT_BRAKE_SET",
+ "AXIS_RIGHT_BRAKE_SET",
+ "AXIS_MIXTURE_SET",
+ "AXIS_MIXTURE1_SET",
+ "AXIS_MIXTURE2_SET",
+ "AXIS_MIXTURE3_SET",
+ "AXIS_MIXTURE4_SET",
+ "AXIS_PROPELLER_SET",
+ "AXIS_PROPELLER1_SET",
+ "AXIS_PROPELLER2_SET",
+ "AXIS_PROPELLER3_SET",
+ "AXIS_PROPELLER4_SET",
+ "AXIS_ELEVATOR_SET",
+ "AXIS_AILERONS_SET",
+ "AXIS_RUDDER_SET",
+ "AXIS_ELEV_TRIM_SET",
+ "AXIS_SPOILER_SET",
+ "AXIS_FLAPS_SET",
+ "AXIS_SLEW_AHEAD_SET",
+ "AXIS_SLEW_SIDEWAYS_SET",
+ "AXIS_SLEW_HEADING_SET",
+ "AXIS_SLEW_ALT_SET",
+ "AXIS_SLEW_BANK_SET",
+ "AXIS_SLEW_PITCH_SET",
+ "AXIS_PAN_PITCH",
+ "AXIS_PAN_HEADING",
+ "AXIS_PAN_TILT",
+]
+
+""" half axis range commands 0..16384"""
+_simconnect_half_range = [
+ "THROTTLE1_SET",
+ "THROTTLE2_SET",
+ "THROTTLE3_SET",
+ "THROTTLE4_SET",
+ "AXIS_THROTTLE_SET",
+ "THROTTLE_SET",
+ "MIXTURE1_SET",
+ "MIXTURE2_SET",
+ "MIXTURE3_SET",
+ "MIXTURE4_SET",
+ "PROP_PITCH1_SET",
+ "PROP_PITCH2_SET",
+ "PROP_PITCH3_SET",
+ "PROP_PITCH4_SET",
+ "SPOILERS_SET",
+ "FLAPS_SET",
+ "ELEVATOR_TRIM_SET",
]
-''' half axis range commands 0..16384'''
-_simconnect_half_range = ["THROTTLE1_SET",
- "THROTTLE2_SET",
- "THROTTLE3_SET",
- "THROTTLE4_SET",
- "AXIS_THROTTLE_SET",
- "THROTTLE_SET",
- "MIXTURE1_SET",
- "MIXTURE2_SET",
- "MIXTURE3_SET",
- "MIXTURE4_SET",
- "PROP_PITCH1_SET",
- "PROP_PITCH2_SET",
- "PROP_PITCH3_SET",
- "PROP_PITCH4_SET",
- "SPOILERS_SET",
- "FLAPS_SET",
- "ELEVATOR_TRIM_SET",
- ]
-
-''' angle range commands 0..360'''
-_simconnect_angle_range = ["VOR1_SET",
- "VOR2_SET",
- "ADF_CARD_SET",
- "KEY_TUG_HEADING",
- ]
-
-''' EGT range comamnds 0..32767 '''
-_simconnect_egt_range = ["EGT1_SET",
- "EGT2_SET",
- "EGT3_SET",
- "EGT4_SET",
- "EGT_SET"
+""" angle range commands 0..360"""
+_simconnect_angle_range = [
+ "VOR1_SET",
+ "VOR2_SET",
+ "ADF_CARD_SET",
+ "KEY_TUG_HEADING",
]
+""" EGT range comamnds 0..32767 """
+_simconnect_egt_range = ["EGT1_SET", "EGT2_SET", "EGT3_SET", "EGT4_SET", "EGT_SET"]
+
+
class SimConnectActionMode(enum.Enum):
- ''' simconnect action output mode '''
- NotSet = 0,
- Ranged = 1, # output varies with input axis
- Trigger = 2, # output is a trigger (no value sent)
- SetValue = 3, # output sets a number value
- Gated = 4, # output of axis is gated - the position of the axis is not linear
- GetValue = 4, # gets a value from simconnect
+ """simconnect action output mode"""
+
+ NotSet = (0,)
+ Ranged = (1,) # output varies with input axis
+ Trigger = (2,) # output is a trigger (no value sent)
+ SetValue = (3,) # output sets a number value
+ Gated = (4,) # output of axis is gated - the position of the axis is not linear
+ GetValue = (4,) # gets a value from simconnect
@staticmethod
def to_string(value):
if value in _simconnect_action_mode_to_string_lookup.keys():
return _simconnect_action_mode_to_string_lookup[value]
return "none"
+
@staticmethod
- def to_enum(value, validate = True):
+ def to_enum(value, validate=True):
if value is None or value.casefold() == "none":
return SimConnectActionMode.NotSet
if value in _simconnect_action_mode_to_enum_lookup.keys():
return _simconnect_action_mode_to_enum_lookup[value]
if validate:
- raise gremlin.error.GremlinError(f"Invalid type in action mode lookup: {value}")
+ raise gremlin.error.GremlinError(
+ f"Invalid type in action mode lookup: {value}"
+ )
return SimConnectActionMode.NotSet
@staticmethod
def to_display(value):
return _simconnect_action_mode_to_display_lookup[value]
+
_simconnect_action_mode_to_display_lookup = {
SimConnectActionMode.NotSet: "N/A",
SimConnectActionMode.Gated: "Gated",
SimConnectActionMode.Ranged: "Ranged",
SimConnectActionMode.Trigger: "Trigger",
SimConnectActionMode.SetValue: "SetValue",
- SimConnectActionMode.GetValue: "GetValue"
+ SimConnectActionMode.GetValue: "GetValue",
}
+
class SimConnectTriggerMode(enum.Enum):
- ''' trigger modes for boolean actions '''
- NotSet = 0 # not set
- TurnOn = 1 # enable or turn on
- TurnOff = 2 # disable or turn off
- Toggle = 3 # toggle
- NoOp = 4 # send nothing (trigger command only)
- InputValue = 5 # send value as the parameter (used for buttons)
+ """trigger modes for boolean actions"""
+
+ NotSet = 0 # not set
+ TurnOn = 1 # enable or turn on
+ TurnOff = 2 # disable or turn off
+ Toggle = 3 # toggle
+ NoOp = 4 # send nothing (trigger command only)
+ InputValue = 5 # send value as the parameter (used for buttons)
@staticmethod
def to_string(value):
if value in _trigger_mode_to_string.keys():
return _trigger_mode_to_string[value]
return "none"
+
@staticmethod
- def to_enum(value, validate = True):
+ def to_enum(value, validate=True):
if value in _trigger_mode_to_enum.keys():
return _trigger_mode_to_enum[value]
if validate:
@@ -216,122 +225,118 @@ def to_enum(value, validate = True):
def to_display(value):
return _trigger_mode_to_display[value]
+
_trigger_mode_to_string = {
- SimConnectTriggerMode.NotSet : "none",
- SimConnectTriggerMode.Toggle : "toggle",
- SimConnectTriggerMode.TurnOff : "off",
- SimConnectTriggerMode.TurnOn : "on",
+ SimConnectTriggerMode.NotSet: "none",
+ SimConnectTriggerMode.Toggle: "toggle",
+ SimConnectTriggerMode.TurnOff: "off",
+ SimConnectTriggerMode.TurnOn: "on",
SimConnectTriggerMode.NoOp: "noop",
- SimConnectTriggerMode.InputValue: "input_value"
+ SimConnectTriggerMode.InputValue: "input_value",
}
_trigger_mode_to_display = {
- SimConnectTriggerMode.NotSet : "N/A",
- SimConnectTriggerMode.Toggle : "Toggle",
- SimConnectTriggerMode.TurnOff : "Off",
- SimConnectTriggerMode.TurnOn : "On",
+ SimConnectTriggerMode.NotSet: "N/A",
+ SimConnectTriggerMode.Toggle: "Toggle",
+ SimConnectTriggerMode.TurnOff: "Off",
+ SimConnectTriggerMode.TurnOn: "On",
SimConnectTriggerMode.NoOp: "NoOp",
- SimConnectTriggerMode.InputValue: "Input Value"
-
+ SimConnectTriggerMode.InputValue: "Input Value",
}
_trigger_mode_to_enum = {
- "none" : SimConnectTriggerMode.NotSet,
- "toggle" : SimConnectTriggerMode.Toggle,
- "off" : SimConnectTriggerMode.TurnOff,
- "on" : SimConnectTriggerMode.TurnOn,
- "noop" : SimConnectTriggerMode.NoOp,
- "input_value": SimConnectTriggerMode.InputValue
+ "none": SimConnectTriggerMode.NotSet,
+ "toggle": SimConnectTriggerMode.Toggle,
+ "off": SimConnectTriggerMode.TurnOff,
+ "on": SimConnectTriggerMode.TurnOn,
+ "noop": SimConnectTriggerMode.NoOp,
+ "input_value": SimConnectTriggerMode.InputValue,
}
-
class SimConnectCommandType(enum.Enum):
NotSet = 0
- Event = 1 # request event
- Request = 2 # request data
- SimVar = 3 # set simvar
- Calculator = 4 # RPN expression via the bridge
- LVar = 5 # LVAR command via simconnect (floating point data only)
+ Event = 1 # request event
+ Request = 2 # request data
+ SimVar = 3 # set simvar
+ Calculator = 4 # RPN expression via the bridge
+ LVar = 5 # LVAR command via simconnect (floating point data only)
@staticmethod
- def to_string(value : SimConnectCommandType) -> str:
+ def to_string(value: SimConnectCommandType) -> str:
if value in _command_type_to_string_map.keys():
return _command_type_to_string_map[value]
return None
+
@staticmethod
- def to_enum(value : str) -> SimConnectCommandType:
+ def to_enum(value: str) -> SimConnectCommandType:
if value in _command_type_to_enum_map:
return _command_type_to_enum_map[value]
return None
+
_command_type_to_string_map = {
- SimConnectCommandType.NotSet : "notset",
- SimConnectCommandType.Event : "event",
+ SimConnectCommandType.NotSet: "notset",
+ SimConnectCommandType.Event: "event",
SimConnectCommandType.Request: "request",
- SimConnectCommandType.SimVar : "simvar",
+ SimConnectCommandType.SimVar: "simvar",
SimConnectCommandType.Calculator: "rpn",
- SimConnectCommandType.LVar : "lvar",
-
+ SimConnectCommandType.LVar: "lvar",
}
_command_type_to_enum_map = {
"notset": SimConnectCommandType.NotSet,
"event": SimConnectCommandType.Event,
- "request" : SimConnectCommandType.Request,
+ "request": SimConnectCommandType.Request,
"lvar": SimConnectCommandType.Calculator,
- "rpn" : SimConnectCommandType.Calculator,
+ "rpn": SimConnectCommandType.Calculator,
"avar": SimConnectCommandType.Calculator,
- "simvar" : SimConnectCommandType.SimVar,
- "lvar": SimConnectCommandType.LVar
+ "simvar": SimConnectCommandType.SimVar,
+ "lvar": SimConnectCommandType.LVar,
}
-
-
-
-
-
-
_simconnect_action_mode_to_string_lookup = {
- SimConnectActionMode.NotSet : "none",
- SimConnectActionMode.Ranged : "ranged",
- SimConnectActionMode.Trigger : "trigger",
- SimConnectActionMode.SetValue : "value",
- SimConnectActionMode.Gated : "gated",
+ SimConnectActionMode.NotSet: "none",
+ SimConnectActionMode.Ranged: "ranged",
+ SimConnectActionMode.Trigger: "trigger",
+ SimConnectActionMode.SetValue: "value",
+ SimConnectActionMode.Gated: "gated",
}
_simconnect_action_mode_to_enum_lookup = {
- "none" : SimConnectActionMode.NotSet,
- "ranged" : SimConnectActionMode.Ranged,
- "trigger" : SimConnectActionMode.Trigger,
- "value" :SimConnectActionMode.SetValue ,
- "gated" : SimConnectActionMode.Gated,
+ "none": SimConnectActionMode.NotSet,
+ "ranged": SimConnectActionMode.Ranged,
+ "trigger": SimConnectActionMode.Trigger,
+ "value": SimConnectActionMode.SetValue,
+ "gated": SimConnectActionMode.Gated,
}
+
class SimConnectEventCategory(enum.Enum):
- ''' command categories for events '''
- NotSet = 0,
- Engine = 1,
- FlightControls = 2,
- AutoPilot = 3,
- FuelSystem = 4,
- FuelSelection = 5,
- Instruments = 6,
- Lights = 7,
- Failures = 8,
- MiscellaneousSystems = 9,
- NoseWheelSteering = 10,
- CabinPressure = 11,
- Catapult = 12,
- Helicopter = 13,
- SlingsAndHoists = 14,
- SlewSystem = 15,
- ViewSystem = 16,
- FreezingPosition = 17,
- MissionKeys = 18,
- ATC = 19,
+ """command categories for events"""
+
+ NotSet = (0,)
+ Engine = (1,)
+ FlightControls = (2,)
+ AutoPilot = (3,)
+ FuelSystem = (4,)
+ FuelSelection = (5,)
+ Instruments = (6,)
+ Lights = (7,)
+ Failures = (8,)
+ MiscellaneousSystems = (9,)
+ NoseWheelSteering = (10,)
+ CabinPressure = (11,)
+ Catapult = (12,)
+ Helicopter = (13,)
+ SlingsAndHoists = (14,)
+ SlewSystem = (15,)
+ ViewSystem = (16,)
+ FreezingPosition = (17,)
+ MissionKeys = (18,)
+ ATC = (19,)
Multiplayer = 20
@staticmethod
@@ -342,7 +347,7 @@ def to_string(value):
raise gremlin.error.GremlinError(f"Invalid type in lookup: {value}")
@staticmethod
- def to_enum(value, validate = True):
+ def to_enum(value, validate=True):
if value in _simconnect_event_category_to_enum_lookup.keys():
return _simconnect_event_category_to_enum_lookup[value]
if validate:
@@ -351,60 +356,63 @@ def to_enum(value, validate = True):
@staticmethod
def to_list():
- ''' generates all categories as a list '''
+ """generates all categories as a list"""
return [item for item in SimConnectEventCategory]
@staticmethod
def to_list_tuple():
- return [(SimConnectEventCategory.to_string(item), item) for item in SimConnectEventCategory]
+ return [
+ (SimConnectEventCategory.to_string(item), item)
+ for item in SimConnectEventCategory
+ ]
_simconnect_event_category_to_string_lookup = {
- SimConnectEventCategory.NotSet : "None",
- SimConnectEventCategory.Engine : "Engine",
- SimConnectEventCategory.FlightControls : "Flight Controls",
- SimConnectEventCategory.AutoPilot : "Autopilot",
- SimConnectEventCategory.FuelSystem : "Fuel System",
- SimConnectEventCategory.FuelSelection : "Fuel Selection",
- SimConnectEventCategory.Instruments : "Instruments",
- SimConnectEventCategory.Lights : "Lights",
+ SimConnectEventCategory.NotSet: "None",
+ SimConnectEventCategory.Engine: "Engine",
+ SimConnectEventCategory.FlightControls: "Flight Controls",
+ SimConnectEventCategory.AutoPilot: "Autopilot",
+ SimConnectEventCategory.FuelSystem: "Fuel System",
+ SimConnectEventCategory.FuelSelection: "Fuel Selection",
+ SimConnectEventCategory.Instruments: "Instruments",
+ SimConnectEventCategory.Lights: "Lights",
SimConnectEventCategory.Failures: "Failures",
SimConnectEventCategory.MiscellaneousSystems: "Miscellaneous Systems",
SimConnectEventCategory.NoseWheelSteering: "Nosewheel Steering",
- SimConnectEventCategory.CabinPressure : "Cabin Pressure",
- SimConnectEventCategory.Catapult : "Catapult",
- SimConnectEventCategory.Helicopter : "Helicopter",
- SimConnectEventCategory.SlingsAndHoists : "Slings and Hoists",
- SimConnectEventCategory.SlewSystem : "Slew System",
- SimConnectEventCategory.ViewSystem : "View System",
- SimConnectEventCategory.FreezingPosition : "Freezing Position",
- SimConnectEventCategory.MissionKeys : "Mission Keys",
- SimConnectEventCategory.ATC : "ATC",
- SimConnectEventCategory.Multiplayer :"Multiplayer",
+ SimConnectEventCategory.CabinPressure: "Cabin Pressure",
+ SimConnectEventCategory.Catapult: "Catapult",
+ SimConnectEventCategory.Helicopter: "Helicopter",
+ SimConnectEventCategory.SlingsAndHoists: "Slings and Hoists",
+ SimConnectEventCategory.SlewSystem: "Slew System",
+ SimConnectEventCategory.ViewSystem: "View System",
+ SimConnectEventCategory.FreezingPosition: "Freezing Position",
+ SimConnectEventCategory.MissionKeys: "Mission Keys",
+ SimConnectEventCategory.ATC: "ATC",
+ SimConnectEventCategory.Multiplayer: "Multiplayer",
}
_simconnect_event_category_to_enum_lookup = {
- "None" : SimConnectEventCategory.NotSet,
- "Engine" :SimConnectEventCategory.Engine ,
- "Flight Controls" : SimConnectEventCategory.FlightControls,
- "Autopilot" : SimConnectEventCategory.AutoPilot,
- "Fuel System" : SimConnectEventCategory.FuelSystem,
- "Fuel Selection" : SimConnectEventCategory.FuelSelection,
- "Instruments" : SimConnectEventCategory.Instruments,
- "Lights" : SimConnectEventCategory.Lights,
- "Failures" : SimConnectEventCategory.Failures,
- "Miscellaneous Systems" : SimConnectEventCategory.MiscellaneousSystems,
- "Nosewheel Steering" : SimConnectEventCategory.NoseWheelSteering,
- "Cabin Pressure" : SimConnectEventCategory.CabinPressure,
- "Catapult" : SimConnectEventCategory.Catapult,
- "Helicopter" : SimConnectEventCategory.Helicopter,
- "Slings and Hoists" : SimConnectEventCategory.SlingsAndHoists,
- "Slew System" : SimConnectEventCategory.SlewSystem,
- "View System" : SimConnectEventCategory.ViewSystem,
- "Freezing Position" : SimConnectEventCategory.FreezingPosition,
- "Mission Keys" : SimConnectEventCategory.MissionKeys,
- "ATC" : SimConnectEventCategory.ATC ,
- "Multiplayer" : SimConnectEventCategory.Multiplayer
+ "None": SimConnectEventCategory.NotSet,
+ "Engine": SimConnectEventCategory.Engine,
+ "Flight Controls": SimConnectEventCategory.FlightControls,
+ "Autopilot": SimConnectEventCategory.AutoPilot,
+ "Fuel System": SimConnectEventCategory.FuelSystem,
+ "Fuel Selection": SimConnectEventCategory.FuelSelection,
+ "Instruments": SimConnectEventCategory.Instruments,
+ "Lights": SimConnectEventCategory.Lights,
+ "Failures": SimConnectEventCategory.Failures,
+ "Miscellaneous Systems": SimConnectEventCategory.MiscellaneousSystems,
+ "Nosewheel Steering": SimConnectEventCategory.NoseWheelSteering,
+ "Cabin Pressure": SimConnectEventCategory.CabinPressure,
+ "Catapult": SimConnectEventCategory.Catapult,
+ "Helicopter": SimConnectEventCategory.Helicopter,
+ "Slings and Hoists": SimConnectEventCategory.SlingsAndHoists,
+ "Slew System": SimConnectEventCategory.SlewSystem,
+ "View System": SimConnectEventCategory.ViewSystem,
+ "Freezing Position": SimConnectEventCategory.FreezingPosition,
+ "Mission Keys": SimConnectEventCategory.MissionKeys,
+ "ATC": SimConnectEventCategory.ATC,
+ "Multiplayer": SimConnectEventCategory.Multiplayer,
}
@@ -423,7 +431,7 @@ def to_list_tuple():
# feet = auto()
# yard = auto()
# miles = auto()
-# # select temperature
+# # select temperature
# celcius = auto()
# fahrenheit = auto()
# # selected angle
@@ -436,62 +444,61 @@ def to_list_tuple():
# mach = auto()
-
-
-
-
-
-
@SingletonDecorator
class SimConnectManager(QtCore.QObject):
- ''' holds simconnect data and manages simconnect '''
-
-
- sim_start = QtCore.Signal() # fires when the sim starts
- sim_stop = QtCore.Signal() # fires when the sim stops
- sim_running = QtCore.Signal(bool) # fires when the sim is running
- sim_paused = QtCore.Signal(bool) # fires when sim is paused or unpaused (state = pause state)
- lvars_updated = QtCore.Signal(object) # triggers when LVARs are updated (after the request to get LVARs)
- alive = QtCore.Signal() # fires when the bridge is connected and alive
- sim_state = QtCore.Signal(int, float, str) # fires when sim state data changes (depends on the state )
- sim_aircraft_loaded = QtCore.Signal(str, str, str) # fires when aircraft title changes (folder, name, title)
- _aircraft_loaded_internal = QtCore.Signal(str, str) # fires when aircraft (folder, name)
-
+ """holds simconnect data and manages simconnect"""
+
+ sim_start = QtCore.Signal() # fires when the sim starts
+ sim_stop = QtCore.Signal() # fires when the sim stops
+ sim_running = QtCore.Signal(bool) # fires when the sim is running
+ sim_paused = QtCore.Signal(
+ bool
+ ) # fires when sim is paused or unpaused (state = pause state)
+ lvars_updated = QtCore.Signal(
+ object
+ ) # triggers when LVARs are updated (after the request to get LVARs)
+ alive = QtCore.Signal() # fires when the bridge is connected and alive
+ sim_state = QtCore.Signal(
+ int, float, str
+ ) # fires when sim state data changes (depends on the state )
+ sim_aircraft_loaded = QtCore.Signal(
+ str, str, str
+ ) # fires when aircraft title changes (folder, name, title)
+ _aircraft_loaded_internal = QtCore.Signal(
+ str, str
+ ) # fires when aircraft (folder, name)
def __init__(self) -> None:
- ''' manages simconnect connections and interactions
-
+ """manages simconnect connections and interactions
+
:param handler: handler that responds to simconnect events
:param force_update: flag to update the default data if it has been modified
-
- '''
+
+ """
QtCore.QObject.__init__(self)
el = gremlin.event_handler.EventListener()
- el.shutdown.connect(self._shutdown) # trap application shutdown
- el.abort.connect(self._abort) # trap abort
- el.profile_stop.connect(self._profile_stop) # trap profile stop
- el.profile_start.connect(self._profile_start) # trap profile start
+ el.shutdown.connect(self._shutdown) # trap application shutdown
+ el.abort.connect(self._abort) # trap abort
+ el.profile_stop.connect(self._profile_stop) # trap profile stop
+ el.profile_start.connect(self._profile_start) # trap profile start
self.verbose = gremlin.config.Configuration().verbose_mode_simconnect
-
self._connect_in_progress = False
self._sm = None
-
handler = SimConnectEventHandler()
-
self._connect_warning_issued = False
- sm = SimConnect(handler, auto_connect = False)
- self._sm : SimConnect = sm
-
+ sm = SimConnect(handler, auto_connect=False)
+ self._sm: SimConnect = sm
+
self._bridge_alive = False
self.bridge = SimConnectBridge(sm)
self.bridge.alive.connect(self._bridge_alive_cb)
- self._lvars = [] # list of lvars
+ self._lvars = [] # list of lvars
handler.simconnect_aircraft_loaded.connect(self._aicraft_loaded_cb)
handler.simconnect_connected.connect(self._connected_cb)
@@ -502,31 +509,29 @@ def __init__(self) -> None:
handler.simconnect_sim_running.connect(self._running_cb)
handler.simconnect_sim_start.connect(self._start_cb)
handler.simconnect_sim_stop.connect(self._stop_cb)
- handler.status_callback_clicked.connect(self._status_callback_cb) # called when status is clicked on the UI
-
-
+ handler.status_callback_clicked.connect(
+ self._status_callback_cb
+ ) # called when status is clicked on the UI
self._aircraft_events = AircraftEvents(self._sm)
self._aircraft_requests = AircraftRequests(self._sm)
self._aircraft_loaded_internal.connect(self._aircraft_loaded_internal_cb)
- self._aircraft_lvar_request = {} # map of lvars to their requests
-
-
+ self._aircraft_lvar_request = {} # map of lvars to their requests
- self._aircraft_title = None # current title from aircraft.cfg
- self._aircraft_name = None # current name from aicraft cfg path
- self._simvars_xml = os.path.join(gremlin.util.userprofile_path(), "simconnect_simvars.xml")
- self._ensure_simvar_xml() # make srue the simvars file exists
+ self._aircraft_title = None # current title from aircraft.cfg
+ self._aircraft_name = None # current name from aicraft cfg path
+ self._simvars_xml = os.path.join(
+ gremlin.util.userprofile_path(), "simconnect_simvars.xml"
+ )
+ self._ensure_simvar_xml() # make srue the simvars file exists
# https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Events_And_Data/SimConnect_AddToDataDefinition.htm
- self._lvars_xml = os.path.join(gremlin.util.userprofile_path(), "simconnect_lvars.xml")
-
-
-
-
- self._connect_attempts = 3 # number of connection attempts before giving up
+ self._lvars_xml = os.path.join(
+ gremlin.util.userprofile_path(), "simconnect_lvars.xml"
+ )
+ self._connect_attempts = 3 # number of connection attempts before giving up
self._is_started = False
self._is_paused = False
@@ -535,7 +540,7 @@ def __init__(self) -> None:
self._aircraft_folder = None
self._aircraft_title = None
- self._request_abort = False # true if processing should stop
+ self._request_abort = False # true if processing should stop
self._registered_feed_blocks = {}
self._registered_requests = {}
@@ -555,8 +560,9 @@ def _ensure_simvar_xml(self):
try:
shutil.copy(master_xml, self._simvars_xml)
except:
- syslog.error("SIMMCONNECT: error placing master simvars to user profile")
-
+ syslog.error(
+ "SIMMCONNECT: error placing master simvars to user profile"
+ )
@QtCore.Slot()
def _bridge_alive_cb(self):
@@ -575,24 +581,21 @@ def _shutdown(self):
if self.connected:
self._stop()
syslog.info("SIMCONNECT: shutdown")
-
+
@QtCore.Slot()
def _profile_stop(self):
self._stop()
@QtCore.Slot()
def _profile_start(self):
- ''' occurs on profile start '''
+ """occurs on profile start"""
self.activate()
-
-
@QtCore.Slot()
def _abort(self):
if self.connected:
self._stop()
syslog.info("SIMCONNECT: abort")
-
def _stop(self):
if self.connected:
@@ -602,22 +605,17 @@ def _stop(self):
self.sim_disconnect()
self._request_abort = True
-
def start_bridge(self):
- ''' starts the bridge '''
+ """starts the bridge"""
if not self._bridge_alive:
self.bridge.start()
-
-
-
def load_internal(self):
- ''' loads GremlinEx simconnect internal commands '''
+ """loads GremlinEx simconnect internal commands"""
self.reload()
-
- def reload(self, force_update = False):
- # if the data file doesn't exist, create it in the user's profile folder from the built-in data
+ def reload(self, force_update=False):
+ # if the data file doesn't exist, create it in the user's profile folder from the built-in data
# list of all commands
self._commands = []
@@ -625,17 +623,15 @@ def reload(self, force_update = False):
# list of command blocks
self._block_map = {}
- self._ensure_simvar_xml() # make sure the simvars file exists
-
+ self._ensure_simvar_xml() # make sure the simvars file exists
+
# if force_update and os.path.isfile(self._simvars_xml):
# os.unlink(self._simvars_xml)
# if not os.path.isfile(self._simvars_xml):
# self._write_default_xml(self._simvars_xml)
-
self._block_map = {}
-
# load the data - including any user modifications/additions
if os.path.isfile(self._simvars_xml):
@@ -644,7 +640,7 @@ def reload(self, force_update = False):
# load any LVAR definitions defined by the user
if not os.path.isfile(self._lvars_xml):
# create an lvar sample file
- xml = '''
+ xml = """
@@ -652,9 +648,9 @@ def reload(self, force_update = False):
-'''
+"""
try:
- with open(self._lvars_xml,"w") as f:
+ with open(self._lvars_xml, "w") as f:
f.write(xml)
f.flush()
except:
@@ -669,99 +665,89 @@ def reload(self, force_update = False):
lvar = node.get("value")
self._lvars.append(lvar)
-
-
-
-
-
-
-
-
-
if len(self._block_map) > 0:
# process lists
- #b = SimConnectBlock
+ # b = SimConnectBlock
self._commands = [b.command for b in self._block_map.values()]
self._commands.sort()
-
-
def _register_feed(self, command):
- if not command in self._registered_feed_blocks:
+ if command not in self._registered_feed_blocks:
block = SimConnectBlock()
block.command = command
block.command_type = SimConnectCommandType.Request
block.is_periodic = True
self._registered_feed_blocks[command] = block
-
+
def _enable_feed(self):
- ''' enables the data feed '''
- block : SimConnectBlock
+ """enables the data feed"""
+ block: SimConnectBlock
for block in self._registered_feed_blocks.values():
block.execute()
def _disable_feed(self):
- ''' disable the data feed '''
- block : SimConnectBlock
+ """disable the data feed"""
+ block: SimConnectBlock
for block in self._registered_feed_blocks.values():
block.stop()
self._registered_feed_blocks = {}
def RegisterFeed(self, command):
- ''' registers a data feed '''
+ """registers a data feed"""
self._register_feed(command)
def _init_feed(self):
- ''' setup the default data feed events, if any '''
+ """setup the default data feed events, if any"""
pass
# self._register_feed("AIRSPEED_INDICATED")
# self._register_feed("PLANE_ALT_ABOVE_GROUND")
# self._register_feed("BRAKE_PARKING_INDICATOR")
def setFeedEnabled(self, enabled):
- ''' enables or disables data feed '''
+ """enables or disables data feed"""
if enabled:
self._init_feed()
self._enable_feed()
else:
self._disable_feed()
- def registerRequest(self, command : str, datatype : str, settable : bool = False) -> Request:
- ''' registers a request
+ def registerRequest(
+ self, command: str, datatype: str, settable: bool = False
+ ) -> Request:
+ """registers a request
:param command: the command (variable name or expression)
:param datatype: the MSFS simconnect datatype such as "number" "percent"
- '''
+ """
# see if the request is already registered
s_command, b_command = gremlin.util.to_byte_string(command)
key = s_command.casefold()
_, b_datatype = gremlin.util.to_byte_string(datatype)
- if not key in self._registered_requests:
- request = Request(definitions = (b_command, b_datatype), sm = self.sm, settable = settable)
+ if key not in self._registered_requests:
+ request = Request(
+ definitions=(b_command, b_datatype), sm=self.sm, settable=settable
+ )
request._ensure_def()
self._registered_requests[key] = request
-
+
return self._registered_requests[key]
-
+
def findRequest(self, command):
- ''' returns a registered request if it was registered before '''
+ """returns a registered request if it was registered before"""
key = command.casefold()
if key in self._registered_requests[key]:
return self._registered_requests[key]
return None
-
def setSimvar(self, command, datatype, value):
- ''' sets a simvar without using a data block '''
+ """sets a simvar without using a data block"""
self.activate()
request = self.registerRequest(command, datatype, True)
request.value = value
request.transmit()
-
-
def sendEvent(self, command):
- ''' sends an event to Simconnect '''
+ """sends an event to Simconnect"""
if command in self._block_map:
self.activate()
block = self._block_map[command]
@@ -769,14 +755,12 @@ def sendEvent(self, command):
return block.execute()
syslog.error(f"SIMCONNECT: event not found: {command}")
-
-
def calculate(self, command):
if self.bridge.connected:
self.bridge.execute_calculator_code(command)
def refreshLvars(self):
- ''' refreshes the list of lvars from the sim '''
+ """refreshes the list of lvars from the sim"""
if self.bridge.connected:
self.bridge.get_lvars()
@@ -787,7 +771,7 @@ def refreshLvars(self):
@QtCore.Slot(object)
def _lvars_updated_cb(self, lvars):
- ''' gets the list of lvars when they are available '''
+ """gets the list of lvars when they are available"""
self._lvars = lvars
# syslog = logging.getLogger("system")
# save the lvars
@@ -805,35 +789,35 @@ def _lvars_updated_cb(self, lvars):
root.remove(node)
for node in root.xpath("//command"):
- if not "value" in node.attrib:
+ if "value" not in node.attrib:
root.remove(node)
-
for lvar in self._lvars:
lvar_name = lvar
nodes = root.xpath(f"//command[@value='{lvar_name}']")
if nodes is not None and len(nodes) > 0:
node = nodes[0]
else:
- node = etree.Element("command") # rpn
+ node = etree.Element("command") # rpn
node.set("value", lvar)
- node.set("type", SimConnectCommandType.to_string(SimConnectCommandType.Calculator))
+ node.set(
+ "type",
+ SimConnectCommandType.to_string(SimConnectCommandType.Calculator),
+ )
node.set("datatype", "float")
node.set("units", "Number")
- node.set("category","none")
+ node.set("category", "none")
node.set("settable", str(True))
node.set("axis", str(False))
node.set("indexed", str(False))
-
root.append(node)
-
tree = etree.ElementTree(root)
- tree.write(xml_source, pretty_print=True,xml_declaration=True,encoding="utf-8")
-
-
+ tree.write(
+ xml_source, pretty_print=True, xml_declaration=True, encoding="utf-8"
+ )
except Exception as err:
syslog.error(f"SimconnectData: XML simvars read error: {xml_source}: {err}")
@@ -842,22 +826,19 @@ def _lvars_updated_cb(self, lvars):
# indicate new data is available
self.lvars_updated.emit(self._lvars)
-
-
def get_lvar_name_list(self) -> list:
- ''' returns the list of known lvars '''
+ """returns the list of known lvars"""
return self._lvars
-
+
@property
def lvars(self):
return self._lvars
-
@QtCore.Slot()
def _connected_cb(self):
self._is_connected = True
if self.verbose:
- syslog.info(f"Simconnect Event: connected")
+ syslog.info("Simconnect Event: connected")
self.setFeedEnabled(True)
@@ -865,12 +846,10 @@ def _connected_cb(self):
def _disconnected_cb(self):
self._is_connected = False
if self.verbose:
- syslog.info(f"Simconnect Event: disconnected")
+ syslog.info("Simconnect Event: disconnected")
self.setFeedEnabled(False)
-
-
@QtCore.Slot(bool)
def _paused_state_cb(self, state):
self._is_paused = state
@@ -880,18 +859,18 @@ def _paused_state_cb(self, state):
def _paused_cb(self):
self._is_paused = True
if self.verbose:
- syslog.info(f"Simconnect Event: paused")
+ syslog.info("Simconnect Event: paused")
@QtCore.Slot()
def _unpaused_cb(self):
if self.verbose:
self._is_paused = False
- syslog.info(f"Simconnect Event: unpaused")
+ syslog.info("Simconnect Event: unpaused")
@QtCore.Slot()
def _start_cb(self):
if self.verbose:
- syslog.info(f"Simconnect Event: started")
+ syslog.info("Simconnect Event: started")
self._is_started = True
self.sim_start.emit()
# update the aircraft
@@ -901,7 +880,7 @@ def _start_cb(self):
@QtCore.Slot()
def _stop_cb(self):
if self.verbose:
- syslog.info(f"Simconnect Event: stopped")
+ syslog.info("Simconnect Event: stopped")
self._is_started = False
self.sim_stop.emit()
@@ -913,8 +892,6 @@ def _status_callback_cb(self):
title = self._aircraft_title
self._dump_current_aircraft()
self.sim_aircraft_loaded.emit(folder, name, title)
-
-
@QtCore.Slot(bool)
def _running_cb(self, state: bool):
@@ -927,11 +904,10 @@ def _running_cb(self, state: bool):
else:
self.sim_stop.emit()
-
@property
def is_connected(self):
return self._is_connected
-
+
@property
def is_mobi_connected(self):
return self.mobi.connected
@@ -939,30 +915,29 @@ def is_mobi_connected(self):
@property
def is_started(self):
return self._is_started
-
+
@property
def is_paused(self):
return self._is_paused
-
+
@property
def current_aircraft_title(self):
- ''' currently loaded aircraft - TITLE from the aircraft.cfg file '''
+ """currently loaded aircraft - TITLE from the aircraft.cfg file"""
return self._aircraft_title
-
+
@property
def current_aircraft_sim_name(self):
- ''' currently loaded aircraft - TITLE from the aircraft.cfg file '''
+ """currently loaded aircraft - TITLE from the aircraft.cfg file"""
return self._aircraft_name
-
+
@property
def current_aircraft_folder(self):
- ''' returns the path to the currently loaded folder '''
- return self._aircraft_folder
-
+ """returns the path to the currently loaded folder"""
+ return self._aircraft_folder
@property
def is_running(self):
- ''' true if the sim state is running '''
+ """true if the sim state is running"""
return self._is_running
def _sim_paused_cb(self, arg):
@@ -972,27 +947,29 @@ def _sim_running_cb(self, state):
self._is_running = state
def _aicraft_loaded_cb(self, folder, name):
- ''' called when a new aircraft is loaded '''
- self._aircraft_loaded_internal.emit(folder, name) # fires self.sim_aircraft_loaded.emit(name)
-
+ """called when a new aircraft is loaded"""
+ self._aircraft_loaded_internal.emit(
+ folder, name
+ ) # fires self.sim_aircraft_loaded.emit(name)
def _state_data_cb(self, int_data, float_data, str_data):
- ''' occurs when state data is requested '''
+ """occurs when state data is requested"""
if gremlin.util.is_binary_string(str_data):
- str_data = str_data.decode('utf-8')
+ str_data = str_data.decode("utf-8")
str_data = str_data.casefold()
if "aircraft.cfg" in str_data:
self._aircraft_folder = str_data
- #self.sim_aircraft_loaded.emit(str_data,None)
+ # self.sim_aircraft_loaded.emit(str_data,None)
self.sim_state.emit(int_data, float_data, str_data)
def get_loaded_aircraft(self):
- ''' gets the loaded aircraft '''
- return self._aircraft_title # use title as the name is meaningless in MSFS 2024 due to streaming AC
-
+ """gets the loaded aircraft"""
+ return (
+ self._aircraft_title
+ ) # use title as the name is meaningless in MSFS 2024 due to streaming AC
- def get_aircraft_title(self, force_update = False):
+ def get_aircraft_title(self, force_update=False):
if not self._aircraft_title or force_update:
self._aircraft_title = None
ar = self._aircraft_requests
@@ -1002,9 +979,9 @@ def get_aircraft_title(self, force_update = False):
title = title.decode()
self._aircraft_title = title
return self._aircraft_title
-
+
def request_loaded_aircraft(self):
- ''' gets the current player aircraft in the sim '''
+ """gets the current player aircraft in the sim"""
try:
if self._sm.ok:
self._sm.requestAircraftLoaded()
@@ -1012,7 +989,7 @@ def request_loaded_aircraft(self):
pass
def request_aircraft_list(self):
- ''' requests user flyable aircraft data '''
+ """requests user flyable aircraft data"""
try:
if self._sm.ok:
self._sm.requestSimObjectsAndLiveries()
@@ -1020,10 +997,10 @@ def request_aircraft_list(self):
pass
def get_aircraft_list(self) -> list[str]:
- ''' retrieves the list of user flyable objects from simconnect '''
+ """retrieves the list of user flyable objects from simconnect"""
return self._sm.getSimObjects()
- def _aircraft_loaded_internal_cb(self, folder : str, name : str):
+ def _aircraft_loaded_internal_cb(self, folder: str, name: str):
# decode the data into useful bits
# syslog = logging.getLogger("system")
title = self.get_aircraft_title(True)
@@ -1033,20 +1010,18 @@ def _aircraft_loaded_internal_cb(self, folder : str, name : str):
self._dump_current_aircraft()
self.sim_aircraft_loaded.emit(folder, name, title)
-
def _dump_current_aircraft(self):
verbose = gremlin.config.Configuration().verbose_mode_simconnect
if verbose:
- syslog.info(f"SIMCONNECT: current aircraft: folder: [{self._aircraft_folder}] name: [{self._aircraft_name}] title: [{self._aircraft_title}]")
+ syslog.info(
+ f"SIMCONNECT: current aircraft: folder: [{self._aircraft_folder}] name: [{self._aircraft_name}] title: [{self._aircraft_title}]"
+ )
def reset(self):
- ''' resets the connection '''
+ """resets the connection"""
self._sm.reset()
self._connect_attempts = 5
self._request_abort = False
-
-
-
@property
def ok(self):
@@ -1054,34 +1029,33 @@ def ok(self):
# attempt to reconnect
self.activate(True)
return self._sm.ok
-
+
@property
def connected(self) -> bool:
- ''' true if GremlinEx is connected to the simulator '''
+ """true if GremlinEx is connected to the simulator"""
return self._sm.ok
@property
def simconnect(self) -> SimConnect:
- ''' returns the simconnect interface instance'''
+ """returns the simconnect interface instance"""
return self._sm
-
- def activate(self, force_retry = False):
+ def activate(self, force_retry=False):
# not connected
if self.connected:
return
if self._connect_in_progress:
return
-
+
self._connect_in_progress = True
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_simconnect
- if verbose: syslog.info(f"SIMCONNECT MGR: reconnect...")
+ if verbose:
+ syslog.info("SIMCONNECT MGR: reconnect...")
try:
-
self._bridge_alive = False
self._connect_attempts = 2
self._connect_warning_issued = False
@@ -1092,7 +1066,6 @@ def activate(self, force_retry = False):
# simconnect is not enabled
return False
-
if not self._sm.ok:
try:
attempt_count = 1
@@ -1101,16 +1074,19 @@ def activate(self, force_retry = False):
self._connect_attempts -= 1
self._sm.connect()
if self._sm.ok:
- # connect is ok
+ # connect is ok
break
time.sleep(0.5)
- if verbose: syslog.info(f"SIMCONNECT: connect attempt {attempt_count}")
+ if verbose:
+ syslog.info(
+ f"SIMCONNECT: connect attempt {attempt_count}"
+ )
except:
pass
if not self._sm.ok:
- syslog.error(f"SIMCONNECT: connect failed")
+ syslog.error("SIMCONNECT: connect failed")
el = gremlin.event_handler.EventListener()
if gremlin.shared_state.is_running:
if not self._connect_warning_issued:
@@ -1118,49 +1094,46 @@ def activate(self, force_retry = False):
syslog.error(msg)
self._connect_warning_issued = True
# request the profile to stop
- el.module_state_change.emit("simconnect",False)
+ el.module_state_change.emit("simconnect", False)
el.request_profile_stop.emit(msg)
- gremlin.shared_state.profile_state = False # indicate a profile start error occured
+ gremlin.shared_state.profile_state = (
+ False # indicate a profile start error occured
+ )
syslog.error("SIMCONNECT: failed to start.")
return False
-
+
else:
syslog.info("Simconnect: connected to simulator")
-
-
+
self._is_running = True
self.bridge.start()
-
-
- return True # connected
+ return True # connected
finally:
self._connect_in_progress = False
def clearRequests(self):
- ''' unregisters all requests '''
+ """unregisters all requests"""
if self._sm.ok:
for request in self._registered_requests.values():
self._sm.clear(request)
self._registered_requests.clear()
-
def sim_connect(self):
- ''' connects to the sim (has to be different from connect() due to event processing )'''
+ """connects to the sim (has to be different from connect() due to event processing )"""
if self._sm.is_connected() and self._bridge_alive:
return True
self.reset()
return self.activate()
def sim_disconnect(self):
- ''' disconnect request '''
+ """disconnect request"""
# if self.mobi.connected:
# self.mobi.stop()
if self.bridge.connect:
self.bridge.stop()
-
if self._sm.ok:
for request in self._registered_requests.values():
self._sm.clear(request)
@@ -1168,11 +1141,11 @@ def sim_disconnect(self):
@property
def valid(self):
- ''' true if block maps are valid '''
+ """true if block maps are valid"""
return len(self._block_map) > 0
- def block(self, command, clone = True) -> SimConnectBlock:
- ''' gets the command block for a given Simconnect command '''
+ def block(self, command, clone=True) -> SimConnectBlock:
+ """gets the command block for a given Simconnect command"""
s_command, b_command = gremlin.util.to_byte_string(command)
if s_command:
key = s_command.casefold()
@@ -1183,23 +1156,22 @@ def block(self, command, clone = True) -> SimConnectBlock:
if clone:
return block.clone()
return block
-
return None
def get_aircraft_data(self):
- ''' returns the current aircraft information
- (aircraft, model, title)
- '''
+ """returns the current aircraft information
+ (aircraft, model, title)
+ """
ar = self._aircraft_requests
trigger = ar.find("ATC_TYPE")
aircraft_type = trigger.get()
if aircraft_type:
- aircraft_type = aircraft_type.decode() # binary string to regular string
+ aircraft_type = aircraft_type.decode() # binary string to regular string
trigger = ar.find("ATC_MODEL")
aircraft_model = trigger.get()
if aircraft_model:
- aircraft_model = aircraft_model.decode()# binary string to regular string
+ aircraft_model = aircraft_model.decode() # binary string to regular string
trigger = ar.find("TITLE")
title = trigger.get()
if title:
@@ -1207,7 +1179,7 @@ def get_aircraft_data(self):
return (aircraft_type, aircraft_model, title)
def get_aircraft(self):
- ''' gets the aircraft title '''
+ """gets the aircraft title"""
ar = self._aircraft_requests
trigger = ar.find("TITLE")
@@ -1215,41 +1187,45 @@ def get_aircraft(self):
if title:
title = title.decode()
return title
-
- def get_aicraft_position(self):
-
- request_id = self._sm.new_request_id()
-
- definitions = [("SIM ON GROUND", "Bool", SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_INT32),
- ("PLANE LATITUDE", "Degrees", SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_FLOAT64),
- ("PLANE LONGITUDE", "Degrees", SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_FLOAT64),
- ("PLANE ALTITUDE", "Feet", SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_FLOAT64),
+ def get_aicraft_position(self):
+ self._sm.new_request_id()
+
+ definitions = [
+ ("SIM ON GROUND", "Bool", SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_INT32),
+ (
+ "PLANE LATITUDE",
+ "Degrees",
+ SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_FLOAT64,
+ ),
+ (
+ "PLANE LONGITUDE",
+ "Degrees",
+ SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_FLOAT64,
+ ),
+ ("PLANE ALTITUDE", "Feet", SIMCONNECT_DATATYPE.SIMCONNECT_DATATYPE_FLOAT64),
]
- request = Request(definitions, self._sm)
-
+ Request(definitions, self._sm)
+
# hr = SimConnect_AddToDataDefinition(hSimConnect, request_id, "SIM ON GROUND", "Bool", SIMCONNECT_DATATYPE_INT32)
# hr = SimConnect_AddToDataDefinition(hSimConnect, request_id, "PLANE LATITUDE", "Degrees", SIMCONNECT_DATATYPE_FLOAT64)
# hr = SimConnect_AddToDataDefinition(hSimConnect, request_id, "PLANE LONGITUDE", "Degrees", SIMCONNECT_DATATYPE_FLOAT64)
# hr = SimConnect_AddToDataDefinition(hSimConnect, request_id, "PLANE ALTITUDE", "Feet", SIMCONNECT_DATATYPE_FLOAT64)
- def save_flight(self, the_path : str, title : str = "", description : str = ""):
- ''' requests to save the flight data '''
+ def save_flight(self, the_path: str, title: str = "", description: str = ""):
+ """requests to save the flight data"""
if self._sm.ok:
-
# get the current aircraft position
-
self._sm.save_flight(the_path, title, description)
- def load_flight(self, the_path : str):
+ def load_flight(self, the_path: str):
if self._sm.ok:
self._sm.load_flight(the_path)
-
def _write_default_xml(self, xml_file):
- ''' writes a default XML file from the base data in the simconnect module '''
+ """writes a default XML file from the base data in the simconnect module"""
# list of aircraft names
aircraft_events_description_map = {}
@@ -1306,21 +1282,18 @@ def _write_default_xml(self, xml_file):
else:
continue
-
-
category_commands[category] = []
for b_command, description, scope in source:
- command = b_command.decode('ascii')
+ command = b_command.decode("ascii")
aircraft_events_description_map[command] = description
aircraft_events_scope_map[command] = scope
data = (b_command, command, description, scope)
category_commands[category].append(data)
- #commands.append(data)
+ # commands.append(data)
aircraft_events_description_map[command] = description
command_category_map[command] = category
command_map[command] = ("e", data)
-
# build request commands
for data in self._aircraft_requests.list:
for command, data in data.list.items():
@@ -1332,12 +1305,14 @@ def _write_default_xml(self, xml_file):
for command in commands:
data = command_map[command]
- command_node = etree.SubElement(root,"command", value = command)
+ command_node = etree.SubElement(root, "command", value=command)
- value_types = ["(0","16383"]
+ value_types = ["(0", "16383"]
units = ""
- is_range = False # assume command has no range specified
- is_toggle = False # true if range is a toggle range (either value of the range)
+ is_range = False # assume command has no range specified
+ is_toggle = (
+ False # true if range is a toggle range (either value of the range)
+ )
min_range = 0
max_range = 0
if data[0] == "e":
@@ -1379,13 +1354,13 @@ def _write_default_xml(self, xml_file):
elif data[0] == "r":
simvar_type = "simvar"
description = data[1][0]
- units = data[1][2].decode('ascii')
- settable = data[1][3] == 'Y'
+ units = data[1][2].decode("ascii")
+ settable = data[1][3] == "Y"
- command_category =self.get_command_category(command)
+ command_category = self.get_command_category(command)
if command_category is None:
command_category = SimConnectEventCategory.NotSet
- category = SimConnectEventCategory.to_string(command_category)
+ category = SimConnectEventCategory.to_string(command_category)
is_axis = "AXIS_" in command
if is_axis:
min_range = -16383
@@ -1395,16 +1370,16 @@ def _write_default_xml(self, xml_file):
# simvar index : https://docs.flightsimulator.com/html/Programming_Tools/SimVars/Simulation_Variables.htm#h
- command_node.attrib['type'] = simvar_type
+ command_node.attrib["type"] = simvar_type
command_node.attrib["datatype"] = "int"
command_node.attrib["units"] = units
command_node.attrib["category"] = category
command_node.attrib["settable"] = str(settable)
command_node.attrib["axis"] = str(is_axis)
command_node.attrib["indexed"] = str(is_indexed)
- description_node = etree.SubElement(command_node,"description", value = description)
+ etree.SubElement(command_node, "description", value=description)
if is_range:
- range_node = etree.SubElement(command_node,"range")
+ range_node = etree.SubElement(command_node, "range")
range_node.attrib["min"] = str(min_range)
range_node.attrib["max"] = str(max_range)
range_node.attrib["toggle"] = str(is_toggle)
@@ -1412,49 +1387,67 @@ def _write_default_xml(self, xml_file):
try:
# save the file
tree = etree.ElementTree(root)
- tree.write(xml_file, pretty_print=True,xml_declaration=True,encoding="utf-8")
+ tree.write(
+ xml_file, pretty_print=True, xml_declaration=True, encoding="utf-8"
+ )
except Exception as err:
- syslog.error(f"SimconnectData: unable to create XML simvars: {xml_file}: {err}")
+ syslog.error(
+ f"SimconnectData: unable to create XML simvars: {xml_file}: {err}"
+ )
def _load_xml(self, xml_source):
- ''' loads blocks from the XML file '''
+ """loads blocks from the XML file"""
- def get_attribute(node : etree._Element, attr, default = '', throw_on_missing = False) -> bool:
- ''' gets a node attribute checking for validity '''
+ def get_attribute(
+ node: etree._Element, attr, default="", throw_on_missing=False
+ ) -> bool:
+ """gets a node attribute checking for validity"""
if attr in node.attrib:
return node.attrib[attr]
elif throw_on_missing:
- raise ValueError(f"Bad or missing boolean XML attribute {attr} on node {node}")
+ raise ValueError(
+ f"Bad or missing boolean XML attribute {attr} on node {node}"
+ )
return str(default)
- def get_bool_attribute(node : etree._Element, attr, default = False, throw_on_missing = False) -> bool:
- ''' gets a node attribute checking for validity '''
+ def get_bool_attribute(
+ node: etree._Element, attr, default=False, throw_on_missing=False
+ ) -> bool:
+ """gets a node attribute checking for validity"""
value = get_attribute(node, attr, throw_on_missing).lower()
- if value in ("t","true","1","-1"):
+ if value in ("t", "true", "1", "-1"):
return True
- if value in ("f","false","0"):
+ if value in ("f", "false", "0"):
return False
return default
- def get_int_attribute(node : etree._Element, attr, default = 0, throw_on_missing = False) -> int:
+ def get_int_attribute(
+ node: etree._Element, attr, default=0, throw_on_missing=False
+ ) -> int:
value = get_attribute(node, attr).lower()
- if value != '':
+ if value != "":
try:
return int(value)
except:
if throw_on_missing:
- raise ValueError(f"Bad or missing int XML attribute {attr} on node {node}")
+ raise ValueError(
+ f"Bad or missing int XML attribute {attr} on node {node}"
+ )
return default
- def get_float_attribute(node : etree._Element, attr, default = 0.0, throw_on_missing = False) -> float:
+ def get_float_attribute(
+ node: etree._Element, attr, default=0.0, throw_on_missing=False
+ ) -> float:
value = get_attribute(node, attr).lower()
- if value != '':
+ if value != "":
try:
return float(value)
except:
if throw_on_missing:
- raise ValueError(f"Bad or missing float XML attribute {attr} on node {node}")
+ raise ValueError(
+ f"Bad or missing float XML attribute {attr} on node {node}"
+ )
return default
@@ -1466,9 +1459,8 @@ def get_float_attribute(node : etree._Element, attr, default = 0.0, throw_on_mis
parser = etree.XMLParser(remove_blank_text=True)
root = etree.parse(xml_source, parser)
- nodes = root.xpath('//command')
+ nodes = root.xpath("//command")
for node in nodes:
-
max_range = 0
min_range = 0
is_toggle = False
@@ -1480,25 +1472,30 @@ def get_float_attribute(node : etree._Element, attr, default = 0.0, throw_on_mis
if "type" in node.attrib:
node_type = node.get("type")
is_lvar = node_type.casefold() == "lvar"
-
- simvar = get_attribute(node,"value")
- simvar_type = get_attribute(node,"type",throw_on_missing=True)
- units = get_attribute(node,"units",throw_on_missing=True)
- category = get_attribute(node,"category")
- settable = get_bool_attribute(node,"settable")
- axis = get_bool_attribute(node,"axis")
- indexed = get_bool_attribute(node,"indexed")
- invert =get_bool_attribute(node,"invert")
+
+ simvar = get_attribute(node, "value")
+ simvar_type = get_attribute(node, "type", throw_on_missing=True)
+ units = get_attribute(node, "units", throw_on_missing=True)
+ category = get_attribute(node, "category")
+ settable = get_bool_attribute(node, "settable")
+ axis = get_bool_attribute(node, "axis")
+ indexed = get_bool_attribute(node, "indexed")
+ invert = get_bool_attribute(node, "invert")
description = ""
for child in node.getchildren():
if child.tag == "description":
- description = get_attribute(child,"value")
+ description = get_attribute(child, "value")
elif child.tag == "range":
-
- is_toggle = get_bool_attribute(child,"toggle",throw_on_missing=True)
- min_range = get_int_attribute(child,"min",throw_on_missing=True)
- max_range = get_int_attribute(child,"max",throw_on_missing=True)
+ is_toggle = get_bool_attribute(
+ child, "toggle", throw_on_missing=True
+ )
+ min_range = get_int_attribute(
+ child, "min", throw_on_missing=True
+ )
+ max_range = get_int_attribute(
+ child, "max", throw_on_missing=True
+ )
block = SimConnectBlock()
s_simvar, b_simvar = gremlin.util.to_byte_string(simvar)
@@ -1514,15 +1511,21 @@ def get_float_attribute(node : etree._Element, attr, default = 0.0, throw_on_mis
block._description = description
block._invert = invert
block.is_lvar = is_lvar
-
+
block._min_range = min_range # can be modified by the user
block._max_range = max_range # can be modified by the user
- block._command_min_range = min_range # original range - cannot be modified
- block._command_max_range = max_range # original range - cannot be modified
+ block._command_min_range = (
+ min_range # original range - cannot be modified
+ )
+ block._command_max_range = (
+ max_range # original range - cannot be modified
+ )
block.is_toggle = is_toggle
key = s_simvar.casefold()
if key in self._block_map.keys():
- syslog.error(f"SimconnectData: duplicate definition found: {s_simvar} in {xml_source}")
+ syslog.error(
+ f"SimconnectData: duplicate definition found: {s_simvar} in {xml_source}"
+ )
self._block_map = {}
return False
self._block_map[key] = block
@@ -1543,7 +1546,7 @@ def get_float_attribute(node : etree._Element, attr, default = 0.0, throw_on_mis
return False
def findRequest(self, command):
- ''' gets the request object'''
+ """gets the request object"""
request = self._aircraft_requests.find(command)
if not request:
command = command.casefold()
@@ -1552,122 +1555,128 @@ def findRequest(self, command):
return None
def get_event_description(self, command):
- ''' maps the description to the given simconnect command name '''
+ """maps the description to the given simconnect command name"""
if command in self._aircraft_events_description_map.keys():
return self._aircraft_events_description_map[command]
return "Not found"
-
+
def get_command_type(self, command) -> SimConnectCommandType:
- ''' maps to the type of command'''
+ """maps to the type of command"""
if self._aircraft_events.find(command):
return SimConnectCommandType.Event
if self._aircraft_requests.find(command):
return SimConnectCommandType.SimVar
return SimConnectCommandType.NotSet
-
@property
def AircraftEvents(self):
- ''' gets a list of aicraft events '''
+ """gets a list of aicraft events"""
return self._aircraft_events
@property
def ok(self) -> bool:
- ''' true if simconnect is ok '''
+ """true if simconnect is ok"""
return self._sm.ok
@property
def running(self) -> bool:
- ''' true if sim is running '''
+ """true if sim is running"""
return self._sm.running
@property
def paused(self) -> bool:
- ''' true if sim is paused '''
+ """true if sim is paused"""
return self._sm.paused
@property
def sm(self) -> SimConnect:
- ''' simconnect object'''
+ """simconnect object"""
return self._sm
def ensure_running(self):
- ''' ensure simconnect is connected '''
+ """ensure simconnect is connected"""
return self._sm.is_connected()
def get_category_list(self):
- ''' returns the list of supported command categories '''
+ """returns the list of supported command categories"""
categories = self._command_category_map.keys()
categories.sort()
return categories
def get_command_name_list(self):
- ''' gets all possible command names '''
+ """gets all possible command names"""
return self._commands
def get_default_command(self):
- ''' gets the default command '''
+ """gets the default command"""
if self.valid:
return self._block_map[0].command
return None
def get_command_category(self, command):
- ''' for a given command, find the category of that command '''
+ """for a given command, find the category of that command"""
if self.valid and command in self._block_map.keys():
block = self._block_map[command]
return block.category
return SimConnectEventCategory.NotSet
-
+
def get_command_block(self, command):
if self.valid:
command = command.casefold()
if command in self._block_map:
return self._block_map[command]
return None
-
- def sendReadbackEvent(self, command, value, readback_command, readback_value, timeout = 4):
- ''' sends an event and waits for a return value to match the readback value, or timeout '''
-
+ def sendReadbackEvent(
+ self, command, value, readback_command, readback_value, timeout=4
+ ):
+ """sends an event and waits for a return value to match the readback value, or timeout"""
+
# simconnect events have to be in byte strings
s_command, b_command = gremlin.util.to_byte_string(command)
_, b_readback = gremlin.util.to_byte_string(readback_command)
-
+
key = s_command.casefold()
- if not key in self._registered_events:
+ if key not in self._registered_events:
self._registered_events[key] = self._sm.map_to_sim_event(b_command)
-
+
if key in self._registered_events:
event_id = self._registered_events[key]
if event_id == 0:
syslog.error(f"Simconnect: unable to register event for {s_command}")
- return False # bad event
+ return False # bad event
# send event and wait for readback value or a timeout
event = DataThreadingEvent()
- thread = threading.Thread(target=self._send_readback_event_worker(event, event_id, value, b_readback, readback_value, timeout), daemon=True)
+ thread = threading.Thread(
+ target=self._send_readback_event_worker(
+ event, event_id, value, b_readback, readback_value, timeout
+ ),
+ daemon=True,
+ )
thread.start()
event.wait()
- if event.data and self.verbose: syslog.info(f"SimConnect: event readback completed OK {command} value: {value}")
- return event.data # data contains the return code
-
+ if event.data and self.verbose:
+ syslog.info(
+ f"SimConnect: event readback completed OK {command} value: {value}"
+ )
+ return event.data # data contains the return code
+
+ def _send_readback_event_worker(
+ self, event, event_id, value, readback, readback_value, timeout
+ ):
+ """readback worker to send the event, then compare the expected value to the return value within the timeout period"""
- def _send_readback_event_worker(self, event, event_id, value, readback, readback_value, timeout):
- ''' readback worker to send the event, then compare the expected value to the return value within the timeout period '''
-
-
# send the event
retval = self._sm.send_event(event_id, value)
# read the data back to confirm it was processed
retval = False
-
+
block = self.block(readback)
if block:
-
-
value = block.read()
if value == readback_value:
retval = True
@@ -1682,24 +1691,27 @@ def _send_readback_event_worker(self, event, event_id, value, readback, readback
time.sleep(0.250)
value = block.read()
else:
- syslog.error(f"Simconnect: event readback FAILED: readback command {readback} not found")
-
- # done
+ syslog.error(
+ f"Simconnect: event readback FAILED: readback command {readback} not found"
+ )
+
+ # done
event.data = retval
event.set()
-class SimConnectBlock():
- ''' holds simconnect block information '''
+
+class SimConnectBlock:
+ """holds simconnect block information"""
def __init__(self):
- ''' creates a simconnect block object
+ """creates a simconnect block object
the block auto-configures itself based on the command, and determines
range, values and options, and what type of command it is.
:param simconnect The simconnect object
- '''
+ """
self._command_type = SimConnectCommandType.NotSet
self._description = None
@@ -1707,75 +1719,79 @@ def __init__(self):
self._category = SimConnectEventCategory.NotSet
self._output_data_type = "Number"
self._output_mode = SimConnectActionMode.NotSet
- self._command = None # the command text
- self._is_set_value = False # true if the item can set a value
- self._readonly = False # if readonly - the request cannot be triggered
- self._is_axis = False # true if the output is an axis variable
- self._is_indexed = False # true if the output is indexed using the :index
- self._min_range = -16383 # user modifieable range
+ self._command = None # the command text
+ self._is_set_value = False # true if the item can set a value
+ self._readonly = False # if readonly - the request cannot be triggered
+ self._is_axis = False # true if the output is an axis variable
+ self._is_indexed = False # true if the output is indexed using the :index
+ self._min_range = -16383 # user modifieable range
self._max_range = 16383
- self._command_min_range = -16383 # command range (cannot be modified)
+ self._command_min_range = -16383 # command range (cannot be modified)
self._command_max_range = 16383
- self._trigger_mode = SimConnectTriggerMode.NoOp # default for on/off type blocks
- self._invert = False # true if the axis output should be inverted
- self._value = 0 # output value
- self._is_value = False # true if the command supports an output value
-
- self._is_toggle = False # true if the range valuers are either mix or max
- self._notify_enabled_count = 0 # true if notifications are enabled
+ self._trigger_mode = (
+ SimConnectTriggerMode.NoOp
+ ) # default for on/off type blocks
+ self._invert = False # true if the axis output should be inverted
+ self._value = 0 # output value
+ self._is_value = False # true if the command supports an output value
+
+ self._is_toggle = False # true if the range valuers are either mix or max
+ self._notify_enabled_count = 0 # true if notifications are enabled
self._command = None
self._units = ""
- self._is_axis = False # true if the block is axis output enabled
- self._is_periodic = False # true if we're requesting period data from the sim when the data changes
- self.is_lvar = False # true if the entry is an lvar entry
- self._request : Request = None # holds any current aircraft request (request commands only )
+ self._is_axis = False # true if the block is axis output enabled
+ self._is_periodic = False # true if we're requesting period data from the sim when the data changes
+ self.is_lvar = False # true if the entry is an lvar entry
+ self._request: Request = (
+ None # holds any current aircraft request (request commands only )
+ )
config = gremlin.config.Configuration()
self.verbose = config.verbose_mode_simconnect
self.verbose_detailed = config.verbose_mode_detailed
@property
def sm(self) -> SimConnect:
- ''' simconnect object '''
+ """simconnect object"""
return SimConnectManager().sm
-
+
@property
def request(self):
- ''' gets any active aicraft request '''
+ """gets any active aicraft request"""
if not self._request:
# create a request for this block
self.register()
return self._request
- def enable_notifications(self, force = False):
- ''' enables data notifications from this block '''
+ def enable_notifications(self, force=False):
+ """enables data notifications from this block"""
if self._notify_enabled_count > 0:
self._notify_enabled_count -= 1
if force:
self._notify_enabled_count = 0
def disable_notifications(self):
- ''' disables data notifications from this block '''
+ """disables data notifications from this block"""
self._notify_enabled_count -= 1
@property
def notifications_enabled(self):
- ''' true if notifications are enabled'''
+ """true if notifications are enabled"""
return self._notify_enabled_count == 0
-
+
@property
def is_periodic(self) -> bool:
- ''' determines if the data should be sent when it changes (true) or just one time (false)'''
+ """determines if the data should be sent when it changes (true) or just one time (false)"""
return self._is_periodic
-
+
@is_periodic.setter
def is_periodic(self, value: bool):
self._is_periodic = value
@property
def command(self):
- ''' the block command'''
+ """the block command"""
return self._command
@command.setter
@@ -1784,35 +1800,34 @@ def command(self, value):
self._command = value
# update flags
self.is_axis = value in _simconnect_full_range
-
-
@property
def is_axis(self):
- ''' true if the command supports axis output '''
+ """true if the command supports axis output"""
return self._is_axis
@property
def is_request(self) -> bool:
- ''' true if the block is a request '''
+ """true if the block is a request"""
return self._command_type == SimConnectCommandType.Request
@property
def is_event(self) -> bool:
- ''' true if the block is an event '''
+ """true if the block is an event"""
return self._command_type == SimConnectCommandType.Event
@property
def is_value(self):
- ''' true if the command supports a value output to simconnect '''
+ """true if the command supports a value output to simconnect"""
return self._is_value
@property
def command_type(self) -> SimConnectCommandType:
- ''' returns the command type '''
+ """returns the command type"""
return self._command_type
+
@command_type.setter
- def command_type(self, value : SimConnectCommandType):
+ def command_type(self, value: SimConnectCommandType):
if isinstance(value, str):
value = SimConnectCommandType.to_string(value)
elif isinstance(value, int):
@@ -1821,28 +1836,28 @@ def command_type(self, value : SimConnectCommandType):
@property
def display_block_type(self) -> str:
- ''' returns the display string for the block type '''
+ """returns the display string for the block type"""
if self._command_type == SimConnectCommandType.Request:
return "Simconnect Request"
elif self._command_type == SimConnectCommandType.Event:
return "Simconnect Event"
- return F"Unknown command type: {self._command_type}"
+ return f"Unknown command type: {self._command_type}"
@property
def output_mode(self) -> SimConnectActionMode:
- ''' output mode '''
+ """output mode"""
return self._output_mode
@output_mode.setter
- def output_mode(self, value : SimConnectActionMode):
+ def output_mode(self, value: SimConnectActionMode):
if value == SimConnectActionMode.Trigger:
pass
self._output_mode = value
@property
def output_data_type(self) -> str:
- ''' block output data type'''
+ """block output data type"""
return self._output_data_type
@output_data_type.setter
@@ -1851,15 +1866,16 @@ def output_data_type(self, value):
@property
def category(self) -> str:
- ''' command category '''
+ """command category"""
return self._category
+
@category.setter
def category(self, value):
self._category = value
@property
def is_readonly(self) -> bool:
- ''' true if readonly - based on the command type '''
+ """true if readonly - based on the command type"""
return self._readonly
@is_readonly.setter
@@ -1868,7 +1884,7 @@ def is_readonly(self, value):
@property
def is_axis(self) -> bool:
- ''' true if axis - based on the command type '''
+ """true if axis - based on the command type"""
return self._axis
@is_axis.setter
@@ -1877,7 +1893,7 @@ def is_axis(self, value):
@property
def is_indexed(self) -> bool:
- ''' true if readonly - based on the command type '''
+ """true if readonly - based on the command type"""
return self._is_indexed
@is_indexed.setter
@@ -1886,8 +1902,9 @@ def is_indexed(self, value):
@property
def units(self) -> str:
- ''' given units '''
+ """given units"""
return self._units
+
@units.setter
def units(self, value):
self._units = value
@@ -1895,19 +1912,19 @@ def units(self, value):
@property
def display_data_type(self) -> str:
return self._value_type
-
@property
def invert_axis(self):
- ''' inverts output (axis input only) '''
+ """inverts output (axis input only)"""
return self._invert
+
@invert_axis.setter
def invert_axis(self, value):
self._invert = value
@property
def min_range(self):
- ''' current min range '''
+ """current min range"""
return self._min_range
@min_range.setter
@@ -1916,7 +1933,7 @@ def min_range(self, value):
@property
def max_range(self):
- ''' current max range '''
+ """current max range"""
return self._max_range
@max_range.setter
@@ -1925,42 +1942,40 @@ def max_range(self, value):
@property
def command_min_range(self):
- ''' current min range '''
+ """current min range"""
return self._command_min_range
-
@property
def command_max_range(self):
- ''' current max range '''
+ """current max range"""
return self._command_max_range
-
@property
def trigger_mode(self) -> SimConnectTriggerMode:
- ''' block trigger mode if the action mode is in trigger mode '''
+ """block trigger mode if the action mode is in trigger mode"""
return self._trigger_mode
@trigger_mode.setter
- def trigger_mode(self, value : SimConnectTriggerMode):
+ def trigger_mode(self, value: SimConnectTriggerMode):
self._trigger_mode = value
@property
def is_toggle(self):
- ''' true if the range output is only two values - min or max'''
+ """true if the range output is only two values - min or max"""
return self._is_toggle
+
@is_toggle.setter
def is_toggle(self, value):
self._is_toggle = value
def custom_range_sync(self):
- ''' makes custom range the same as the default range '''
+ """makes custom range the same as the default range"""
self._min_range_custom = self._min_range
self._max_range_custom = self._max_range_custom
self._notify_range_update()
-
def _notify_range_update(self):
- ''' fires a range changed event '''
+ """fires a range changed event"""
if self.notifications_enabled:
event = RangeEvent()
@@ -1972,14 +1987,13 @@ def _notify_range_update(self):
eh = SimConnectEventHandler()
eh.range_changed.emit(self, event)
-
@property
def value(self):
return self._value
@value.setter
def value(self, number):
- ''' sets the output value'''
+ """sets the output value"""
if number != self._value:
self._value = number
if self.notifications_enabled:
@@ -1988,12 +2002,12 @@ def value(self, number):
@property
def is_ranged(self):
- ''' true if the command is a ranged command (suitable for a range of values)'''
+ """true if the command is a ranged command (suitable for a range of values)"""
return self._output_mode == SimConnectActionMode.Ranged
-
+
@property
def description(self) -> str:
- ''' returns the command description'''
+ """returns the command description"""
return self._description
@description.setter
@@ -2006,8 +2020,8 @@ def valid(self) -> bool:
@property
def display_name(self) -> str:
- ''' returns a readable form of the block '''
- stub = f"Command:{self.command} Mode: {SimConnectActionMode.to_display(self.output_mode)}"
+ """returns a readable form of the block"""
+ stub = f"Command:{self.command} Mode: {SimConnectActionMode.to_display(self.output_mode)}"
if self.output_mode == SimConnectActionMode.SetValue:
stub += f" Value: {self.value}"
elif self.output_mode == SimConnectActionMode.Ranged:
@@ -2018,26 +2032,25 @@ def display_name(self) -> str:
return stub
-
def _set_range(self):
- ''' sets the data range from the current command '''
-
+ """sets the data range from the current command"""
+
if self._command in _simconnect_half_range:
min_range = 0
max_range = 16383
-
+
elif self._command in _simconnect_angle_range:
min_range = 0
max_range = 360
-
+
elif self._command in _simconnect_egt_range:
min_range = 0
max_range = 32767
-
+
elif self.command in _simconnect_full_range:
min_range = -16383
max_range = 16383
-
+
else:
# default
min_range = -16383
@@ -2050,35 +2063,34 @@ def _set_range(self):
if self._max_range != max_range:
self._max_range = max_range
changed = True
-
+
if changed:
self._notify_range_update()
def map_range(self, value: float) -> int:
- ''' maps the input float value -1 to +1 to the command's range '''
- if value < -1.0: value = -1.0
- if value > 1.0: value = 1.0
+ """maps the input float value -1 to +1 to the command's range"""
+ if value < -1.0:
+ value = -1.0
+ if value > 1.0:
+ value = 1.0
r_min = self._min_range
r_max = self._max_range
- return round(r_min + (value + 1.0)*((r_max - r_min)/2.0))
-
+ return round(r_min + (value + 1.0) * ((r_max - r_min) / 2.0))
@staticmethod
def from_command(self, command):
- ''' '''
+ """ """
block = SimConnectBlock(command)
return block
-
def register(self):
- ''' registers a block command '''
+ """registers a block command"""
if not self.sm.is_connected():
# not connectedkl
return False
if self._command:
-
- if self._command_type == SimConnectCommandType.Event:
+ if self._command_type == SimConnectCommandType.Event:
ae = AircraftEvents(self.sm)
trigger = ae.find(self._command)
if trigger:
@@ -2090,14 +2102,19 @@ def register(self):
ar = AircraftRequests(self.sm, time=2000)
self._request = ar.request(self._command)
if self.verbose:
- syslog.info(f"Simconnect: register simvar: '{self._command}' mode: {self.output_mode}")
+ syslog.info(
+ f"Simconnect: register simvar: '{self._command}' mode: {self.output_mode}"
+ )
return True
- elif self._command_type == SimConnectCommandType.Request and not self._readonly:
+ elif (
+ self._command_type == SimConnectCommandType.Request
+ and not self._readonly
+ ):
ar = AircraftRequests(self.sm, time=2000)
self._request = ar.request(self._command)
if self._request:
- self._request._ensure_def()
+ self._request._ensure_def()
self._request.callback = self.request_changed_callback
if self.is_periodic:
self.sm._request_periodic_data(self._request)
@@ -2109,170 +2126,202 @@ def register(self):
syslog.error(f"Simmconnect: unknown command '{self._command}'")
return True
- return False
+ return False
def read(self):
- ''' gets a value from simconnect '''
+ """gets a value from simconnect"""
if not self.sm.is_connected():
# not connected
return None
- return self.execute(mode = SimConnectActionMode.GetValue)
+ return self.execute(mode=SimConnectActionMode.GetValue)
- def execute(self, value = None, mode : SimConnectActionMode = None):
- ''' executes the command '''
+ def execute(self, value=None, mode: SimConnectActionMode = None):
+ """executes the command"""
if not self.sm.is_connected():
# not connected
- syslog.warning(f"SIMCONNECT: trigger failed - not connected ({self._command})")
+ syslog.warning(
+ f"SIMCONNECT: trigger failed - not connected ({self._command})"
+ )
return False
-
+
mgr = SimConnectManager()
verbose = gremlin.config.Configuration().verbose_mode_details
- if verbose: syslog.info(f"SIMCONNECT: block execute: ({self._command}) value: ({value})")
+ if verbose:
+ syslog.info(
+ f"SIMCONNECT: block execute: ({self._command}) value: ({value})"
+ )
if self._command:
if self._command_type == SimConnectCommandType.Event:
- if verbose: syslog.info(f"\tcommand type: event")
+ if verbose:
+ syslog.info("\tcommand type: event")
ae = AircraftEvents(self.sm)
trigger = ae.find(self._command)
if trigger:
if self.is_readonly:
# no param to set
- if verbose: syslog.info(f"\ttrigger event (single) readonly")
+ if verbose:
+ syslog.info("\ttrigger event (single) readonly")
trigger()
else:
- if verbose: syslog.info(f"\ttrigger event value")
+ if verbose:
+ syslog.info("\ttrigger event value")
trigger(value)
return True
else:
syslog.error(f"SIMCONNECT: event: '{self._command}' not found")
elif self._command_type == SimConnectCommandType.SimVar:
# set simvar
- if verbose: syslog.info(f"\tcommand type: simvar")
+ if verbose:
+ syslog.info("\tcommand type: simvar")
ae = AircraftEvents(self.sm)
request = mgr.findRequest(self._command)
if not request:
-
trigger = ae.find(self._command)
if trigger:
if self.is_readonly:
# no param to set
- if verbose: syslog.info(f"\ttrigger event (single): {self._command}")
+ if verbose:
+ syslog.info(
+ f"\ttrigger event (single): {self._command}"
+ )
trigger()
else:
- if verbose: syslog.info(f"\ttrigger event value: {self._command} {value}")
+ if verbose:
+ syslog.info(
+ f"\ttrigger event value: {self._command} {value}"
+ )
trigger(value)
return True
-
+
if not request:
# register it
- if verbose: syslog.info(f"\tregistering request...")
- request = mgr.registerRequest(self._command, self._units, self._value)
+ if verbose:
+ syslog.info("\tregistering request...")
+ request = mgr.registerRequest(
+ self._command, self._units, self._value
+ )
if not request:
- syslog.error(f"\trequest: '{self._command}' not found or could not be registered.")
+ syslog.error(
+ f"\trequest: '{self._command}' not found or could not be registered."
+ )
return False
- if verbose: syslog.info(f"\tset simvar: '{self._command}' mode: {self.output_mode}")
+ if verbose:
+ syslog.info(
+ f"\tset simvar: '{self._command}' mode: {self.output_mode}"
+ )
if mode is None:
mode = self.output_mode
ar = AircraftRequests()
if mode == SimConnectActionMode.Trigger:
trigger_mode = self.trigger_mode
- if verbose: syslog.info(f"\taircraft request trigger mode")
+ if verbose:
+ syslog.info("\taircraft request trigger mode")
match trigger_mode:
- case SimConnectTriggerMode.Toggle:
+ case SimConnectTriggerMode.Toggle:
# get the current state and flip it
if request:
state = ar.get(self._command)
value = 1 if state == 0 else 0
- if self.verbose: syslog.info(f"\t\tToggle state: {state} -> {value}")
+ if self.verbose:
+ syslog.info(f"\t\tToggle state: {state} -> {value}")
case SimConnectTriggerMode.TurnOff:
-
value = 0
- if verbose: syslog.info(f"\t\tTrigger Turn off: {value}")
+ if verbose:
+ syslog.info(f"\t\tTrigger Turn off: {value}")
case SimConnectTriggerMode.TurnOn:
value = 1
- if verbose: syslog.info(f"\t\tTrigger Turn on: {value}")
+ if verbose:
+ syslog.info(f"\t\tTrigger Turn on: {value}")
case SimConnectTriggerMode.NoOp:
value = 1
- if verbose: syslog.info(f"\t\tTrigger No op: {value}")
-
+ if verbose:
+ syslog.info(f"\t\tTrigger No op: {value}")
+
case SimConnectTriggerMode.InputValue:
if isinstance(value, bool):
value = 1 if value else 0
- if verbose: syslog.info(f"\t\tTrigger InputValue: {value}")
-
+ if verbose:
+ syslog.info(f"\t\tTrigger InputValue: {value}")
+
# regular request
request.set(value)
elif mode == SimConnectActionMode.SetValue:
- if verbose: syslog.info(f"\tset value mode:")
+ if verbose:
+ syslog.info("\tset value mode:")
ar.set(self._command, value)
- if verbose: syslog.info(f"\t\tSet value: {value}")
+ if verbose:
+ syslog.info(f"\t\tSet value: {value}")
elif mode == SimConnectActionMode.GetValue:
value = ar.get(self._command)
- if verbose: syslog.info(f"\t\tGet value: {value}")
+ if verbose:
+ syslog.info(f"\t\tGet value: {value}")
return value
return True
-
-
-
-
- elif self._command_type == SimConnectCommandType.Request and not self._readonly:
- if verbose: syslog.info(f"\tcommand type: request")
+ elif (
+ self._command_type == SimConnectCommandType.Request
+ and not self._readonly
+ ):
+ if verbose:
+ syslog.info("\tcommand type: request")
ar = AircraftRequests(self.sm, time=2000)
self._request = ar.request(self._command)
if self._request:
- self._request._ensure_def()
+ self._request._ensure_def()
self._request.callback = self.request_changed_callback
if self.is_periodic:
self.sm._request_periodic_data(self._request)
else:
self.sm._request_data(self._request)
- if verbose: syslog.info(f"\tset request {self._command} {value}")
+ if verbose:
+ syslog.info(f"\tset request {self._command} {value}")
else:
syslog.error(f"\tError: unknown command '{self._command}'")
return True
return False
-
+
def request_changed_callback(self):
- ''' called when the request receives data '''
+ """called when the request receives data"""
sh = SimConnectEventHandler()
if self.verbose_detailed:
- syslog.info(f"Simconnect block: {self._command} Received data: {self._request.buffer} data type: {type(self._request.buffer).__name__}")
+ syslog.info(
+ f"Simconnect block: {self._command} Received data: {self._request.buffer} data type: {type(self._request.buffer).__name__}"
+ )
event = SimConnectEvent(self._command, self._request.buffer)
sh.simconnect_event.emit(event)
-
+
def stop(self):
- ''' stops a periodic request '''
+ """stops a periodic request"""
request = self._request
- if request is not None: # request was made
- if self.is_periodic: # request is periodic
- self.sm.stop_periodic_data(request) # tell the sim to stop sending data
- self.sm.clear(request) # remove the definition
+ if request is not None: # request was made
+ if self.is_periodic: # request is periodic
+ self.sm.stop_periodic_data(request) # tell the sim to stop sending data
+ self.sm.clear(request) # remove the definition
def to_xml(self):
- ''' writes to an xml node block '''
+ """writes to an xml node block"""
if self.is_lvar:
# LVAR format (anything that is handled internally by the sim via calculator code)
- node = etree.Element("command") # rpn
+ node = etree.Element("command") # rpn
node.set("type", SimConnectCommandType.to_string(self.command_type))
if self.output_data_type:
node.set("datatype", self.output_data_type)
node.set("units", self.units)
- node.set("category","none")
+ node.set("category", "none")
node.set("settable", str(not self._readonly))
node.set("axis", str(self.is_axis))
node.set("indexed", str(self.is_indexed))
else:
-
node = etree.Element("block")
node.set("command", gremlin.util.safe_format(self.command, str))
diff --git a/action_plugins/map_to_simconnect/__init__.py b/action_plugins/map_to_simconnect/__init__.py
index a59f6429..1dbcb0fd 100644
--- a/action_plugins/map_to_simconnect/__init__.py
+++ b/action_plugins/map_to_simconnect/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -22,52 +22,50 @@
import gremlin.base_profile
import gremlin.config
-import gremlin.config
import gremlin.event_handler
from gremlin.input_types import InputType
from gremlin.input_devices import ButtonReleaseActions
import gremlin.macro
import gremlin.shared_state
-import gremlin.shared_state
-import gremlin.shared_state
import gremlin.singleton_decorator
import gremlin.ui.ui_common
import gremlin.ui.input_item
import gremlin.input_devices
-#import gremlin.gated_handler
-import enum
+
+# import gremlin.gated_handler
+from enum import Enum
from gremlin.profile import safe_format, safe_read
import gremlin.util
from .SimConnectManager import *
import re
-from lxml import etree
from lxml import etree as ElementTree
-#from gremlin.gated_handler import *
+
+# from gremlin.gated_handler import *
from gremlin.ui.qdatawidget import QDataWidget
import gremlin.config
import gremlin.joystick_handling
import gremlin.actions
import gremlin.curve_handler
-from gremlin.input_types import InputType
from action_plugins.map_to_simconnect.SimConnectManager import SimConnectManager
syslog = logging.getLogger("system")
+
class QHLine(QtWidgets.QFrame):
- def __init__(self, parent = None):
+ def __init__(self, parent=None):
super().__init__(parent)
self.setFrameShape(QtWidgets.QFrame.Shape.HLine)
self.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
class CommandValidator(QtGui.QValidator):
- ''' validator for command selection '''
+ """validator for command selection"""
+
def __init__(self):
super().__init__()
self.commands = SimConnectManager().get_command_name_list()
-
-
+
def validate(self, value, pos):
clean_value = value.upper().strip()
if not clean_value or clean_value in self.commands:
@@ -82,13 +80,15 @@ def validate(self, value, pos):
# invalid regex - probably a special char
pass
return QtGui.QValidator.State.Invalid
-
+
+
class LvarValidator(QtGui.QValidator):
- ''' validator for lvars selection '''
+ """validator for lvars selection"""
+
def __init__(self):
super().__init__()
self.manager = SimConnectManager()
-
+
def validate(self, value, pos):
clean_value = value.strip().casefold()
if not clean_value or clean_value in self.manager.lvars:
@@ -102,87 +102,84 @@ def validate(self, value, pos):
except:
# invalid regex - probably a special char
pass
- return QtGui.QValidator.State.Invalid
-
+ return QtGui.QValidator.State.Invalid
+
@property
def lvars(self):
return self.manager.lvars
-
+
class SimconnectSortMode(Enum):
NotSet = auto()
AicraftAscending = auto()
AircraftDescending = auto()
Mode = auto()
+
class SimConnectCommandMode(Enum):
- Simvar = 0 # simvar command mode
- Calculator = 1 # lvar command mode
- CalculatorParam = 2 # expression with parameter (axis)
-
+ Simvar = 0 # simvar command mode
+ Calculator = 1 # lvar command mode
+ CalculatorParam = 2 # expression with parameter (axis)
@staticmethod
def to_string(value) -> str:
return _simconnect_command_mode_to_string[value]
-
+
@staticmethod
def to_enum(value):
return _simconnect_command_mode_to_enum[value]
-
+
@staticmethod
def to_display(value) -> str:
return _simconnect_command_mode_to_display[value]
-
+
@staticmethod
def to_description(value) -> str:
return _simconnect_command_mode_to_description[value]
+
_simconnect_command_mode_to_display = {
- SimConnectCommandMode.Simvar : "SimVar",
- SimConnectCommandMode.Calculator : "Calculator",
- SimConnectCommandMode.CalculatorParam : "Calculator (value)",
+ SimConnectCommandMode.Simvar: "SimVar",
+ SimConnectCommandMode.Calculator: "Calculator",
+ SimConnectCommandMode.CalculatorParam: "Calculator (value)",
}
_simconnect_command_mode_to_description = {
- SimConnectCommandMode.Simvar : "Regular simVar",
- SimConnectCommandMode.Calculator : "Evaluated RPN expression and calculator code",
- SimConnectCommandMode.CalculatorParam : "Evaluated RPN expression and calculator code with axis parameter",
+ SimConnectCommandMode.Simvar: "Regular simVar",
+ SimConnectCommandMode.Calculator: "Evaluated RPN expression and calculator code",
+ SimConnectCommandMode.CalculatorParam: "Evaluated RPN expression and calculator code with axis parameter",
}
_simconnect_command_mode_to_string = {
- SimConnectCommandMode.Simvar : "simvar",
- SimConnectCommandMode.Calculator : "rpn",
- SimConnectCommandMode.CalculatorParam : "rpnparam",
+ SimConnectCommandMode.Simvar: "simvar",
+ SimConnectCommandMode.Calculator: "rpn",
+ SimConnectCommandMode.CalculatorParam: "rpnparam",
}
_simconnect_command_mode_to_enum = {
- "simvar" : SimConnectCommandMode.Simvar,
- "lvar" : SimConnectCommandMode.Calculator,
- "rpn" : SimConnectCommandMode.Calculator,
- "rpnparam" : SimConnectCommandMode.CalculatorParam,
+ "simvar": SimConnectCommandMode.Simvar,
+ "lvar": SimConnectCommandMode.Calculator,
+ "rpn": SimConnectCommandMode.Calculator,
+ "rpnparam": SimConnectCommandMode.CalculatorParam,
}
+class SimconnectManualDefinition:
+ """holds a manual entry for a mode"""
-class SimconnectManualDefinition():
- ''' holds a manual entry for a mode '''
- def __init__(self,
- id = None,
- sim_name = None,
- mode = None):
-
+ def __init__(self, id=None, sim_name=None, mode=None):
self.id = id if id else gremlin.util.get_guid()
self.sim_name = sim_name
self.mode = mode
# runtime item (not saved or loaded)
- self.selected = False # for UI interation - selected mode
+ self.selected = False # for UI interation - selected mode
self.error_status = None
@property
def display_name(self):
return f"{self.sim_name}"
-
+
@property
def key(self):
if self.sim_name:
@@ -190,26 +187,28 @@ def key(self):
return ""
+class SimconnectAicraftDefinition:
+ """holds the data entry for a single aicraft from the MSFS config data"""
-
-class SimconnectAicraftDefinition():
- ''' holds the data entry for a single aicraft from the MSFS config data '''
class EntryType(IntEnum):
- Scan = 0 # entry is coming from the manual scan of the community folder
- Sim = 1 # entry is coming from the sim
- def __init__(self, id = None,
- mode = None, # attached GremlinEx mode for this aicraft
- icao_type = None,
- icao_manufacturer = None,
- icao_model = None,
- titles = [],
- path = None,
- community_path = None,
- aircraft_path = None,
- state_folder = None,
- sim_name = None,
- entry_type = None,
- ):
+ Scan = 0 # entry is coming from the manual scan of the community folder
+ Sim = 1 # entry is coming from the sim
+
+ def __init__(
+ self,
+ id=None,
+ mode=None, # attached GremlinEx mode for this aicraft
+ icao_type=None,
+ icao_manufacturer=None,
+ icao_model=None,
+ titles=[],
+ path=None,
+ community_path=None,
+ aircraft_path=None,
+ state_folder=None,
+ sim_name=None,
+ entry_type=None,
+ ):
self.icao_type = icao_type
self.icao_manufacturer = icao_manufacturer
self.icao_model = icao_model
@@ -219,95 +218,105 @@ def __init__(self, id = None,
self.mode = mode
self.sim_name = sim_name
self.id = id if id else gremlin.util.get_guid()
- self.entry_type = SimconnectAicraftDefinition.EntryType.Scan if entry_type is None else entry_type
+ self.entry_type = (
+ SimconnectAicraftDefinition.EntryType.Scan
+ if entry_type is None
+ else entry_type
+ )
self.community_path = None
self.aircraft_path = None
if self.entry_type == SimconnectAicraftDefinition.EntryType.Scan:
- assert community_path and aircraft_path,"Community path and Aircraft path are primary keys and cannot be NULL"
+ assert community_path and aircraft_path, (
+ "Community path and Aircraft path are primary keys and cannot be NULL"
+ )
self.community_path = community_path.casefold() # AP
- self.aircraft_path = aircraft_path.casefold() # CP
-
+ self.aircraft_path = aircraft_path.casefold() # CP
+
# runtime item (not saved or loaded)
- self.selected = False # for UI interation - selected mode
+ self.selected = False # for UI interation - selected mode
self.error_status = None
-
@property
def is_scanned(self) -> bool:
- ''' true if the entry was scanned from the community folder '''
+ """true if the entry was scanned from the community folder"""
return self.entry_type == SimconnectAicraftDefinition.EntryType.Scan
+
@property
def is_scanned(self) -> bool:
- ''' true if the entry came from msfs user flyable data '''
+ """true if the entry came from msfs user flyable data"""
return self.entry_type == SimconnectAicraftDefinition.EntryType.Sim
-
-
@property
def display_name(self):
if self.icao_manufacturer and self.icao_model:
return f"{self.icao_manufacturer} {self.icao_model}"
return self.sim_name
-
+
@property
def key(self):
- ''' key for this item (CP = community path, AP = aircraft path)'''
+ """key for this item (CP = community path, AP = aircraft path)"""
match self.entry_type:
case SimconnectAicraftDefinition.EntryType.Scan:
- return (self.community_path, self.aircraft_path)
+ return (self.community_path, self.aircraft_path)
case SimconnectAicraftDefinition.EntryType.Sim:
return self.sim_name
return None
-
@property
def valid(self):
- ''' true if the item contains valid data '''
+ """true if the item contains valid data"""
match self.entry_type:
case SimconnectAicraftDefinition.EntryType.Scan:
return not self.error_status and self.aircraft_path and self.mode
case SimconnectAicraftDefinition.EntryType.Sim:
return bool(self.sim_name) and not self.error_status
return False
-
+
def __hash__(self):
return hash(self.id)
-
+
+
@gremlin.singleton_decorator.SingletonDecorator
-class SimconnectOptions():
+class SimconnectOptions:
+ """holds simconnect mapper options for all actions"""
- ''' holds simconnect mapper options for all actions '''
- def __init__(self, manager : SimConnectManager):
- self._manager : SimConnectManager = manager
+ def __init__(self, manager: SimConnectManager):
+ self._manager: SimConnectManager = manager
el = gremlin.event_handler.EventListener()
- el.profile_loaded.connect(self._profile_loaded) # trap profile load to update modes
- el.profile_start.connect(self._profile_edit_mode_changed) # trap profile start to update modes
- el.edit_mode_changed.connect(self._profile_edit_mode_changed) # trap edit mode mode changes to update modes
- el.shutdown.connect(self.save) # save configuration on shutdown
+ el.profile_loaded.connect(
+ self._profile_loaded
+ ) # trap profile load to update modes
+ el.profile_start.connect(
+ self._profile_edit_mode_changed
+ ) # trap profile start to update modes
+ el.edit_mode_changed.connect(
+ self._profile_edit_mode_changed
+ ) # trap edit mode mode changes to update modes
+ el.shutdown.connect(self.save) # save configuration on shutdown
self._handler = SimConnectEventHandler()
- self._handler.simconnect_AircraftLiveriesReceived.connect(self._aircraft_list_loaded)
-
-
-
+ self._handler.simconnect_AircraftLiveriesReceived.connect(
+ self._aircraft_list_loaded
+ )
# configuration file stored in the user's GremlinEx profile
base_file = "simconnect_config.xml"
user_source = os.path.join(gremlin.util.userprofile_path(), base_file)
self._xml_source = user_source
- self._auto_mode_select = True # if set, autoloads the mode associated with the aircraft if such a mode exists, on by default
- self._auto_mode_lock = True # if set, mode changes other the mapped aicraft will be ignored
- self._aircraft_definition_map = {} # holds definitions by aircraft container name, [name] = SimconnectAicraftDefinition
- self._aircraft_manual_definitions = [] # holds manual aicraft entries
+ self._auto_mode_select = True # if set, autoloads the mode associated with the aircraft if such a mode exists, on by default
+ self._auto_mode_lock = (
+ True # if set, mode changes other the mapped aicraft will be ignored
+ )
+ self._aircraft_definition_map = {} # holds definitions by aircraft container name, [name] = SimconnectAicraftDefinition
+ self._aircraft_manual_definitions = [] # holds manual aicraft entries
self._titles = []
-
- self._base_community_folder = None # base of community folder
- self._local_state_folder = None # local state folder for streaming data
+
+ self._base_community_folder = None # base of community folder
+ self._local_state_folder = None # local state folder for streaming data
self._community_folder = gremlin.shared_state.community_folder
self._update_folders()
-
# last command mode for the UI
self._last_command_mode = SimConnectCommandMode.Simvar
@@ -325,7 +334,7 @@ def definitions(self) -> dict:
return self._aircraft_definition_map
def validateEntries(self) -> bool:
- ''' validates the manual entries to make sure they are unique '''
+ """validates the manual entries to make sure they are unique"""
sim_names = []
for item in self._aircraft_manual_definitions:
if item.sim_name and item.sim_name in sim_names:
@@ -333,15 +342,13 @@ def validateEntries(self) -> bool:
sim_names.append(item.sim_name)
return True
-
-
@QtCore.Slot(dict)
def _aircraft_list_loaded(self, data):
- ''' triggered when simconnect sends aircraft data '''
+ """triggered when simconnect sends aircraft data"""
added = False
verbose = gremlin.config.Configuration().verbose_mode_simconnect
name_list = [name for name in data.keys()]
- name_list.sort(key = lambda x: x.casefold()) # sort case insensitive
+ name_list.sort(key=lambda x: x.casefold()) # sort case insensitive
for aircraft in name_list:
key = aircraft.casefold()
if "fsltl" in key or "passiveaircraft" in key:
@@ -350,27 +357,28 @@ def _aircraft_list_loaded(self, data):
continue
if "a350" in key:
pass
- if not key in self._aircraft_definition_map:
- item = SimconnectAicraftDefinition(sim_name = aircraft,
- entry_type=SimconnectAicraftDefinition.EntryType.Sim,
- )
+ if key not in self._aircraft_definition_map:
+ item = SimconnectAicraftDefinition(
+ sim_name=aircraft,
+ entry_type=SimconnectAicraftDefinition.EntryType.Sim,
+ )
self._aircraft_definition_map[key] = item
- if verbose: syslog.info(f"SIMCONNECT: add sim user aircraft: {aircraft}")
+ if verbose:
+ syslog.info(f"SIMCONNECT: add sim user aircraft: {aircraft}")
added = True
-
+
if added:
# fire the event the data changed
self._handler.AircraftDefinitionsChanged.emit()
-
@QtCore.Slot()
def _profile_loaded(self):
- ''' profile is loaded '''
+ """profile is loaded"""
self._mode_list = self.profile.get_modes()
@QtCore.Slot()
def _profile_edit_mode_changed(self):
- ''' profile modes changed '''
+ """profile modes changed"""
self._mode_list = self.profile.get_modes()
@property
@@ -380,14 +388,15 @@ def profile(self) -> gremlin.base_profile.Profile:
@property
def current_aircraft_folder(self):
return self._manager.current_aircraft_folder
-
+
@property
def current_aircraft_title(self):
return self._manager.current_aircraft_title
-
+
@property
def community_folder(self) -> str:
return self._community_folder
+
@community_folder.setter
def community_folder(self, value):
if os.path.isdir(value) and value != self._community_folder:
@@ -399,9 +408,8 @@ def community_folder(self, value):
def local_state_folder(self) -> str:
return self._local_state_folder
-
def _update_folders(self):
- ''' updates the folders from the community folder '''
+ """updates the folders from the community folder"""
community_folder = self._community_folder
if community_folder and os.path.isdir(community_folder):
basedir = os.path.dirname(community_folder)
@@ -417,20 +425,22 @@ def _update_folders(self):
self._base_community_folder = base_folder
# setup the local state folder
- local_state_folder = os.path.join(base_folder, "MSFS2024 LocalState", "StreamedPackages")
+ local_state_folder = os.path.join(
+ base_folder, "MSFS2024 LocalState", "StreamedPackages"
+ )
if os.path.isdir(local_state_folder):
self._local_state_folder = local_state_folder
-
@property
def last_command_mode(self) -> SimConnectCommandMode:
return self._last_command_mode
+
@last_command_mode.setter
def last_command_mode(self, value: SimConnectCommandMode):
self._last_command_mode = value
def validate(self):
- ''' validates options are ok '''
+ """validates options are ok"""
a_list = []
valid = True
for item in self._aircraft_definition_map.values():
@@ -441,35 +451,35 @@ def validate(self):
continue
a_list.append(item.key)
if not item.mode:
- item.error_status = f"Mode not selected"
+ item.error_status = "Mode not selected"
valid = False
continue
- if not item.mode in self._mode_list:
+ if item.mode not in self._mode_list:
item.error_status = f"Invalid mode {item.mode}"
valid = False
continue
if not item.display_name:
- item.error_status = f"Aircraft name cannot be blank"
+ item.error_status = "Aircraft name cannot be blank"
valid = False
return valid
-
+
def find_definition_by_state(self, state_string):
- ''' gets an item based on the state data which is a partial subfolder '''
+ """gets an item based on the state data which is a partial subfolder"""
# example: SimObjects\\Airplanes\\FNX_320_IAE\\aircraft.CFG
stub = os.path.dirname(state_string.casefold())
- item : SimconnectAicraftDefinition
- print (stub)
+ item: SimconnectAicraftDefinition
+ print(stub)
for item in self._aircraft_definition_map.values():
- print (item.path)
+ print(item.path)
if item.path.endswith(stub):
return item
return None
-
+
def dump(self):
- ''' dumps current data to the log file '''
+ """dumps current data to the log file"""
# syslog = logging.getLogger("system")
syslog.info("Scanned entry mode configurations:")
for item in self._aircraft_definition_map.values():
@@ -479,112 +489,133 @@ def dump(self):
for item in self._aircraft_manual_definitions:
syslog.info(f"\t{item.display_name} {item.sim_name} mode: {item.mode}")
-
- def find_definition_by_sim_name(self, key, is_scan = True, is_manual = True):
- ''' gets an item based on the state data which is a partial subfolder '''
+ def find_definition_by_sim_name(self, key, is_scan=True, is_manual=True):
+ """gets an item based on the state data which is a partial subfolder"""
key = key.casefold()
verbose = gremlin.config.Configuration().verbose_mode_details
- if verbose: self.dump()
+ if verbose:
+ self.dump()
if is_scan:
# lookup scanned entries
if key in self._aircraft_definition_map:
return self._aircraft_definition_map[key]
-
+
return None
if is_manual:
# lookup manual entries
- item = next((item for item in self._aircraft_manual_definitions if item.sim_name == key), None)
+ item = next(
+ (
+ item
+ for item in self._aircraft_manual_definitions
+ if item.sim_name == key
+ ),
+ None,
+ )
if item:
return item
return None
-
- def find_definition_by_aicraft(self, aircraft) -> SimconnectAicraftDefinition:
- ''' gets an item by aircraft name (not case sensitive)'''
+ def find_definition_by_aircraft(
+ self, aircraft
+ ) -> SimconnectAicraftDefinition | None:
+ """gets an item by aircraft name (not case sensitive)"""
if not aircraft:
return None
key = aircraft.casefold().strip()
- item : SimconnectAicraftDefinition
+ item: SimconnectAicraftDefinition
if key in self._aircraft_definition_map:
return self._aircraft_definition_map[key]
return None
-
+
def find_definition_by_title(self, title) -> SimconnectAicraftDefinition:
- ''' finds aircraft data by the loaded aircraft title '''
+ """finds aircraft data by the loaded aircraft title"""
if not title:
return None
- item = next((n for n in self._aircraft_definition_map.values() if n.title in item.titles), None)
- return item
+ item = next(
+ (n for n in self._aircraft_definition_map.values() if title in n.titles),
+ None,
+ )
+ return item
- def find_definition_by_aicraft_folder(self, folder) -> SimconnectAicraftDefinition:
- ''' gets an item by aircraft name (not case sensitive)'''
+ def find_definition_by_aircraft_folder(self, folder) -> SimconnectAicraftDefinition:
+ """gets an item by aircraft name (not case sensitive)"""
if not folder:
return None
key = folder.casefold().strip()
- item : SimconnectAicraftDefinition
- item = next((n for n in self._aircraft_definition_map.values() if n.aircraft_path == key), None)
+ item: SimconnectAicraftDefinition
+ item = next(
+ (
+ n
+ for n in self._aircraft_definition_map.values()
+ if n.aircraft_path == key
+ ),
+ None,
+ )
return item
-
-
+
@property
def auto_mode_select(self):
- ''' true if automatic mode selection for aicraft is enabled '''
+ """true if automatic mode selection for aicraft is enabled"""
return self._auto_mode_select
+
@auto_mode_select.setter
def auto_mode_select(self, value):
self._auto_mode_select = value
-
+
@property
def auto_mode_lock(self):
- ''' true if mode locking is enabled '''
- return self._auto_mode_lock and self._auto_mode_select # both must be enabled to lock a profile
+ """true if mode locking is enabled"""
+ return (
+ self._auto_mode_lock and self._auto_mode_select
+ ) # both must be enabled to lock a profile
+
@auto_mode_lock.setter
def auto_mode_lock(self, value):
self._auto_mode_lock = value
-
-
-
-
-
-
def save(self):
- ''' saves the configuration data '''
+ """saves the configuration data"""
self.to_xml()
- def parse_xml(self, data = None):
+ def parse_xml(self, data=None):
xml_source = self._xml_source
if not os.path.isfile(xml_source):
# options not saved yet - ignore
return
-
-
+
self._titles = []
self._aircraft_manual_definitions = []
self._aircraft_definition_map.clear()
-
try:
parser = etree.XMLParser(remove_blank_text=True)
root = etree.parse(xml_source, parser)
- nodes = root.xpath('//options')
+ nodes = root.xpath("//options")
for node in nodes:
if "auto_mode_select" in node.attrib:
- self._auto_mode_select = safe_read(node,"auto_mode_select",bool,True)
+ self._auto_mode_select = safe_read(
+ node, "auto_mode_select", bool, True
+ )
if "auto_mode_lock" in node.attrib:
- self._auto_mode_lock = safe_read(node,"auto_mode_lock",bool,True)
+ self._auto_mode_lock = safe_read(node, "auto_mode_lock", bool, True)
if "community_folder" in node.attrib:
- self._community_folder = safe_read(node,"community_folder", str, "")
+ self._community_folder = safe_read(
+ node, "community_folder", str, ""
+ )
if "sort" in node.attrib:
try:
- sort_mode = safe_read(node,"sort",int, SimconnectSortMode.NotSet.value)
+ sort_mode = safe_read(
+ node, "sort", int, SimconnectSortMode.NotSet.value
+ )
self._sort_mode = SimconnectSortMode(sort_mode)
except:
self._sort_mode = SimconnectSortMode.NotSet
pass
if "last_command_mode" in node.attrib:
- self._last_command_mode = SimConnectCommandMode.to_enum(node.get("last_command_mode"))
+ self._last_command_mode = SimConnectCommandMode.to_enum(
+ node.get("last_command_mode")
+ )
break
# reference items scanned from MSFS
@@ -597,24 +628,24 @@ def parse_xml(self, data = None):
default_mode = profile.get_default_mode() if profile else None
if node_items is not None:
for node in node_items:
- icao_model = safe_read(node,"model", str, "")
- icao_manufacturer = safe_read(node,"manufacturer", str, "")
- icao_type = safe_read(node,"type", str, "")
- path = safe_read(node,"path", str, "")
- key = safe_read(node,"key", str, "")
+ icao_model = safe_read(node, "model", str, "")
+ icao_manufacturer = safe_read(node, "manufacturer", str, "")
+ icao_type = safe_read(node, "type", str, "")
+ path = safe_read(node, "path", str, "")
+ key = safe_read(node, "key", str, "")
if "mode" in node.attrib:
mode = node.get("mode")
else:
mode = default_mode
-
- id = safe_read(node,"id", str, "")
- entry_type_int = safe_read(node,"entry_type",int,0)
+
+ id = safe_read(node, "id", str, "")
+ entry_type_int = safe_read(node, "entry_type", int, 0)
entry_type = SimconnectAicraftDefinition.EntryType(entry_type_int)
- state_folder = safe_read(node,"state_folder",str,"")
- community_path = safe_read(node,"community_path",str,"")
- aircraft_path = safe_read(node,"aircraft_path",str,"")
+ state_folder = safe_read(node, "state_folder", str, "")
+ community_path = safe_read(node, "community_path", str, "")
+ aircraft_path = safe_read(node, "aircraft_path", str, "")
sim_name = None
if "sim_name" in node.attrib:
sim_name = node.get("sim_name")
@@ -632,41 +663,43 @@ def parse_xml(self, data = None):
for child in node_titles:
titles.append(child.text)
- item = SimconnectAicraftDefinition(id = id,
- icao_model = icao_model,
- icao_manufacturer = icao_manufacturer,
- icao_type = icao_type,
- titles = titles,
- path = path,
- mode = mode,
- community_path=community_path,
- aircraft_path=aircraft_path,
- state_folder = state_folder,
- sim_name = sim_name,
- entry_type = entry_type)
- if not key in self._aircraft_definition_map:
+ item = SimconnectAicraftDefinition(
+ id=id,
+ icao_model=icao_model,
+ icao_manufacturer=icao_manufacturer,
+ icao_type=icao_type,
+ titles=titles,
+ path=path,
+ mode=mode,
+ community_path=community_path,
+ aircraft_path=aircraft_path,
+ state_folder=state_folder,
+ sim_name=sim_name,
+ entry_type=entry_type,
+ )
+ if key not in self._aircraft_definition_map:
self._aircraft_definition_map[key] = item
-
node_user_items = root.xpath("//user_items/item")
verbose = gremlin.config.Configuration().verbose_mode_details
for node in node_user_items:
- mode = safe_read(node,"mode", str, "")
- id = safe_read(node,"id", str, "")
- sim_name = safe_read(node,"sim_name", str, "")
- item =SimconnectManualDefinition(id, sim_name, mode)
+ mode = safe_read(node, "mode", str, "")
+ id = safe_read(node, "id", str, "")
+ sim_name = safe_read(node, "sim_name", str, "")
+ item = SimconnectManualDefinition(id, sim_name, mode)
self._aircraft_manual_definitions.append(item)
-
- if verbose: syslog.info (f"SIMCONNECT: manual: read mode: {mode} for item: {sim_name}")
-
+ if verbose:
+ syslog.info(
+ f"SIMCONNECT: manual: read mode: {mode} for item: {sim_name}"
+ )
node_titles = None
nodes = root.xpath("//titles")
for node in nodes:
node_titles = node
break
-
+
if node_titles is not None:
for node in node_titles:
if node.tag == "title":
@@ -677,19 +710,18 @@ def parse_xml(self, data = None):
# sort the entries according to the current sort mode
self.sort()
-
except Exception as err:
syslog.error(f"Simconnect Config: XML read error: {xml_source}: {err}")
return False
def to_xml(self):
- ''' writes the simconnect options to the xml configuration file '''
+ """writes the simconnect options to the xml configuration file"""
root = etree.Element("simconnect_config")
node_options = etree.SubElement(root, "options")
# selection mode
- node_options.set("auto_mode_select",str(self._auto_mode_select))
+ node_options.set("auto_mode_select", str(self._auto_mode_select))
# autolock mode
node_options.set("auto_mode_lock", str(self._auto_mode_lock))
@@ -698,19 +730,22 @@ def to_xml(self):
node_options.set("community_folder", self._community_folder)
node_options.set("sort", str(self._sort_mode.value))
- node_options.set("last_command_mode", SimConnectCommandMode.to_string(self._last_command_mode))
+ node_options.set(
+ "last_command_mode",
+ SimConnectCommandMode.to_string(self._last_command_mode),
+ )
# scanned aicraft titles (local content)
if self._aircraft_definition_map:
- node_items = etree.SubElement(root,"items")
+ node_items = etree.SubElement(root, "items")
for sim_name, item in self._aircraft_definition_map.items():
- node = etree.SubElement(node_items,"item")
+ node = etree.SubElement(node_items, "item")
if item.icao_model:
node.set("model", item.icao_model)
if item.icao_manufacturer:
node.set("manufacturer", item.icao_manufacturer)
if item.icao_type:
- node.set("type",item.icao_type)
+ node.set("type", item.icao_type)
if item.path:
node.set("path", item.path)
node.set("id", item.id)
@@ -721,7 +756,6 @@ def to_xml(self):
if item.sim_name:
node.set("sim_name", item.sim_name)
node.set("key", sim_name)
-
if item.community_path:
node.set("community_path", item.community_path)
@@ -737,84 +771,100 @@ def to_xml(self):
# manual entries (usually for streamed entries) - this only has name and mode as we don't have any other info
if self._aircraft_manual_definitions:
- node_items = etree.SubElement(root,"user_items")
+ node_items = etree.SubElement(root, "user_items")
for item in self._aircraft_manual_definitions:
- node = etree.SubElement(node_items,"item")
+ node = etree.SubElement(node_items, "item")
node.set("id", item.id)
if item.sim_name:
- node.set("sim_name", item.sim_name)
+ node.set("sim_name", item.sim_name)
else:
node.set("sim_name", "")
-
+
if item.mode:
node.set("mode", item.mode)
else:
node.set("mode", "")
-
try:
# save the file
tree = etree.ElementTree(root)
- tree.write(self._xml_source, pretty_print=True,xml_declaration=True,encoding="utf-8")
+ tree.write(
+ self._xml_source,
+ pretty_print=True,
+ xml_declaration=True,
+ encoding="utf-8",
+ )
except Exception as err:
- syslog.error(f"SimconnectData: unable to create XML simvars: {self._xml_source}: {err}")
+ syslog.error(
+ f"SimconnectData: unable to create XML simvars: {self._xml_source}: {err}"
+ )
def get_community_folder(self):
- ''' looks for the community folder '''
+ """looks for the community folder"""
dir = QtWidgets.QFileDialog.getExistingDirectory(
- None,
- "Select Community Folder",
- dir = self.community_folder
+ None, "Select Community Folder", dir=self.community_folder
)
if dir and os.path.isdir(dir):
self.community_folder = dir
return dir
return None
-
+
def _getCommunityFolder(self):
- ''' gets the active community folder - this is user configured in options as there can be multiple installs and versions '''
+ """gets the active community folder - this is user configured in options as there can be multiple installs and versions"""
from gremlin.ui import ui_common
+
if not self._community_folder or not os.path.isdir(self._community_folder):
folder = self.get_community_folder()
if os.path.isdir(folder):
- folder = None
-
+ folder = None
+
self._community_folder = folder
-
- return self._community_folder
+ return self._community_folder
- def addManualEntry(self, sim_name: str, mode : str = None):
- ''' adds a manual entry '''
+ def addManualEntry(self, sim_name: str, mode: str = None):
+ """adds a manual entry"""
assert sim_name
if not mode:
mode = gremlin.shared_state.current_profile.get_default_mode()
sim_name = sim_name.casefold()
- item = SimconnectManualDefinition(sim_name = sim_name, mode = mode)
+ item = SimconnectManualDefinition(sim_name=sim_name, mode=mode)
self._aircraft_manual_definitions.append(item)
def removeEntry(self, item):
- ''' deletes an entry, scanned or manual - returns True if the entry was deleted'''
+ """deletes an entry, scanned or manual - returns True if the entry was deleted"""
if item:
- if isinstance(item, SimconnectAicraftDefinition) and item in self._aircraft_definitions:
+ if (
+ isinstance(item, SimconnectAicraftDefinition)
+ and item in self._aircraft_definitions
+ ):
self._aircraft_definitions.remove(item)
return True
- if isinstance(item, SimconnectManualDefinition) and item in self._aircraft_manual_definitions:
+ if (
+ isinstance(item, SimconnectManualDefinition)
+ and item in self._aircraft_manual_definitions
+ ):
self._aircraft_manual_definitions.remove(item)
return True
return False
def removeManualEntry(self, sim_name: str):
- ''' removes a manual entry '''
+ """removes a manual entry"""
assert sim_name
sim_name = sim_name.casefold()
- item = next((item for item in self._aircraft_manual_definitions if item.sim_name == sim_name), None)
+ item = next(
+ (
+ item
+ for item in self._aircraft_manual_definitions
+ if item.sim_name == sim_name
+ ),
+ None,
+ )
if item:
self._aircraft_manual_definitions.remove(item)
-
def scan_entry(self, folder):
- ''' scans a single aicraft folder entry '''
+ """scans a single aicraft folder entry"""
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_simconnect
@@ -828,12 +878,13 @@ def scan_entry(self, folder):
item = self._read_aicraft_config(aicraft_folder)
if item:
if verbose:
- syslog.error(f"SIMCONNECT: added aircraft definition: {item.display_name}")
+ syslog.error(
+ f"SIMCONNECT: added aircraft definition: {item.display_name}"
+ )
return item
-
-
+
def _fix_entry(self, value):
- if "\"" in value:
+ if '"' in value:
# remove double quotes
matches = re.findall('"(.*?)"', value)
if matches:
@@ -844,24 +895,25 @@ def _fix_entry(self, value):
value = matches.pop()
# value = re.sub(r'[^0-9a-zA-Z\s_-]+', '', value)
-
+
return value.strip()
def _read_aicraft_config(self, aircraft_cfg):
- ''' reads a configuration folder and extracts a configuration object '''
+ """reads a configuration folder and extracts a configuration object"""
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_simconnect
if not aircraft_cfg or not os.path.isfile(aircraft_cfg):
- syslog.error(f"SIMCONNECT: aicraft configuration file not found: {aircraft_cfg}")
+ syslog.error(
+ f"SIMCONNECT: aicraft configuration file not found: {aircraft_cfg}"
+ )
return
- cmp_icao_type = r'(?i)icao_type_designator\s*=\s*\"?(.*?)\"?$'
- cmp_icao_manuf = r'(?i)icao_manufacturer\s*=\s*\"?(.*?)\"?$'
- cmp_icao_model = r'(?i)icao_model\s*=\s*\"?(.*?)\"?$'
+ cmp_icao_type = r"(?i)icao_type_designator\s*=\s*\"?(.*?)\"?$"
+ cmp_icao_manuf = r"(?i)icao_manufacturer\s*=\s*\"?(.*?)\"?$"
+ cmp_icao_model = r"(?i)icao_model\s*=\s*\"?(.*?)\"?$"
cmp_title = r"(?i)title\s*=\s*\"?(.*?)\"?$"
-
titles = []
icao_type = None
icao_model = None
@@ -888,74 +940,78 @@ def _read_aicraft_config(self, aircraft_cfg):
matches = re.findall(cmp_title, line)
if matches:
titles.extend(matches)
-
- # extract the root folder in the community folder
- aircraft_path = os.path.dirname(aircraft_cfg)
- airplane_path = os.path.dirname(aircraft_path)
- simobject_path = os.path.dirname(airplane_path)
- community_path = os.path.dirname(simobject_path)
+ # extract the root folder in the community folder
+
+ aircraft_path = os.path.dirname(aircraft_cfg)
+ airplane_path = os.path.dirname(aircraft_path)
+ simobject_path = os.path.dirname(airplane_path)
+ community_path = os.path.dirname(simobject_path)
# rebuild the state folder returned by the sim when it has an active aicraft
- state_folder = os.path.join(community_path, simobject_path, airplane_path, aircraft_path, "aicraft.cfg")
+ state_folder = os.path.join(
+ community_path, simobject_path, airplane_path, aircraft_path, "aicraft.cfg"
+ )
aircraft_name = os.path.basename(aircraft_path)
community_name = os.path.basename(community_path)
-
sim_name = None
- work_cfg = aircraft_cfg.replace("/", os.sep).casefold()
+ work_cfg = aircraft_cfg.replace("/", os.sep).casefold()
splits = work_cfg.split(os.sep)
max_index = len(splits)
index = 0
while splits[index] != "simobjects" and index < max_index:
- index+=1
- index+=1
+ index += 1
+ index += 1
if index < max_index:
while splits[index] != "airplanes" and index < max_index:
- index+=1
- index+=1
+ index += 1
+ index += 1
if index < max_index:
sim_name = splits[index]
-
-
+
if titles:
titles = list(set(titles))
titles = [self._fix_entry(t) for t in titles]
titles.sort()
if icao_model and icao_type and icao_manuf:
path = os.path.dirname(aircraft_cfg)
- item = SimconnectAicraftDefinition(icao_type=icao_type,
- icao_manufacturer= icao_manuf,
- icao_model= icao_model,
- titles= titles,
- path = path,
- community_path = community_name,
- aircraft_path = aircraft_name,
- state_folder = state_folder,
- sim_name = sim_name
- )
-
- return item
+ item = SimconnectAicraftDefinition(
+ icao_type=icao_type,
+ icao_manufacturer=icao_manuf,
+ icao_model=icao_model,
+ titles=titles,
+ path=path,
+ community_path=community_name,
+ aircraft_path=aircraft_name,
+ state_folder=state_folder,
+ sim_name=sim_name,
+ )
- return None
+ return item
+ return None
def scan_aircraft_config(self, owner):
- ''' scans MSFS folders for the list of aircraft names '''
-
+ """scans MSFS folders for the list of aircraft names"""
- #options = SimconnectOptions()
+ # options = SimconnectOptions()
community_folder = self.community_folder
if not community_folder:
return
-
# scan for lvars
- #self._scan_lvars()
-
- progress = QtWidgets.QProgressDialog(parent = owner, labelText ="Scanning folders... (this can take a while)", cancelButtonText = "Cancel", minimum = 0, maximum= 100) #, flags = QtCore.Qt.FramelessWindowHint)
+ # self._scan_lvars()
+
+ progress = QtWidgets.QProgressDialog(
+ parent=owner,
+ labelText="Scanning folders... (this can take a while)",
+ cancelButtonText="Cancel",
+ minimum=0,
+ maximum=100,
+ ) # , flags = QtCore.Qt.FramelessWindowHint)
progress.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
progress.setValue(0)
progress.show()
@@ -971,15 +1027,13 @@ def scan_aircraft_config(self, owner):
# # add the streamd folders to the list
# search_folders.append(self._local_state_folder)
-
source_files = []
for root_folder in search_folders:
folders = gremlin.util.find_folders(root_folder)
-
- for folder in folders:
+ for folder in folders:
# only process simobjects
- ac_root = os.path.join(folder, "SimObjects","Airplanes")
+ ac_root = os.path.join(folder, "SimObjects", "Airplanes")
if not os.path.isdir(ac_root):
continue
ac_folders = gremlin.util.find_folders(ac_root)
@@ -990,15 +1044,11 @@ def scan_aircraft_config(self, owner):
# valid configuration folder because it has an aicraft.cfg and is a player playable plane because it also has a cockpit.cfg
source_files.append(ac_cfg)
-
-
-
-
file_count = len(source_files)
progress.setLabelText = f"SIMCONNECT: Processing {file_count:,} aircraft..."
verbose = gremlin.config.Configuration().verbose
-
+
is_canceled = False
items = []
keys = []
@@ -1006,32 +1056,32 @@ def scan_aircraft_config(self, owner):
if verbose:
syslog.info(f"SIMCONNECT: Processing {len(source_files):,}...")
for count, ac_file in enumerate(source_files):
-
-
progress.setValue(int(100 * count / file_count))
if progress.wasCanceled():
- is_canceled = True
+ is_canceled = True
break
item = self._read_aicraft_config(ac_file)
- if item and not item.key in keys:
+ if item and item.key not in keys:
# avoid duplicate entries
items.append(item)
keys.append(item.key)
if verbose:
- syslog.info(f"\tFound: {item.display_name} folder: {item.community_path} ac: {item.aircraft_path}")
+ syslog.info(
+ f"\tFound: {item.display_name} folder: {item.community_path} ac: {item.aircraft_path}"
+ )
if not is_canceled:
# update modes that exist already so they are preserved between scans
mapped_modes = {}
for item in self._aircraft_definitions:
mapped_modes[item.key] = (item.id, item.mode)
-
+
self._aircraft_definitions = items
# sort
self.sort()
-
+
for item in self._aircraft_definitions:
key = item.key
if key in mapped_modes.keys():
@@ -1039,31 +1089,35 @@ def scan_aircraft_config(self, owner):
self.save()
progress.close()
-
- #gremlin.util.popCursor()
-
+
+ # gremlin.util.popCursor()
+
def sort(self):
- ''' sorts definitions '''
+ """sorts definitions"""
if self._sort_mode == SimconnectSortMode.AicraftAscending:
- self._aircraft_definitions.sort(key = lambda x: x.key)
- self._aircraft_manual_definitions.sort(key = lambda x: x.key)
+ self._aircraft_definitions.sort(key=lambda x: x.key)
+ self._aircraft_manual_definitions.sort(key=lambda x: x.key)
elif self._sort_mode == SimconnectSortMode.AircraftDescending:
- self._aircraft_definitions.sort(key = lambda x: x.key, reverse = True)
- self._aircraft_manual_definitions.sort(key = lambda x: x.key)
+ self._aircraft_definitions.sort(key=lambda x: x.key, reverse=True)
+ self._aircraft_manual_definitions.sort(key=lambda x: x.key)
elif self._sort_mode == SimconnectSortMode.Mode:
- self._aircraft_definitions.sort(key = lambda x: (x.mode.casefold(), x.key))
- self._aircraft_manual_definitions.sort(key = lambda x: (x.mode.casefold(), x.key))
+ self._aircraft_definitions.sort(key=lambda x: (x.mode.casefold(), x.key))
+ self._aircraft_manual_definitions.sort(
+ key=lambda x: (x.mode.casefold(), x.key)
+ )
+
@SingletonDecorator
-class SimconnectMonitor():
- ''' simconnect monitor
+class SimconnectMonitor:
+ """simconnect monitor
Monitors current aircraft for profile mode changes
-
-
-
- '''
+
+
+
+ """
+
def __init__(self):
# syslog = logging.getLogger("system")
syslog.info("SCMonitor: listening")
@@ -1076,21 +1130,18 @@ def __init__(self):
self._verbose = gremlin.config.Configuration().verbose_mode_simconnect
self._verbose_detailed = gremlin.config.Configuration().verbose_mode_details
self._options = SimconnectOptions(self._manager)
- el= gremlin.event_handler.EventListener()
- el.profile_started.connect(self._profile_start) # trap profile start
- el.profile_stop.connect(self.stop) # trap profile stop
+ el = gremlin.event_handler.EventListener()
+ el.profile_started.connect(self._profile_start) # trap profile start
+ el.profile_stop.connect(self.stop) # trap profile stop
el.abort.connect(self.stop)
- el.shutdown.connect(self._shutdown) # trap application shutdown
- #el.runtime_mode_changed.connect(self._mode_changed) # trap runtime mode changes - these occur post validation
+ el.shutdown.connect(self._shutdown) # trap application shutdown
+ # el.runtime_mode_changed.connect(self._mode_changed) # trap runtime mode changes - these occur post validation
- self._auto_reconnect_event = threading.Event() # controls reconnect thread exit
- self._enabled = False # default, not enabled - set by profile start event
-
-
+ self._auto_reconnect_event = threading.Event() # controls reconnect thread exit
+ self._enabled = False # default, not enabled - set by profile start event
-
- def getStartupMode(self, name : str = None):
- ''' gets the startup mode for the current aicraft '''
+ def getStartupMode(self, name: str = None):
+ """gets the startup mode for the current aicraft"""
if self._manager.connected:
# sim is running
@@ -1105,72 +1156,74 @@ def getStartupMode(self, name : str = None):
mode = None
if name:
- #item = self._options.find_definition_by_state(state_folder)
+ # item = self._options.find_definition_by_state(state_folder)
item = self._options.find_definition_by_sim_name(name)
if item is not None:
# found the aicraft entry
key = item.key
-
+
mode = profile.getSimconnectMode(key)
if not mode:
mode = item.mode
if not mode:
mode = profile.get_start_mode()
-
- if self._verbose: syslog.info(f"SCMONITOR: Aircraft changed to: [{name}] - activating profile mode [{mode}]")
+
+ if self._verbose:
+ syslog.info(
+ f"SCMONITOR: Aircraft changed to: [{name}] - activating profile mode [{mode}]"
+ )
self._startup_mode[name] = mode
return mode
-
+
return None
-
+
@QtCore.Slot()
def _profile_start(self):
- ''' occurs when a profile starts '''
+ """occurs when a profile starts"""
enabled = gremlin.shared_state.getSimConnectEnabled()
- self._startup_mode = {} # reset the mode cache
+ self._startup_mode = {} # reset the mode cache
self._enabled = enabled
if enabled:
- syslog.info(f"SCMONITOR: Start")
+ syslog.info("SCMONITOR: Start")
# change to the correct mode
self._manager.request_loaded_aircraft()
# eh = gremlin.event_handler.EventHandler()
# eh.registerModeValidator(self._mode_change_validator) # filter mode change requests and discard them if needed to avoid interrupting Simconnect activities
-
+
self.start()
else:
- self.stop() # stop monitoring if it was
- syslog.info(f"SCMONITOR: no simconnect mappings found - start skipped")
+ self.stop() # stop monitoring if it was
+ syslog.info("SCMONITOR: no simconnect mappings found - start skipped")
-
def start(self):
- ''' starts monitoring for aicraft changes '''
+ """starts monitoring for aicraft changes"""
if self._started:
return
-
+
# trap abort
eh = gremlin.event_handler.EventListener()
eh.abort.connect(self.stop)
-
+
# start the reconnect thread
- self._auto_reconnect_thread = threading.Thread(target = self._auto_reconnect_loop, daemon=True)
+ self._auto_reconnect_thread = threading.Thread(
+ target=self._auto_reconnect_loop, daemon=True
+ )
self._auto_reconnect_thread.setName("SCMONITOR: auto-reconnect")
self._auto_reconnect_event.clear()
self._auto_reconnect_thread.start()
self._started = True
-
self._manager.sim_connect()
if self._options.auto_mode_select:
if self._manager.connected:
self._get_aircraft()
-
-
+
def stop(self):
- ''' stop monitoring aircraft changes '''
+ """stop monitoring aircraft changes"""
if not self._started:
- return
+ return
self._auto_reconnect_event.set()
self._auto_reconnect_thread.join()
@@ -1179,34 +1232,29 @@ def stop(self):
self._manager.sim_aircraft_loaded.disconnect(self._sim_aircraft_loaded)
self._started = False
-
-
def _auto_reconnect_loop(self):
- # in case the sim got restarted or we lost connection
+ # in case the sim got restarted or we lost connection
while not self._auto_reconnect_event.is_set():
if not self._manager.running:
self._manager.ensure_running()
time.sleep(1)
-
def _get_aircraft_list(self):
- ''' requests the current list of aircraft known to the sim'''
+ """requests the current list of aircraft known to the sim"""
self._manager.request_aircraft_list()
def _get_aircraft(self):
- ''' updates the current player aircraft in the sim'''
+ """updates the current player aircraft in the sim"""
self._manager.request_loaded_aircraft()
-
-
def _shutdown(self):
- ''' program exit - cleanup monitoring '''
+ """program exit - cleanup monitoring"""
# syslog = logging.getLogger("system")
syslog.info("SCMONITOR: shutdown")
- # remove the validator
+ # remove the validator
eh = gremlin.event_handler.EventHandler()
- eh.unregisterModeValidator(self._mode_change_validator)
+ eh.unregisterModeValidator(self._mode_change_validator)
# stop
self.stop()
@@ -1218,76 +1266,74 @@ def _shutdown(self):
self._manager = None
@QtCore.Slot(str, str, str)
- def _sim_aircraft_loaded(self, folder : str, name : str, title : str):
- ''' called when a new aicraft has been detected '''
+ def _sim_aircraft_loaded(self, folder: str, name: str, title: str):
+ """called when a new aicraft has been detected"""
# syslog = logging.getLogger("system")
- if self._verbose: syslog.info(f"SCMONITOR: Aircraft loaded: [{title}]")
+ if self._verbose:
+ syslog.info(f"SCMONITOR: Aircraft loaded: [{title}]")
self.changeModeForAicraft(title)
-
- def changeModeForAicraft(self, title : str):
- ''' changes the mode for the current aircraft '''
- mode = self.getStartupMode(title) # get the mode to use for this profile
+ def changeModeForAicraft(self, title: str):
+ """changes the mode for the current aircraft"""
+ mode = self.getStartupMode(title) # get the mode to use for this profile
if mode and gremlin.shared_state.runtime_mode != mode:
# suitable mode found - if this is the current mode - change_mode will do nothing
self.change_mode(mode)
return mode
-
@QtCore.Slot()
def _sim_start(self):
- ''' sim started event '''
+ """sim started event"""
# syslog = logging.getLogger("system")
- if self._verbose: syslog.info(f"SCMONITOR: sim start")
-
+ if self._verbose:
+ syslog.info("SCMONITOR: sim start")
@QtCore.Slot()
def _sim_stop(self):
- ''' sim stop event '''
+ """sim stop event"""
# syslog = logging.getLogger("system")
- if self._verbose: syslog.info(f"SCMONITOR: sim stop")
+ if self._verbose:
+ syslog.info("SCMONITOR: sim stop")
eh = gremlin.event_handler.EventListener()
eh.request_profile_stop.emit("Sim Stop")
def _mode_change_validator(self, new_mode) -> bool:
- ''' hook called when a request for a mode change is made.
- this checks to see if the mode is locked by option '''
+ """hook called when a request for a mode change is made.
+ this checks to see if the mode is locked by option"""
if not gremlin.shared_state.is_running:
# allow mode change while at edit/design time
return True
-
+
# syslog = logging.getLogger("system")
- if self._verbose: syslog.info(f"SCMONITOR: Profile mode change request to: {new_mode}")
+ if self._verbose:
+ syslog.info(f"SCMONITOR: Profile mode change request to: {new_mode}")
mode = self.getStartupMode()
if mode and mode != new_mode and self._options.auto_mode_lock:
# not allowed
- if self._verbose: syslog.warning(f"SCMONITOR: per option request denied - aicraft mode lock is enabled and locked to mode [{mode}]")
+ if self._verbose:
+ syslog.warning(
+ f"SCMONITOR: per option request denied - aicraft mode lock is enabled and locked to mode [{mode}]"
+ )
return False
-
+
# allowed
return True
def change_mode(self, mode):
- ''' force a mode change
+ """force a mode change
This only changes the mode if we're not already in the mode and the mode exists.
- '''
+ """
eh = gremlin.event_handler.EventHandler()
- eh.change_mode(mode, validate = False)
-
-
-
-
-
-
-
+ eh.change_mode(mode, validate=False)
class SimconnectOptionsUi(gremlin.ui.ui_common.QRememberDialog):
- """UI to set individual simconnect settings """
+ """UI to set individual simconnect settings"""
- def __init__(self, simconnect : SimConnect, parent=None):
+ def __init__(self, simconnect: SimConnect, parent=None):
from gremlin.ui import ui_common
- super().__init__(self.__class__.__name__, parent = parent)
+
+ super().__init__(self.__class__.__name__, parent=parent)
# make modal
self.setWindowModality(QtCore.Qt.ApplicationModal)
@@ -1296,53 +1342,51 @@ def __init__(self, simconnect : SimConnect, parent=None):
self._manager.sim_aircraft_loaded.connect(self._aircraft_loaded)
self._manager.sim_state.connect(self._sim_state)
self._verbose = gremlin.config.Configuration().verbose_mode_simconnect
-
+
self.options = SimconnectOptions(simconnect)
- self._data = None # sorted list of aircraft definitions
+ self._data = None # sorted list of aircraft definitions
self._handler = SimConnectEventHandler()
self._handler.AircraftDefinitionsChanged.connect(self._aircraft_list_loaded)
- self._current_page = 0 # page number displayed
- self._page_size = 25 # how many entries to display at a time
- self._page_count = 0 # number of pages available
+ self._current_page = 0 # page number displayed
+ self._page_size = 25 # how many entries to display at a time
+ self._page_count = 0 # number of pages available
self._start_index = 0
self._end_index = 0
self._page_number = 1
-
self._mode_selector_map = {}
self._selected_cb_map = {}
self._manual_mode_selector_map = {}
self._manual_selected_cb_map = {}
-
-
self._content_widget = gremlin.ui.ui_common.QContentWidget()
self._content_widget.resized.connect(self._content_resized)
- self._content_widget.setContentsMargins(0,0,0,0)
+ self._content_widget.setContentsMargins(0, 0, 0, 0)
- self._splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Vertical, self._content_widget)
+ self._splitter = QtWidgets.QSplitter(
+ QtCore.Qt.Orientation.Vertical, self._content_widget
+ )
self._top_panel_widget = QtWidgets.QWidget()
- self._top_panel_widget.setContentsMargins(0,0,0,0)
+ self._top_panel_widget.setContentsMargins(0, 0, 0, 0)
self._top_panel_widget.setMinimumWidth(200)
self._bottom_panel_widget = QtWidgets.QWidget()
- self._bottom_panel_widget.setContentsMargins(0,0,0,0)
+ self._bottom_panel_widget.setContentsMargins(0, 0, 0, 0)
self._top_panel_layout = QtWidgets.QVBoxLayout(self._top_panel_widget)
- self._top_panel_layout.setContentsMargins(0,0,0,0)
+ self._top_panel_layout.setContentsMargins(0, 0, 0, 0)
self._bottom_panel_layout = QtWidgets.QVBoxLayout(self._bottom_panel_widget)
- self._bottom_panel_layout.setContentsMargins(0,0,0,0)
+ self._bottom_panel_layout.setContentsMargins(0, 0, 0, 0)
self._splitter.addWidget(self._top_panel_widget)
self._splitter.addWidget(self._bottom_panel_widget)
- self._splitter.setStretchFactor(0,1)
- self._splitter.setStretchFactor(1,1)
+ self._splitter.setStretchFactor(0, 1)
+ self._splitter.setStretchFactor(1, 1)
-
self._splitter.setCollapsible(0, False)
self._splitter.setCollapsible(1, False)
@@ -1350,55 +1394,69 @@ def __init__(self, simconnect : SimConnect, parent=None):
self.config = gremlin.config.Configuration()
self.setMinimumWidth(600)
-
self.mode_list = []
- self.profile : gremlin.base_profile.Profile = gremlin.shared_state.current_profile
+ self.profile: gremlin.base_profile.Profile = (
+ gremlin.shared_state.current_profile
+ )
self.mode_list = self.profile.get_modes()
-
# display name to mode pair list
self.mode_pair_list = gremlin.ui.ui_common.get_mode_list(self.profile)
- is_dark = gremlin.shared_state.is_dark_theme
- prefix = "dark_" if is_dark else ""
+ is_dark = gremlin.shared_state.is_dark_theme
self.setWindowTitle("Simconnect Options")
- self.installEventFilter(self) # trap some events
+ self.installEventFilter(self) # trap some events
self.main_layout = QtWidgets.QVBoxLayout(self)
- self._auto_mode_switch = QtWidgets.QCheckBox("Change profile mode based on active aicraft")
- self._auto_mode_switch.setToolTip("When enabled, the profile mode will automatically change based on the mode associated with the active player aircraft in Flight Simulator")
+ self._auto_mode_switch = QtWidgets.QCheckBox(
+ "Change profile mode based on active aicraft"
+ )
+ self._auto_mode_switch.setToolTip(
+ "When enabled, the profile mode will automatically change based on the mode associated with the active player aircraft in Flight Simulator"
+ )
self._auto_mode_switch.setChecked(self.options.auto_mode_select)
self._auto_mode_switch.clicked.connect(self._auto_mode_select_cb)
- self._auto_mode_lock = QtWidgets.QCheckBox("Lock the mode to the active aicraft")
- self._auto_mode_lock.setToolTip("When enabled, the profile mode mapped to the aircraft will stay locked in that mode and other mode changes will be ignored.\nThis prevents inadvertent loss of control due to other GremlinEx actions.")
+ self._auto_mode_lock = QtWidgets.QCheckBox(
+ "Lock the mode to the active aicraft"
+ )
+ self._auto_mode_lock.setToolTip(
+ "When enabled, the profile mode mapped to the aircraft will stay locked in that mode and other mode changes will be ignored.\nThis prevents inadvertent loss of control due to other GremlinEx actions."
+ )
self._auto_mode_lock.setChecked(self.options.auto_mode_select)
self._auto_mode_lock.clicked.connect(self._auto_mode_lock_cb)
-
- self._msfs_path_widget = ui_common.QPathLineItem(header="MSFS Community Folder", text = self.options.community_folder, dir_mode=True)
+ self._msfs_path_widget = ui_common.QPathLineItem(
+ header="MSFS Community Folder",
+ text=self.options.community_folder,
+ dir_mode=True,
+ )
self._msfs_path_widget.pathChanged.connect(self._community_folder_changed_cb)
self._msfs_path_widget.open.connect(self._community_folder_open_cb)
- self._mode_from_aircraft_button_widget = QtWidgets.QPushButton("Mode from Aicraft")
- self._mode_from_aircraft_button_widget.clicked.connect(self._mode_from_aircraft_button_cb)
+ self._mode_from_aircraft_button_widget = QtWidgets.QPushButton(
+ "Mode from Aicraft"
+ )
+ self._mode_from_aircraft_button_widget.clicked.connect(
+ self._mode_from_aircraft_button_cb
+ )
# toolbar for map
self.container_bar_widget = QtWidgets.QWidget()
self.container_bar_layout = QtWidgets.QHBoxLayout(self.container_bar_widget)
- self.container_bar_layout.setContentsMargins(0,0,0,0)
-
+ self.container_bar_layout.setContentsMargins(0, 0, 0, 0)
self.edit_mode_widget = QtWidgets.QPushButton()
-
- manage_modes_icon = "gfx/dark_manage_modes.svg" if is_dark else "gfx/manage_modes.svg"
+
+ manage_modes_icon = (
+ "gfx/dark_manage_modes.svg" if is_dark else "gfx/manage_modes.svg"
+ )
self.edit_mode_widget.setIcon(ui_common.load_icon(manage_modes_icon))
self.edit_mode_widget.clicked.connect(self._manage_modes_cb)
self.edit_mode_widget.setToolTip("Manage Modes")
-
# self.scan_aircraft_widget = QtWidgets.QPushButton("Scan Aircraft")
# self.scan_aircraft_widget.setIcon(gremlin.util.load_icon("mdi.magnify-scan"))
# self.scan_aircraft_widget.clicked.connect(self._scan_aircraft_cb)
@@ -1410,49 +1468,61 @@ def __init__(self, simconnect : SimConnect, parent=None):
self.current_aircraft_widget.setReadOnly(True)
self.current_aircraft_widget.setMinimumWidth(line_entry_width)
+ self.current_aircraft_folder = (
+ None # holds the active aircraft data folder (from the sim)
+ )
+ self.current_aircraft_title = (
+ None # holds the active aicraft title (from the sim)
+ )
+ self.current_aircraft_name = (
+ None # holds the active aicraft name (from the sim)
+ )
- self.current_aircraft_folder = None # holds the active aircraft data folder (from the sim)
- self.current_aircraft_title = None # holds the active aicraft title (from the sim)
- self.current_aircraft_name = None # holds the active aicraft name (from the sim)
-
- self.refresh_current_aircraft_widget = QtWidgets.QPushButton("Get Current Aircraft")
+ self.refresh_current_aircraft_widget = QtWidgets.QPushButton(
+ "Get Current Aircraft"
+ )
self.refresh_current_aircraft_widget.clicked.connect(self._refresh_aircraft_cb)
- #self.refresh_current_aircraft_widget.setIcon(gremlin.util.load_icon("ei.refresh"))
- self.refresh_current_aircraft_widget.setToolTip("Queries the current aircraft loaded in the sim")
- #self.refresh_current_aircraft_widget.setMaximumWidth(24)
-
+ # self.refresh_current_aircraft_widget.setIcon(gremlin.util.load_icon("ei.refresh"))
+ self.refresh_current_aircraft_widget.setToolTip(
+ "Queries the current aircraft loaded in the sim"
+ )
+ # self.refresh_current_aircraft_widget.setMaximumWidth(24)
self.add_current_aircraft_widget = QtWidgets.QPushButton("Add Current Aircraft")
self.add_current_aircraft_widget.clicked.connect(self._add_current_aircraft_cb)
- self.add_current_aircraft_widget.setToolTip("Adds the aircraft to the manual list if it doesn't exist")
+ self.add_current_aircraft_widget.setToolTip(
+ "Adds the aircraft to the manual list if it doesn't exist"
+ )
# self.add_manual_entry_widget = QtWidgets.QPushButton("Add Manual Entry")
# self.add_manual_entry_widget.setToolTip("Adds a manual entry")
# self.add_manual_entry_widget.clicked.connect(self.add_entry_cb)
- self.paginator_widget = gremlin.ui.ui_common.QPaginator(page_size = self._page_size)
+ self.paginator_widget = gremlin.ui.ui_common.QPaginator(
+ page_size=self._page_size
+ )
self.paginator_widget.pageChanged.connect(self._handle_paginator)
-
- row_widgets = [QtWidgets.QLabel("Current Aircraft:"),
- self.current_aircraft_widget,
- self.add_current_aircraft_widget,
- #self.add_manual_entry_widget,
- #QtWidgets.QLabel(" "),
- self.refresh_current_aircraft_widget,
- self.edit_mode_widget
+ row_widgets = [
+ QtWidgets.QLabel("Current Aircraft:"),
+ self.current_aircraft_widget,
+ self.add_current_aircraft_widget,
+ # self.add_manual_entry_widget,
+ # QtWidgets.QLabel(" "),
+ self.refresh_current_aircraft_widget,
+ self.edit_mode_widget,
]
widget, layout = gremlin.ui.ui_common.getGridContainer(row_widgets)
self.container_bar_widget = widget
self.container_bar_layout = layout
-
-
self.filter_widget = QtWidgets.QLineEdit()
- self.filter_widget.returnPressed.connect(self._handle_search) # on enter, do the search
+ self.filter_widget.returnPressed.connect(
+ self._handle_search
+ ) # on enter, do the search
self.filter_widget.setMinimumWidth(line_entry_width)
-
+
self.apply_filter_widget = QtWidgets.QPushButton("Search")
self.apply_filter_widget.clicked.connect(self._handle_search)
self.apply_filter_widget.setToolTip("Search aicraft using the current filter")
@@ -1461,49 +1531,53 @@ def __init__(self, simconnect : SimConnect, parent=None):
self.clear_filter_widget.clicked.connect(self._handle_clear_search)
self.clear_filter_widget.setToolTip("Clears the search filter")
- self.filter_current_widget = QtWidgets.QPushButton("Search Current")
+ self.filter_current_widget = QtWidgets.QPushButton("Search Current")
self.filter_current_widget.clicked.connect(self._handle_current_search)
self.filter_current_widget.setToolTip("Search for current aircraft")
-
self.refresh_aircraft_list_widget = QtWidgets.QPushButton("Refresh All")
- self.refresh_aircraft_list_widget.setToolTip("Refresh the available aircraft list from MSFS")
+ self.refresh_aircraft_list_widget.setToolTip(
+ "Refresh the available aircraft list from MSFS"
+ )
self.refresh_aircraft_list_widget.setIcon(gremlin.util.load_icon("ei.refresh"))
- self.refresh_aircraft_list_widget.clicked.connect(self._handle_refresh_aircraft_list)
-
+ self.refresh_aircraft_list_widget.clicked.connect(
+ self._handle_refresh_aircraft_list
+ )
+
+ row_widgets = [
+ QtWidgets.QLabel("Filter:"),
+ self.filter_widget,
+ self.apply_filter_widget,
+ self.clear_filter_widget,
+ self.filter_current_widget,
+ self.refresh_aircraft_list_widget,
+ ]
- row_widgets = [QtWidgets.QLabel("Filter:"),
- self.filter_widget,
- self.apply_filter_widget,
- self.clear_filter_widget,
- self.filter_current_widget,
- self.refresh_aircraft_list_widget,
- ]
-
widget, layout = gremlin.ui.ui_common.getGridContainer(row_widgets)
self.container_navigation_widget = widget
self.container_navigation_layout = layout
- self.grid_sync_widgets = [self.container_navigation_widget, self.container_bar_widget]
+ self.grid_sync_widgets = [
+ self.container_navigation_widget,
+ self.container_bar_widget,
+ ]
page_sizes = [10, 25, 50, 100]
widgets = [self.paginator_widget]
for size in page_sizes:
- widget = ui_common.QDataPushButton(str(size),size)
+ widget = ui_common.QDataPushButton(str(size), size)
widget.clicked.connect(self._handle_page_size)
widgets.append(widget)
widget, layout = gremlin.ui.ui_common.getHContainer(widgets, left_stretch=True)
self.container_paginator_widget = widget
self.container_paginator_layout = layout
-
-
# start scrolling container widget definition
self.container_map_widget = QtWidgets.QWidget()
self.container_map_layout = QtWidgets.QVBoxLayout(self.container_map_widget)
- self.container_map_layout.setContentsMargins(0,0,0,0)
+ self.container_map_layout.setContentsMargins(0, 0, 0, 0)
# self.manual_container_map_widget = QtWidgets.QWidget()
# self.manual_container_map_layout = QtWidgets.QVBoxLayout(self.manual_container_map_widget)
@@ -1514,7 +1588,6 @@ def __init__(self, simconnect : SimConnect, parent=None):
self.scroll_widget = QtWidgets.QWidget()
self.scroll_layout = QtWidgets.QVBoxLayout()
-
# add manual aircraft map items
# self.manual_scroll_area = QtWidgets.QScrollArea()
# self.manual_scroll_widget = QtWidgets.QWidget()
@@ -1523,44 +1596,38 @@ def __init__(self, simconnect : SimConnect, parent=None):
# Configure the widget holding the layout with all the buttons
self.scroll_widget.setLayout(self.scroll_layout)
self.scroll_widget.setSizePolicy(
- QtWidgets.QSizePolicy.Expanding,
- QtWidgets.QSizePolicy.Expanding
+ QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
)
self.scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
self.scroll_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
# Configure the scroll area
- #self.scroll_area.setMinimumWidth(300)
+ # self.scroll_area.setMinimumWidth(300)
self.scroll_area.setWidgetResizable(True)
self.scroll_area.setWidget(self.scroll_widget)
self.map_widget = QtWidgets.QWidget()
self.map_layout = QtWidgets.QGridLayout(self.map_widget)
- self.map_layout.setContentsMargins(0,0,0,0)
+ self.map_layout.setContentsMargins(0, 0, 0, 0)
self.manual_map_widget = QtWidgets.QWidget()
self.manual_map_layout = QtWidgets.QGridLayout(self.manual_map_widget)
- self.manual_map_layout.setContentsMargins(0,0,0,0)
-
-
+ self.manual_map_layout.setContentsMargins(0, 0, 0, 0)
self.scroll_layout.addWidget(self.map_widget)
- self.scroll_layout.setContentsMargins(6,0,6,0)
+ self.scroll_layout.setContentsMargins(6, 0, 6, 0)
self.scroll_layout.addStretch()
-
self.container_map_layout.addWidget(self.scroll_area)
# end scrolling container widget definition
-
self.close_button_widget = QtWidgets.QPushButton("Close")
self.close_button_widget.clicked.connect(self.close_button_cb)
-
+
# disables as MSFS 2024 doesn't have flight save/flight load in a working state currently
# self.save_flight_widget = QtWidgets.QPushButton("Save Flight")
# self.save_flight_widget.clicked.connect(self._handle_save_flight)
-
# self.load_flight_widget = QtWidgets.QPushButton("Load Flight")
# self.load_flight_widget.clicked.connect(self._handle_load_flight)
@@ -1579,34 +1646,34 @@ def __init__(self, simconnect : SimConnect, parent=None):
top_bar_container_layout.addWidget(self._auto_mode_switch)
top_bar_container_layout.addWidget(self._auto_mode_lock)
top_bar_container_layout.addStretch()
-
+
self.main_layout.addWidget(top_bar_container_widget)
self.main_layout.addWidget(self._msfs_path_widget)
self.main_layout.addWidget(self.container_bar_widget)
self.main_layout.addWidget(self.container_navigation_widget)
self._top_panel_layout.addWidget(self.container_map_widget)
self._top_panel_layout.addWidget(self.container_paginator_widget)
- #self._bottom_panel_layout.addWidget(self.manual_container_map_widget)
-
+ # self._bottom_panel_layout.addWidget(self.manual_container_map_widget)
warning_container = QtWidgets.QWidget()
warning_layout = QtWidgets.QHBoxLayout(warning_container)
warning_color = gremlin.ui.ui_common.Color.warningColor()
- self.warning_widget = gremlin.ui.ui_common.QIconLabel("ph.shield-warning-fill",use_qta=True,icon_color=QtGui.QColor(warning_color),text="Error goes here", use_wrap=False)
+ self.warning_widget = gremlin.ui.ui_common.QIconLabel(
+ "ph.shield-warning-fill",
+ use_qta=True,
+ icon_color=QtGui.QColor(warning_color),
+ text="Error goes here",
+ use_wrap=False,
+ )
self.warning_widget.setVisible(False)
warning_layout.addWidget(self.warning_widget)
warning_layout.addStretch()
-
-
- self.main_layout.addWidget(self._content_widget, stretch = 3)
+
+ self.main_layout.addWidget(self._content_widget, stretch=3)
self.main_layout.addWidget(warning_container)
self.main_layout.addWidget(button_bar_widget)
-
-
-
-
# figure out the size of the header part of the control so things line up
lbl = QtWidgets.QLabel("w")
@@ -1614,25 +1681,22 @@ def __init__(self, simconnect : SimConnect, parent=None):
headers = ["Aicraft:"]
width = 0
for header in headers:
- width = max(width, char_width*(len(header)))
+ width = max(width, char_width * (len(header)))
self._width = width
self._char_width = char_width
-
- self._update_current_aircraft() # refresh sim data
- self._update_data() # update display
-
+ self._update_current_aircraft() # refresh sim data
+ self._update_data() # update display
@QtCore.Slot()
def _profile_edit_mode_changed(self):
- ''' called when profile modes have been edited or changed '''
+ """called when profile modes have been edited or changed"""
self.mode_pair_list = gremlin.ui.ui_common.get_mode_list(self.profile)
self._populate_ui()
-
- def _set_warning(self, message = None):
- ''' displays a warning in the UI, set to None to clear'''
+ def _set_warning(self, message=None):
+ """displays a warning in the UI, set to None to clear"""
if message:
self.warning_widget.setText(message)
self.warning_widget.setVisible(True)
@@ -1640,25 +1704,26 @@ def _set_warning(self, message = None):
self.warning_widget.setVisible(False)
@QtCore.Slot(QtCore.QSize)
- def _content_resized(self, size : QtCore.QSize):
- ''' called when the container object is resized '''
+ def _content_resized(self, size: QtCore.QSize):
+ """called when the container object is resized"""
# resize the splitter to the container's size as it doesn't happen by itself for some reason
width = self._content_widget.frameGeometry().width()
height = self._content_widget.frameGeometry().height()
if width > 0:
self._splitter.setFixedWidth(width)
- self._splitter.setFixedHeight(height)
+ self._splitter.setFixedHeight(height)
@QtCore.Slot()
def _manage_modes_cb(self):
import gremlin.shared_state
+
gremlin.shared_state.ui.manage_modes()
self._populate_ui()
@QtCore.Slot(object)
def _community_folder_open_cb(self, widget):
- ''' opens the profile list '''
+ """opens the profile list"""
dir = self.options.get_community_folder()
if dir:
with QtCore.QSignalBlocker(widget):
@@ -1668,10 +1733,9 @@ def _community_folder_open_cb(self, widget):
def _community_folder_changed_cb(self, widget, text):
if os.path.isdir(text):
self.options.community_folder = text
-
def closeEvent(self, event):
- ''' occurs on window close '''
+ """occurs on window close"""
self.options.save()
profile = gremlin.shared_state.current_profile
if profile:
@@ -1680,75 +1744,75 @@ def closeEvent(self, event):
@QtCore.Slot(bool)
def _auto_mode_select_cb(self, checked):
- ''' auto mode changed'''
+ """auto mode changed"""
self.options.auto_mode_select = checked
@QtCore.Slot(bool)
def _auto_mode_lock_cb(self, checked):
- ''' auto mode lock changed'''
+ """auto mode lock changed"""
self.options.auto_mode_lock = checked
-
@QtCore.Slot()
def _scan_aircraft_cb(self):
if not self._manager.connected:
self._manager.activate()
if self._manager.connected:
- self._manager.request_aircraft_list() # get aircraft list
+ self._manager.request_aircraft_list() # get aircraft list
# stop scanning community folder
- #self.options.scan_aircraft_config(self)
+ # self.options.scan_aircraft_config(self)
# update the aicraft drop down choices
- #self._populate_ui()
+ # self._populate_ui()
def _update_current_aircraft(self):
- ''' request an update from simconnect on the current aircraft '''
+ """request an update from simconnect on the current aircraft"""
if not self._manager.connected:
self._manager.activate()
if self._manager.connected:
- self._manager.request_loaded_aircraft() # will trigger the aircraft loaded callback
+ self._manager.request_loaded_aircraft() # will trigger the aircraft loaded callback
def _update_aircraft_list(self):
if not self._manager.connected:
self._manager.activate()
if self._manager.connected:
- self._manager.request_aircraft_list() # get aircraft list
-
+ self._manager.request_aircraft_list() # get aircraft list
@QtCore.Slot()
def _refresh_aircraft_cb(self):
- ''' refreshes the current aircraft '''
+ """refreshes the current aircraft"""
self._update_current_aircraft()
-
@QtCore.Slot()
def _add_current_aircraft_cb(self):
- ''' adds the current simconnect aircraft to the mode list '''
+ """adds the current simconnect aircraft to the mode list"""
name = self.current_aircraft_widget.text()
folder = self.current_aircraft_folder
if folder and os.path.isdir(folder):
# local entry
- if not self.options.find_definition_by_sim_name(name, is_manual = False):
+ if not self.options.find_definition_by_sim_name(name, is_manual=False):
self.options.scan_entry(folder)
self._populate_ui()
else:
- gremlin.ui.ui_common.MessageBox(title = "Duplicate Entry", prompt = f"Entry {name} already exists")
+ gremlin.ui.ui_common.MessageBox(
+ title="Duplicate Entry", prompt=f"Entry {name} already exists"
+ )
else:
# manual entry
- item = self.options.find_definition_by_sim_name(name, is_scan = False)
+ item = self.options.find_definition_by_sim_name(name, is_scan=False)
if not item:
# only add it if not there
self.options.addManualEntry(name)
self._populate_ui()
else:
- gremlin.ui.ui_common.MessageBox(title = "Duplicate Entry", prompt = f"Entry {name} already exists")
-
+ gremlin.ui.ui_common.MessageBox(
+ title="Duplicate Entry", prompt=f"Entry {name} already exists"
+ )
@QtCore.Slot()
def _remove_current_aircraft_cb(self):
- ''' remove button '''
+ """remove button"""
widget = self.sender()
item, _ = widget.data
@@ -1760,16 +1824,18 @@ def _remove_current_aircraft_cb(self):
self._populate_ui()
def _validate_entries(self):
- ''' ensures the manual entries are unique '''
+ """ensures the manual entries are unique"""
valid = self.options.validateEntries()
if not valid:
- self._set_warning("Warning: duplicate manual aicraft entries detected. The first entry will be used.")
+ self._set_warning(
+ "Warning: duplicate manual aicraft entries detected. The first entry will be used."
+ )
else:
self._set_warning()
- @QtCore.Slot(str,str,str)
+ @QtCore.Slot(str, str, str)
def _aircraft_loaded(self, folder, name, title):
- ''' triggered when simconnect sends aircraft data '''
+ """triggered when simconnect sends aircraft data"""
self.current_aircraft_widget.setText(title)
self.current_aircraft_widget.setToolTip(title)
self.current_aircraft_folder = folder
@@ -1780,8 +1846,8 @@ def _aircraft_loaded(self, folder, name, title):
@QtCore.Slot()
def _aircraft_list_loaded(self):
- ''' triggered when simconnect sends aircraft data '''
- self._update_data()
+ """triggered when simconnect sends aircraft data"""
+ self._update_data()
@QtCore.Slot()
def _handle_page_size(self):
@@ -1789,9 +1855,8 @@ def _handle_page_size(self):
count = widget.data
self.paginator_widget.setPageSize(count)
-
def _update_data(self):
- ''' re-index the data '''
+ """re-index the data"""
definitions = self.options.definitions
item_count = len(definitions)
@@ -1801,15 +1866,13 @@ def _update_data(self):
data = [item for item in definitions.values()]
# sort the data
- data.sort(key = lambda x: x.sim_name)
+ data.sort(key=lambda x: x.sim_name)
self._data = data
self._populate_ui()
-
-
@QtCore.Slot(int, float, str)
def _sim_state(self, int_data, float_data_, str_data):
- ''' triggered on state requests '''
+ """triggered on state requests"""
# the data will be returned as a partial subfolder so we need to match it to the actual aircraft
item = self.options.find_definition_by_state(str_data)
@@ -1821,52 +1884,51 @@ def add_entry_cb(self):
item = SimconnectManualDefinition()
self.options._aircraft_manual_definitions.append(item)
self._update_manual_list()
-
-
@QtCore.Slot()
def close_button_cb(self):
- ''' called when close button clicked '''
+ """called when close button clicked"""
self.close()
@QtCore.Slot()
def _handle_save_flight(self):
pass
- #self._manager.save_flight("c:\\gremlinex_test_flight.flt","test save","test description")
+ # self._manager.save_flight("c:\\gremlinex_test_flight.flt","test save","test description")
@QtCore.Slot()
def _handle_load_flight(self):
pass
- #self._manager.load_flight("c:\\gremlinex_test_flight.flt")
-
+ # self._manager.load_flight("c:\\gremlinex_test_flight.flt")
@QtCore.Slot()
def _handle_clear_search(self):
- ''' clears the filter '''
+ """clears the filter"""
self.filter_widget.setText("")
self._update_data()
@QtCore.Slot()
def _handle_refresh_aircraft_list(self):
- ''' refresh the list of aircraft '''
+ """refresh the list of aircraft"""
self._update_aircraft_list()
@QtCore.Slot()
def _handle_current_search(self):
- ''' search on current aircraft entry '''
+ """search on current aircraft entry"""
filter = self.current_aircraft_widget.text()
self.filter_widget.setText(filter)
self._search(filter)
- def _search(self, filter : str):
- ''' handles the search '''
+ def _search(self, filter: str):
+ """handles the search"""
if not filter:
self._update_data()
else:
- pattern = re.compile(filter,re.IGNORECASE)
+ pattern = re.compile(filter, re.IGNORECASE)
definitions = self.options.definitions
- data = [item for item in definitions.values() if pattern.search(item.sim_name)]
- data.sort(key = lambda x: x.sim_name)
+ data = [
+ item for item in definitions.values() if pattern.search(item.sim_name)
+ ]
+ data.sort(key=lambda x: x.sim_name)
item_count = len(data)
if self.paginator_widget.itemCount != item_count:
self.paginator_widget.setItemCount(item_count, False)
@@ -1874,18 +1936,13 @@ def _search(self, filter : str):
self._data = data
self._populate_ui()
-
@QtCore.Slot()
def _handle_search(self):
- ''' handles a search '''
+ """handles a search"""
filter = self.filter_widget.text()
self._search(filter)
-
-
-
-
- @QtCore.Slot(int,int,int)
+ @QtCore.Slot(int, int, int)
def _handle_paginator(self, page_number, start_index, end_index):
self._start_index = start_index
self._end_index = end_index
@@ -1893,22 +1950,21 @@ def _handle_paginator(self, page_number, start_index, end_index):
self._populate_ui()
def _update_scanned_list(self):
- ''' updates the regular scanned or sim list '''
-
+ """updates the regular scanned or sim list"""
# clear the widgets
gremlin.ui.ui_common.clear_layout(self.map_layout)
# display one row per aicraft found
if not self._data:
- missing = QtWidgets.QLabel("No mappings found.")
- missing.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
- self.map_layout.addWidget(missing)
- return
+ missing = QtWidgets.QLabel("No mappings found.")
+ missing.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
+ self.map_layout.addWidget(missing)
+ return
gremlin.util.pushCursor()
- item : SimconnectAicraftDefinition
+ item: SimconnectAicraftDefinition
self._mode_selector_map = {}
self._selected_cb_map = {}
@@ -1923,16 +1979,16 @@ def _update_scanned_list(self):
default_mode = profile.get_default_mode()
icon_color = gremlin.ui.ui_common.Color.normalColor()
-
- create_mode_icon = gremlin.util.load_icon("fa5.plus-square", qta_color=icon_color)
-
+
+ create_mode_icon = gremlin.util.load_icon(
+ "fa5.plus-square", qta_color=icon_color
+ )
+
start_index = self.paginator_widget.startIndex
end_index = self.paginator_widget.endIndex
for item in self._data[start_index:end_index]:
-
# header row
if row == 0:
-
select_widget = QtWidgets.QCheckBox()
select_widget.clicked.connect(self._global_selected_changed_cb)
select_widget.setToolTip("Select/Deselect All")
@@ -1946,14 +2002,18 @@ def _update_scanned_list(self):
aircraft_header_layout.addWidget(self.display_header_widget)
display_sort_up_widget = QtWidgets.QPushButton()
- display_sort_up_widget.setIcon(gremlin.util.load_icon("mdi.sort-ascending", qta_color=icon_color))
+ display_sort_up_widget.setIcon(
+ gremlin.util.load_icon("mdi.sort-ascending", qta_color=icon_color)
+ )
display_sort_up_widget.setMaximumWidth(20)
display_sort_up_widget.clicked.connect(self._sort_display_up_cb)
display_sort_up_widget.setStyleSheet("border: none;")
display_sort_up_widget.setToolTip("Sort aircraft ascending")
display_sort_down_widget = QtWidgets.QPushButton()
- display_sort_down_widget.setIcon(gremlin.util.load_icon("mdi.sort-descending", qta_color=icon_color))
+ display_sort_down_widget.setIcon(
+ gremlin.util.load_icon("mdi.sort-descending", qta_color=icon_color)
+ )
display_sort_down_widget.setMaximumWidth(20)
display_sort_down_widget.clicked.connect(self._sort_display_down_cb)
display_sort_down_widget.setStyleSheet("border: none;")
@@ -1967,21 +2027,18 @@ def _update_scanned_list(self):
mode_header_layout = QtWidgets.QHBoxLayout(mode_header_widget)
mode_sort_up_widget = QtWidgets.QPushButton()
- mode_sort_up_widget.setIcon(gremlin.util.load_icon("mdi.sort-ascending", qta_color=icon_color))
+ mode_sort_up_widget.setIcon(
+ gremlin.util.load_icon("mdi.sort-ascending", qta_color=icon_color)
+ )
mode_sort_up_widget.setMaximumWidth(20)
mode_sort_up_widget.clicked.connect(self._sort_mode_up_cb)
mode_sort_up_widget.setStyleSheet("border: none;")
mode_sort_up_widget.setToolTip("Sort by mode")
-
mode_widget = QtWidgets.QLabel("Profile Mode")
mode_header_layout.addWidget(mode_widget)
mode_header_layout.addStretch()
mode_header_layout.addWidget(mode_sort_up_widget)
-
-
-
-
# manufacturer_widget = QtWidgets.QLabel("Manufacturer")
@@ -1990,22 +2047,21 @@ def _update_scanned_list(self):
# community_widget = QtWidgets.QLabel("Community Folder")
# aircraft_widget = QtWidgets.QLabel("Aircraft Folder")
-
row_selector = gremlin.ui.ui_common.QRowSelectorFrame()
row_selector.setSelectable(False)
spacer = QDataWidget()
spacer.setMinimumWidth(3)
self.map_layout.addWidget(row_selector, 0, 0, 1, -1)
-
+
col = 1
self.map_layout.addWidget(spacer, row, col)
- col+=1
+ col += 1
self.map_layout.addWidget(select_widget, 0, col)
- col+=1
+ col += 1
self.map_layout.addWidget(aircraft_header_widget, 0, col)
# col+=1
# self.map_layout.addWidget(sim_name_widget, 0, col)
- col+=2
+ col += 2
self.map_layout.addWidget(mode_header_widget, 0, col)
# col+=1
# self.map_layout.addWidget(manufacturer_widget, 0, col)
@@ -2019,35 +2075,40 @@ def _update_scanned_list(self):
# self.map_layout.addWidget(aircraft_widget, 0, col)
# col+=1
- row+=1
-
+ row += 1
-
- # selector
- row_selector = gremlin.ui.ui_common.QRowSelectorFrame(selected = item.selected)
+ # selector
+ row_selector = gremlin.ui.ui_common.QRowSelectorFrame(
+ selected=item.selected
+ )
row_selector.setMinimumHeight(30)
row_selector.selected_changed.connect(self._row_selector_clicked_cb)
- selected_widget = gremlin.ui.ui_common.QDataCheckbox(data = (item, row_selector))
+ selected_widget = gremlin.ui.ui_common.QDataCheckbox(
+ data=(item, row_selector)
+ )
selected_widget.setChecked(item.selected)
selected_widget.checkStateChanged.connect(self._selected_changed_cb)
- row_selector.data = ((item, selected_widget))
+ row_selector.data = (item, selected_widget)
# aicraft display
- self.display_header_widget = gremlin.ui.ui_common.QDataLineEdit(data = (item, selected_widget))
+ self.display_header_widget = gremlin.ui.ui_common.QDataLineEdit(
+ data=(item, selected_widget)
+ )
self.display_header_widget.setReadOnly(True)
self.display_header_widget.setText(item.display_name)
self.display_header_widget.setToolTip(item.display_name)
self.display_header_widget.installEventFilter(self)
- w = len(item.display_name)* self._char_width
+ w = len(item.display_name) * self._char_width
if w > display_width:
display_width = w
-
- name_widget = gremlin.ui.ui_common.QDataLineEdit(data = (item, selected_widget))
+ name_widget = gremlin.ui.ui_common.QDataLineEdit(
+ data=(item, selected_widget)
+ )
name_widget.setReadOnly(True)
if item.sim_name:
name_widget.setText(item.sim_name)
- name_widget.installEventFilter(self)
+ name_widget.installEventFilter(self)
# # manufacturer
# manufacturer_widget = gremlin.ui.ui_common.QDataLineEdit(data = (item, selected_widget))
@@ -2067,11 +2128,10 @@ def _update_scanned_list(self):
# type_widget.setText(item.icao_type if item.icao_type else "n/a")
# type_widget.installEventFilter(self)
-
# mode drop down
- mode_selector = gremlin.ui.ui_common.QDataComboBox(data = (item, selected_widget), wheel_enabled=False)
-
-
+ mode_selector = gremlin.ui.ui_common.QDataComboBox(
+ data=(item, selected_widget), wheel_enabled=False
+ )
for display_mode, mode in self.mode_pair_list:
mode_selector.addItem(display_mode, mode)
@@ -2088,32 +2148,30 @@ def _update_scanned_list(self):
self._mode_selector_map[item] = mode_selector
self._selected_cb_map[item] = selected_widget
-
create_mode_widget = gremlin.ui.ui_common.QDataPushButton()
create_mode_widget.setIcon(create_mode_icon)
create_mode_widget.data = (item, select_widget)
create_mode_widget.setMaximumWidth(24)
create_mode_widget.clicked.connect(self._create_mode_cb)
create_mode_widget.setToolTip(f"Create mode {item.sim_name}")
-
- self.map_layout.addWidget(row_selector, row ,0 , 1, -1)
-
+ self.map_layout.addWidget(row_selector, row, 0, 1, -1)
+
spacer = QDataWidget()
spacer.setMinimumWidth(3)
spacer.installEventFilter(self)
-
+
col = 1
self.map_layout.addWidget(spacer, row, col)
- col +=1
+ col += 1
self.map_layout.addWidget(selected_widget, row, col)
- col +=1
+ col += 1
self.map_layout.addWidget(self.display_header_widget, row, col)
- col +=1
+ col += 1
# self.map_layout.addWidget(name_widget, row, col)
# col +=1
self.map_layout.addWidget(create_mode_widget, row, col)
- col +=1
+ col += 1
self.map_layout.addWidget(mode_selector, row, col)
# col +=1
# self.map_layout.addWidget(manufacturer_widget,row, col)
@@ -2121,7 +2179,7 @@ def _update_scanned_list(self):
# self.map_layout.addWidget(model_widget,row, col)
# col +=1
# self.map_layout.addWidget(type_widget,row, col)
- col +=1
+ col += 1
# self.map_layout.addWidget(community_widget,row, col)
# col +=1
# self.map_layout.addWidget(aircraft_widget,row, col)
@@ -2132,18 +2190,14 @@ def _update_scanned_list(self):
spacer.setMinimumWidth(6)
self.map_layout.addWidget(spacer, row, 8)
-
row += 1
-
- self.map_layout.setColumnStretch(3,2)
+ self.map_layout.setColumnStretch(3, 2)
display_width = min(display_width, 250)
self.map_layout.setColumnMinimumWidth(3, display_width)
-
-
def _update_manual_list(self):
- ''' updates the manual user entries '''
+ """updates the manual user entries"""
# manual entries
# clear the widgets
@@ -2153,7 +2207,7 @@ def _update_manual_list(self):
missing.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
self.manual_map_layout.addWidget(missing)
return
-
+
create_mode_icon = gremlin.util.load_icon("fa5.plus-square")
profile = gremlin.shared_state.current_profile
@@ -2164,10 +2218,7 @@ def _update_manual_list(self):
delete_icon = gremlin.util.load_icon("fa6.trash-can")
row = 0
for item in self.options._aircraft_manual_definitions:
-
if row == 0:
-
-
row_selector = gremlin.ui.ui_common.QRowSelectorFrame()
row_selector.setSelectable(False)
spacer = QDataWidget()
@@ -2177,38 +2228,37 @@ def _update_manual_list(self):
select_widget.clicked.connect(self._global_selected_changed_cb)
select_widget.setToolTip("Select/Deselect All")
-
sim_name_widget = QtWidgets.QLabel("Manual Entry Sim Name")
mode_widget = QtWidgets.QLabel("Mode")
-
self.manual_map_layout.addWidget(row_selector, 0, 0, 1, -1)
-
+
col = 1
self.manual_map_layout.addWidget(spacer, 0, col)
col += 2
self.manual_map_layout.addWidget(sim_name_widget, 0, col)
col += 2
self.manual_map_layout.addWidget(mode_widget, 0, col)
- row +=1
-
-
-
+ row += 1
# selector
- row_selector = gremlin.ui.ui_common.QRowSelectorFrame(selected = item.selected)
+ row_selector = gremlin.ui.ui_common.QRowSelectorFrame(
+ selected=item.selected
+ )
row_selector.setMinimumHeight(30)
row_selector.selected_changed.connect(self._row_selector_clicked_cb)
- selected_widget = gremlin.ui.ui_common.QDataCheckbox(data = (item, row_selector))
+ selected_widget = gremlin.ui.ui_common.QDataCheckbox(
+ data=(item, row_selector)
+ )
selected_widget.setChecked(item.selected)
selected_widget.checkStateChanged.connect(self._selected_changed_cb)
- row_selector.data = ((item, selected_widget))
+ row_selector.data = (item, selected_widget)
# name_widget = gremlin.ui.ui_common.QDataLineEdit(data = (item, selected_widget))
# if item.sim_name:
# name_widget.setText(item.sim_name)
# name_widget.valueChanged.connect(self._name_changed_cb)
- # name_widget.installEventFilter(self)
+ # name_widget.installEventFilter(self)
delete_widget = gremlin.ui.ui_common.QDataPushButton()
delete_widget.setIcon(delete_icon)
@@ -2217,14 +2267,13 @@ def _update_manual_list(self):
delete_widget.clicked.connect(self._remove_current_aircraft_cb)
# mode drop down
- mode_selector = gremlin.ui.ui_common.QDataComboBox(data = (item, selected_widget))
-
+ mode_selector = gremlin.ui.ui_common.QDataComboBox(
+ data=(item, selected_widget)
+ )
for display_mode, mode in self.mode_pair_list:
mode_selector.addItem(display_mode, mode)
-
-
mode = profile.getSimconnectMode(item.key)
if not mode:
mode = item.mode
@@ -2234,7 +2283,6 @@ def _update_manual_list(self):
index = mode_selector.findData(mode)
mode_selector.setCurrentIndex(index)
-
mode_selector.currentIndexChanged.connect(self._mode_selector_changed_cb)
self._manual_mode_selector_map[item] = mode_selector
self._manual_selected_cb_map[item] = selected_widget
@@ -2249,27 +2297,27 @@ def _update_manual_list(self):
spacer = QDataWidget()
spacer.setMinimumWidth(3)
spacer.installEventFilter(self)
-
- self.manual_map_layout.addWidget(row_selector, row , 0 , 1, -1)
+
+ self.manual_map_layout.addWidget(row_selector, row, 0, 1, -1)
col = 1
self.manual_map_layout.addWidget(spacer, row, col)
- col +=1
+ col += 1
self.manual_map_layout.addWidget(selected_widget, row, col)
- col +=1
+ col += 1
# self.manual_map_layout.addWidget(name_widget, row, col)
# col +=1
self.manual_map_layout.addWidget(create_mode_widget, row, col)
- col +=1
+ col += 1
self.manual_map_layout.addWidget(mode_selector, row, col)
- col +=1
+ col += 1
self.manual_map_layout.addWidget(delete_widget, row, col)
- col +=1
+ col += 1
self._selected_cb_map[item] = selected_widget
# next row
row += 1
-
+
# update any warnings
self._validate_entries()
@@ -2282,9 +2330,10 @@ def _name_changed_cb(self):
self._validate_entries()
def _populate_ui(self):
- ''' populates the map of aircraft to profile modes '''
+ """populates the map of aircraft to profile modes"""
from gremlin.ui import ui_common
+
self.options.validate()
gremlin.util.pushCursor()
@@ -2292,7 +2341,6 @@ def _populate_ui(self):
self._update_scanned_list()
# self._update_manual_list()
-
# mode locking is only enabled if auto mode change enabled
self._auto_mode_lock.setEnabled(self._auto_mode_switch.isChecked())
@@ -2301,22 +2349,21 @@ def _populate_ui(self):
gremlin.util.popCursor(True)
-
@QtCore.Slot()
def _sort_display_up_cb(self):
# sorts data by aicraft name
self.options._sort_mode = SimconnectSortMode.AicraftAscending
self.options.sort()
self._populate_ui()
- self.scroll_area.ensureVisible(0,0)
-
+ self.scroll_area.ensureVisible(0, 0)
+
@QtCore.Slot()
def _sort_display_down_cb(self):
# sorts data by aicraft name reversed
self.options._sort_mode = SimconnectSortMode.AircraftDescending
self.options.sort()
self._populate_ui()
- self.scroll_area.ensureVisible(0,0)
+ self.scroll_area.ensureVisible(0, 0)
@QtCore.Slot()
def _sort_mode_up_cb(self):
@@ -2324,25 +2371,22 @@ def _sort_mode_up_cb(self):
self.options._sort_mode = SimconnectSortMode.Mode
self.options.sort()
self._populate_ui()
- self.scroll_area.ensureVisible(0,0)
-
+ self.scroll_area.ensureVisible(0, 0)
@QtCore.Slot(bool)
def _global_selected_changed_cb(self, checked):
for item in self._selected_cb_map.keys():
self._selected_cb_map[item].setChecked(checked)
-
def _get_selected(self):
- ''' gets the items that are selected '''
+ """gets the items that are selected"""
return [item for item in self._data if item.selected]
-
@QtCore.Slot(bool)
def _selected_changed_cb(self, state):
widget = self.sender()
item, row_selector = widget.data
- checked = widget.isChecked() # param is an enum - ignore
+ checked = widget.isChecked() # param is an enum - ignore
item.selected = checked
row_selector.selected = checked
@@ -2355,15 +2399,13 @@ def _row_selector_clicked_cb(self):
with QtCore.QSignalBlocker(selector_widget):
selector_widget.setChecked(checked)
-
-
def eventFilter(self, widget, event):
- ''' ensure line changes are saved '''
+ """ensure line changes are saved"""
t = event.type()
if t == QtCore.QEvent.Type.MouseButtonPress and hasattr(widget, "data"):
item, selected_widget = widget.data
selected_widget.setChecked(not selected_widget.isChecked())
- return True # handled
+ return True # handled
elif t == QtCore.QEvent.Type.KeyPress:
key = event.key()
@@ -2372,13 +2414,12 @@ def eventFilter(self, widget, event):
widget.returnPressed.emit()
elif widget == self._msfs_path_widget:
self._community_folder_open_cb()
- return True # handled
+ return True # handled
return super().eventFilter(widget, event)
-
@QtCore.Slot()
def _create_mode_cb(self):
- ''' create a mode in the profile for the aircraft '''
+ """create a mode in the profile for the aircraft"""
widget = self.sender()
item, _ = widget.data
profile = gremlin.shared_state.current_profile
@@ -2390,18 +2431,19 @@ def _create_mode_cb(self):
dialog.setWindowModality(QtCore.Qt.ApplicationModal)
dialog.show()
else:
- gremlin.ui.ui_common.MessageBox(prompt=f"Mode {item.sim_name} already exists in the profile.")
-
+ gremlin.ui.ui_common.MessageBox(
+ prompt=f"Mode {item.sim_name} already exists in the profile."
+ )
@QtCore.Slot(int)
def _mode_selector_changed_cb(self, selected_index):
- ''' occurs when the mode is changed on an entry '''
+ """occurs when the mode is changed on an entry"""
profile = gremlin.shared_state.current_profile
widget = self.sender()
mode = widget.currentData()
item, _ = widget.data
items = self._get_selected()
- if not item in items:
+ if item not in items:
# include the current item if not in the selection
items.append(item)
mode_index = None
@@ -2415,40 +2457,29 @@ def _mode_selector_changed_cb(self, selected_index):
selector.setCurrentIndex(mode_index)
item.mode = mode
profile.setSimconnectMode(key, mode)
- print (f"set mode {mode} for {item.sim_name}")
-
-
+ print(f"set mode {mode} for {item.sim_name}")
@QtCore.Slot()
def _active_button_cb(self):
widget = self.sender()
sm = SimConnectManager()
-
+
aircraft = sm.get_aircraft()
if aircraft:
item = widget.data
item.aircraft = aircraft
-
@QtCore.Slot()
def _mode_from_aircraft_button_cb(self):
- ''' mode from aicraft button '''
+ """mode from aicraft button"""
aircraft, model, title = self._sm_data.get_aircraft_data()
- if self._verbose: syslog.info(f"Aircraft: {aircraft} model: {model} title: {title}")
- if not title in self._mode_list:
+ if self._verbose:
+ syslog.info(f"Aircraft: {aircraft} model: {model} title: {title}")
+ if title not in self._mode_list:
self.profile.add_mode(title)
-
-
-
-
-
-
-
-
class MapToSimConnectWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""UI widget for mapping inputs to keyboard key combinations - adds extra functionality to the base module ."""
def __init__(self, action_data, parent=None):
@@ -2461,45 +2492,49 @@ def __init__(self, action_data, parent=None):
# call super last because it will call create_ui and populate_ui so the vars must exist
super().__init__(action_data, parent=parent)
-
def _create(self, action_data):
- '''' initialize before createUI() '''
-
- self.action_data : MapToSimConnect = action_data
+ """' initialize before createUI()"""
+
+ self.action_data: MapToSimConnect = action_data
self.action_data.events.range_changed.connect(self._action_range_changed)
-
self._simconnect = SimConnectManager().simconnect
# handler to update curve widget if displayed
self.curve_update_handler = None
- self.input_type = self.action_data.hardware_input_type
+ self.input_type = self.action_data.hardware_input_type
self._is_axis = self.action_data.input_is_axis()
- self._current_value = 0 # value of the current axis input for the repeater
+ self._current_value = 0 # value of the current axis input for the repeater
self.manager = SimConnectManager()
self.manager.lvars_updated.connect(self._lvars_udpated_cb)
-
-
@QtCore.Slot()
def _action_range_changed(self):
- ''' occurs when the range update to the action data caused another update '''
+ """occurs when the range update to the action data caused another update"""
self._update_ui()
def _create_ui(self):
"""Creates the UI components."""
- #import gremlin.gated_handler
+ # import gremlin.gated_handler
verbose = gremlin.config.Configuration().verbose_mode_detailed
# syslog = logging.getLogger("system")
if verbose:
- syslog.info(f"Simconnect UI for: {self.action_data.hardware_input_type_name} {self.action_data.hardware_device_name} input: {self.action_data.hardware_input_id}")
+ syslog.info(
+ f"Simconnect UI for: {self.action_data.hardware_input_type_name} {self.action_data.hardware_device_name} input: {self.action_data.hardware_input_id}"
+ )
warning_color = gremlin.ui.ui_common.Color.warningColor()
- self._warning_widget = gremlin.ui.ui_common.QIconLabel("ph.shield-warning-fill",use_qta=True,icon_color=QtGui.QColor(warning_color),text="Parameter Calculation requires a {#} marker in the expression where the output value goes.", use_wrap=False)
+ self._warning_widget = gremlin.ui.ui_common.QIconLabel(
+ "ph.shield-warning-fill",
+ use_qta=True,
+ icon_color=QtGui.QColor(warning_color),
+ text="Parameter Calculation requires a {#} marker in the expression where the output value goes.",
+ use_wrap=False,
+ )
- # if the input is chained
+ # if the input is chained
self.chained_input = self.action_data.input_item.is_action
# mode from aircraft button - grabs the aicraft name as a mode
@@ -2507,30 +2542,32 @@ def _create_ui(self):
self._options_button_widget.setIcon(gremlin.util.load_icon("fa6s.gear"))
self._options_button_widget.clicked.connect(self._show_options_dialog_cb)
-
-
-
# holds type options - visible for manual entries
self._type_container_widget = QtWidgets.QWidget()
- self._type_container_widget.setContentsMargins(0,0,0,0)
+ self._type_container_widget.setContentsMargins(0, 0, 0, 0)
self._type_container_layout = QtWidgets.QHBoxLayout(self._type_container_widget)
- self._type_container_layout.setContentsMargins(0,0,0,0)
-
-
- # holds data entry mode options
- data = [(SimConnectCommandMode.to_display(mode),
- mode,
- SimConnectCommandMode.to_description(mode)
- ) for mode in SimConnectCommandMode]
+ self._type_container_layout.setContentsMargins(0, 0, 0, 0)
+
+ # holds data entry mode options
+ data = [
+ (
+ SimConnectCommandMode.to_display(mode),
+ mode,
+ SimConnectCommandMode.to_description(mode),
+ )
+ for mode in SimConnectCommandMode
+ ]
- self._mode_container_widget, self._mode_container_layout = gremlin.ui.ui_common.getRadioContainer(data,
- self._command_mode_changed_cb,
- default =self.action_data.command_mode,
- label="Simconnect mode:")
+ self._mode_container_widget, self._mode_container_layout = (
+ gremlin.ui.ui_common.getRadioContainer(
+ data,
+ self._command_mode_changed_cb,
+ default=self.action_data.command_mode,
+ label="Simconnect mode:",
+ )
+ )
self._mode_container_layout.addWidget(self._options_button_widget)
-
-
# type options
self._type_is_calculator_widget = QtWidgets.QCheckBox("RPN (Calculator) code")
@@ -2547,74 +2584,93 @@ def _create_ui(self):
self._type_container_layout.addWidget(self._type_units_widget)
self._type_container_layout.addStretch()
-
-
# command selector
self._command_container_widget = QtWidgets.QWidget()
- self._command_container_widget.setContentsMargins(0,0,0,0)
- self._command_container_layout = QtWidgets.QVBoxLayout(self._command_container_widget)
- self._command_container_layout.setContentsMargins(0,0,0,0)
-
+ self._command_container_widget.setContentsMargins(0, 0, 0, 0)
+ self._command_container_layout = QtWidgets.QVBoxLayout(
+ self._command_container_widget
+ )
+ self._command_container_layout.setContentsMargins(0, 0, 0, 0)
- # actions elector
+ # actions elector
self._action_selector_widget = QtWidgets.QWidget()
- self._action_selector_widget.setContentsMargins(0,0,0,0)
- self._action_selector_layout = QtWidgets.QHBoxLayout(self._action_selector_widget)
- self._action_selector_layout.setContentsMargins(0,0,0,0)
-
+ self._action_selector_widget.setContentsMargins(0, 0, 0, 0)
+ self._action_selector_layout = QtWidgets.QHBoxLayout(
+ self._action_selector_widget
+ )
+ self._action_selector_layout.setContentsMargins(0, 0, 0, 0)
# calculator selector
self._calculator_container_widget = QtWidgets.QWidget()
- self._calculator_container_widget.setContentsMargins(0,0,0,0)
- self._calculator_container_layout = QtWidgets.QVBoxLayout(self._calculator_container_widget)
- self._calculator_container_layout.setContentsMargins(0,0,0,0)
+ self._calculator_container_widget.setContentsMargins(0, 0, 0, 0)
+ self._calculator_container_layout = QtWidgets.QVBoxLayout(
+ self._calculator_container_widget
+ )
+ self._calculator_container_layout.setContentsMargins(0, 0, 0, 0)
# calculator release selector
self._calculator_release_container_widget = QtWidgets.QWidget()
- self._calculator_release_container_widget.setContentsMargins(0,0,0,0)
- self._calculator_release_container_layout = QtWidgets.QVBoxLayout(self._calculator_release_container_widget)
- self._calculator_release_container_layout.setContentsMargins(0,0,0,0)
+ self._calculator_release_container_widget.setContentsMargins(0, 0, 0, 0)
+ self._calculator_release_container_layout = QtWidgets.QVBoxLayout(
+ self._calculator_release_container_widget
+ )
+ self._calculator_release_container_layout.setContentsMargins(0, 0, 0, 0)
# list of possible events to trigger
self._command_selector_widget = gremlin.ui.ui_common.QComboBox()
self._command_list = self.action_data._manager.get_command_name_list()
self._command_selector_widget.setEditable(True)
self._command_selector_widget.addItems(self._command_list)
- self._command_selector_widget.currentIndexChanged.connect(self._command_changed_cb)
+ self._command_selector_widget.currentIndexChanged.connect(
+ self._command_changed_cb
+ )
self._command_selector_widget.setValidator(CommandValidator())
self._command_selector_widget.setMinimumWidth(200)
# setup auto-completer for the command
- self._command_completer = QtWidgets.QCompleter(self._command_selector_widget.validator().commands, self)
- self._command_completer.setCaseSensitivity(QtGui.Qt.CaseSensitivity.CaseInsensitive)
+ self._command_completer = QtWidgets.QCompleter(
+ self._command_selector_widget.validator().commands, self
+ )
+ self._command_completer.setCaseSensitivity(
+ QtGui.Qt.CaseSensitivity.CaseInsensitive
+ )
self._command_completer.setFilterMode(QtCore.Qt.MatchFlag.MatchContains)
self._command_selector_widget.setCompleter(self._command_completer)
- data = [("Block",SimConnectCommandType.SimVar),
- ("LVAR",SimConnectCommandType.LVar)]
- self._simvar_mode_container_widget, _ = gremlin.ui.ui_common.getRadioContainer(data, self._command_type_changed, default = self.action_data.command_type,label="Simvar mode:" )
-
+ data = [
+ ("Block", SimConnectCommandType.SimVar),
+ ("LVAR", SimConnectCommandType.LVar),
+ ]
+ self._simvar_mode_container_widget, _ = gremlin.ui.ui_common.getRadioContainer(
+ data,
+ self._command_type_changed,
+ default=self.action_data.command_type,
+ label="Simvar mode:",
+ )
+
self._lvar_command_widget = QtWidgets.QLineEdit()
self._lvar_command_widget.setText(self.action_data.command)
self._lvar_command_widget.textChanged.connect(self._lvar_changed_cb)
- self._lvar_container_widget, _ = gremlin.ui.ui_common.getHContainer(self._lvar_command_widget,"Set LVAR:")
+ self._lvar_container_widget, _ = gremlin.ui.ui_common.getHContainer(
+ self._lvar_command_widget, "Set LVAR:"
+ )
self._command_container_layout.addWidget(self._simvar_mode_container_widget)
self._command_container_layout.addWidget(self._action_selector_widget)
self._command_container_layout.addWidget(self._lvar_container_widget)
-
# lvar lookup container
self._lvar_lookup_container_widget = QtWidgets.QWidget()
- self._lvar_lookup_container_widget.setContentsMargins(0,0,0,0)
- self._lvar_lookup_container_layout = QtWidgets.QHBoxLayout(self._lvar_lookup_container_widget)
- self._lvar_lookup_container_layout.setContentsMargins(0,0,0,0)
+ self._lvar_lookup_container_widget.setContentsMargins(0, 0, 0, 0)
+ self._lvar_lookup_container_layout = QtWidgets.QHBoxLayout(
+ self._lvar_lookup_container_widget
+ )
+ self._lvar_lookup_container_layout.setContentsMargins(0, 0, 0, 0)
self._refresh_lvar_widget = QtWidgets.QPushButton("Lvars")
self._refresh_lvar_widget.setIcon(gremlin.util.load_icon("ei.refresh"))
self._refresh_lvar_widget.clicked.connect(self._refresh_lvar_cb)
-
# list of possible lvars to trigger
self._lvar_selector_widget = gremlin.ui.ui_common.QComboBox()
self._lvar_selector_widget.setEditable(True)
@@ -2629,8 +2685,12 @@ def _create_ui(self):
self._lvar_button_widget.clicked.connect(self._lvar_selected_cb)
# setup auto-completer for the lvar
- self._lvar_completer = QtWidgets.QCompleter(self._lvar_selector_widget.validator().lvars, self)
- self._lvar_completer.setCaseSensitivity(QtGui.Qt.CaseSensitivity.CaseInsensitive)
+ self._lvar_completer = QtWidgets.QCompleter(
+ self._lvar_selector_widget.validator().lvars, self
+ )
+ self._lvar_completer.setCaseSensitivity(
+ QtGui.Qt.CaseSensitivity.CaseInsensitive
+ )
self._lvar_completer.setFilterMode(QtCore.Qt.MatchFlag.MatchContains)
self._lvar_selector_widget.setCompleter(self._lvar_completer)
@@ -2641,39 +2701,50 @@ def _create_ui(self):
self._lvar_lookup_container_layout.addWidget(self._refresh_lvar_widget)
self._lvar_lookup_container_layout.addStretch()
-
# calculator entry
self._calculator_entry_widget = QtWidgets.QTextEdit()
self._calculator_entry_widget.setAcceptRichText(False)
- self._calculator_entry_widget.setToolTip("RPN calculator expression sent to MSFS")
+ self._calculator_entry_widget.setToolTip(
+ "RPN calculator expression sent to MSFS"
+ )
self._calculator_entry_widget.setMinimumWidth(200)
self._calculator_entry_widget.setPlainText(self.action_data.command)
self._calculator_entry_widget.textChanged.connect(self._expression_changed_cb)
-
self._calculator_release_entry_widget = QtWidgets.QTextEdit()
self._calculator_release_entry_widget.setAcceptRichText(False)
- self._calculator_release_entry_widget.setToolTip("RPN calculator expression sent to MSFS on input release")
+ self._calculator_release_entry_widget.setToolTip(
+ "RPN calculator expression sent to MSFS on input release"
+ )
self._calculator_release_entry_widget.setMinimumWidth(200)
- self._calculator_release_entry_widget.setPlainText(self.action_data.command_release)
- self._calculator_release_entry_widget.textChanged.connect(self._expression_release_changed_cb)
+ self._calculator_release_entry_widget.setPlainText(
+ self.action_data.command_release
+ )
+ self._calculator_release_entry_widget.textChanged.connect(
+ self._expression_release_changed_cb
+ )
self._calculator_container_layout.addWidget(QtWidgets.QLabel("RPN Expression:"))
self._calculator_container_layout.addWidget(self._calculator_entry_widget)
+ self._calculator_release_container_layout.addWidget(
+ QtWidgets.QLabel("RPN Expression on release:")
+ )
+ self._calculator_release_container_layout.addWidget(
+ self._calculator_release_entry_widget
+ )
- self._calculator_release_container_layout.addWidget(QtWidgets.QLabel("RPN Expression on release:"))
- self._calculator_release_container_layout.addWidget(self._calculator_release_entry_widget)
-
-
self._autorepeat_container_widget = QtWidgets.QWidget()
- self._autorepeat_container_layout = QtWidgets.QHBoxLayout(self._autorepeat_container_widget)
-
+ self._autorepeat_container_layout = QtWidgets.QHBoxLayout(
+ self._autorepeat_container_widget
+ )
self._autorepeat_widget = QtWidgets.QCheckBox("Autorepeat")
self._autorepeat_widget.setChecked(self.action_data.auto_repeat)
self._autorepeat_widget.clicked.connect(self._auto_repeat_state_changed)
- self._autorepeat_widget.setToolTip("When enabled, the command will repeat at set interval while the input is pressed")
+ self._autorepeat_widget.setToolTip(
+ "When enabled, the command will repeat at set interval while the input is pressed"
+ )
self._autorepeat_delay_label = QtWidgets.QLabel("Repeat Interval (ms)")
self._autorepeat_delay_widget = gremlin.ui.ui_common.QIntLineEdit()
@@ -2681,61 +2752,66 @@ def _create_ui(self):
width = gremlin.ui.ui_common.get_char_width(8)
self._autorepeat_delay_widget.setMaximumWidth(width)
self._autorepeat_delay_widget.setValue(self.action_data.auto_repeat_interval)
- self._autorepeat_delay_widget.valueChanged.connect(self._auto_repeat_delay_changed)
-
+ self._autorepeat_delay_widget.valueChanged.connect(
+ self._auto_repeat_delay_changed
+ )
- self._release_command_widget = QtWidgets.QCheckBox("Separate Release Expression")
- self._release_command_widget.setToolTip("If enabled, a separate expression will be sent on input release")
+ self._release_command_widget = QtWidgets.QCheckBox(
+ "Separate Release Expression"
+ )
+ self._release_command_widget.setToolTip(
+ "If enabled, a separate expression will be sent on input release"
+ )
self._release_command_widget.setChecked(self.action_data.is_release_command)
self._release_command_widget.clicked.connect(self._is_release_command_changed)
-
self._autorepeat_container_layout.addWidget(self._release_command_widget)
self._autorepeat_container_layout.addWidget(self._autorepeat_widget)
self._autorepeat_container_layout.addWidget(self._autorepeat_delay_label)
self._autorepeat_container_layout.addWidget(self._autorepeat_delay_widget)
self._autorepeat_container_layout.addStretch()
-
-
self._calculator_container_layout.addWidget(self._autorepeat_container_widget)
-
- #self.action_selector_layout.addWidget(self.category_widget)
+ # self.action_selector_layout.addWidget(self.category_widget)
self._action_selector_layout.addWidget(QtWidgets.QLabel("Selected command:"))
self._action_selector_layout.addWidget(self._command_selector_widget)
self._action_selector_layout.addStretch()
-
- self._action_selector_widget.setContentsMargins(0,0,0,0)
-
+ self._action_selector_widget.setContentsMargins(0, 0, 0, 0)
self._output_mode_container_widget = QtWidgets.QWidget()
- self._output_mode_container_widget.setContentsMargins(0,0,0,0)
- self._output_mode_container_layout = QtWidgets.QHBoxLayout(self._output_mode_container_widget)
- self._output_mode_container_layout.setContentsMargins(0,0,0,0)
+ self._output_mode_container_widget.setContentsMargins(0, 0, 0, 0)
+ self._output_mode_container_layout = QtWidgets.QHBoxLayout(
+ self._output_mode_container_widget
+ )
+ self._output_mode_container_layout.setContentsMargins(0, 0, 0, 0)
self._output_mode_readonly_widget = QtWidgets.QRadioButton("Read/Only")
self._output_mode_readonly_widget.setEnabled(False)
-
# set range of values output mode (axis input only)
self._output_mode_ranged_widget = QtWidgets.QRadioButton("Ranged")
self._output_mode_ranged_widget.clicked.connect(self._mode_ranged_cb)
- self._output_mode_ranged_widget.setToolTip("Sets the output as a linear axis to the simconnect command.
The output is scaled to the specified output range as defined by the command or manually.")
-
+ self._output_mode_ranged_widget.setToolTip(
+ "Sets the output as a linear axis to the simconnect command.
The output is scaled to the specified output range as defined by the command or manually."
+ )
+
# trigger output mode (event trigger only)
self._output_mode_trigger_widget = QtWidgets.QRadioButton("Trigger")
self._output_mode_trigger_widget.clicked.connect(self._mode_trigger_cb)
- self._output_mode_trigger_widget.setToolTip("Triggers a simconnect command (for momentary inputs only like a button or a hat)")
+ self._output_mode_trigger_widget.setToolTip(
+ "Triggers a simconnect command (for momentary inputs only like a button or a hat)"
+ )
self._output_mode_description_widget = QtWidgets.QLabel()
self._output_mode_container_layout.addWidget(QtWidgets.QLabel("Output mode:"))
-
# set value output mode (output value only)
self._output_mode_set_value_widget = QtWidgets.QRadioButton("Value")
self._output_mode_set_value_widget.clicked.connect(self._mode_value_cb)
- self._output_mode_set_value_widget.setToolTip("Sends a single value to the simconnect command regardless of the input.")
+ self._output_mode_set_value_widget.setToolTip(
+ "Sends a single value to the simconnect command regardless of the input."
+ )
self._output_mode_container_layout.addWidget(self._output_mode_readonly_widget)
self._output_mode_container_layout.addWidget(self._output_mode_trigger_widget)
@@ -2747,67 +2823,74 @@ def _create_ui(self):
self._output_mode_container_layout.addWidget(self.output_readonly_status_widget)
self._button_mode_container_widget = QtWidgets.QWidget()
- self._button_mode_container_layout = QtWidgets.QHBoxLayout(self._button_mode_container_widget)
+ self._button_mode_container_layout = QtWidgets.QHBoxLayout(
+ self._button_mode_container_widget
+ )
self._trigger_on_release_widget = QtWidgets.QCheckBox("Trigger on release")
- self._trigger_on_release_widget.setToolTip("When enabled, the action will trigger when the input is released.")
+ self._trigger_on_release_widget.setToolTip(
+ "When enabled, the action will trigger when the input is released."
+ )
self._trigger_on_release_widget.clicked.connect(self._trigger_on_release_cb)
self._trigger_on_press_widget = QtWidgets.QCheckBox("Trigger on press")
- self._trigger_on_press_widget.setToolTip("When enabled, the action will trigger when the input is released.")
+ self._trigger_on_press_widget.setToolTip(
+ "When enabled, the action will trigger when the input is released."
+ )
self._trigger_on_press_widget.clicked.connect(self._trigger_on_press_cb)
-
self._button_mode_container_layout.addWidget(self._trigger_on_press_widget)
self._button_mode_container_layout.addWidget(self._trigger_on_release_widget)
self._button_mode_container_layout.addStretch()
-
-
-
# output data type UI
self._output_data_type_widget = QtWidgets.QWidget()
- self._output_data_type_widget.setContentsMargins(0,0,0,0)
- self._output_data_type_layout = QtWidgets.QHBoxLayout(self._output_data_type_widget)
+ self._output_data_type_widget.setContentsMargins(0, 0, 0, 0)
+ self._output_data_type_layout = QtWidgets.QHBoxLayout(
+ self._output_data_type_widget
+ )
self._output_data_2_type_widget = QtWidgets.QWidget()
- self._output_data_2_type_widget.setContentsMargins(0,0,0,0)
- self._output_data_2_type_layout = QtWidgets.QHBoxLayout(self._output_data_2_type_widget)
+ self._output_data_2_type_widget.setContentsMargins(0, 0, 0, 0)
+ self._output_data_2_type_layout = QtWidgets.QHBoxLayout(
+ self._output_data_2_type_widget
+ )
+ self._output_data_type_layout.setContentsMargins(0, 0, 0, 0)
- self._output_data_type_layout.setContentsMargins(0,0,0,0)
-
self._output_data_type_label_widget = QtWidgets.QLabel("Not Set")
-
-
self._output_data_type_layout.addWidget(self._output_data_type_label_widget)
self._output_data_type_layout.addWidget(self._output_mode_description_widget)
self._output_data_type_layout.addStretch()
- self._output_data_2_type_layout.addWidget(QtWidgets.QLabel("Output type:"))
+ self._output_data_2_type_layout.addWidget(
+ QtWidgets.QLabel("Output type:")
+ )
self._output_data_2_type_layout.addStretch()
-
-
# output range UI
self._output_range_container_widget = QtWidgets.QWidget()
- self._output_range_container_widget.setContentsMargins(0,0,0,0)
- self._output_range_container_layout = QtWidgets.QVBoxLayout(self._output_range_container_widget)
- self._output_range_container_layout.setContentsMargins(0,0,0,0)
+ self._output_range_container_widget.setContentsMargins(0, 0, 0, 0)
+ self._output_range_container_layout = QtWidgets.QVBoxLayout(
+ self._output_range_container_widget
+ )
+ self._output_range_container_layout.setContentsMargins(0, 0, 0, 0)
# output value widget - displays a min/max range or a fixed value
- self._value_widget = gremlin.ui.ui_common.QJoystickRangeWidget(show_mode_change=False,
- min_norm= self.action_data.normalized_min_range,
- max_norm= self.action_data.normalized_max_range,
- min_cmd= self.action_data.command_min_range,
- max_cmd= self.action_data.command_max_range,
- min_range=-16383,
- max_range=16384,
- parent = self)
+ self._value_widget = gremlin.ui.ui_common.QJoystickRangeWidget(
+ show_mode_change=False,
+ min_norm=self.action_data.normalized_min_range,
+ max_norm=self.action_data.normalized_max_range,
+ min_cmd=self.action_data.command_min_range,
+ max_cmd=self.action_data.command_max_range,
+ min_range=-16383,
+ max_range=16384,
+ parent=self,
+ )
self._value_widget.valueChanged.connect(self._value_changed)
self._value_widget.rangeChanged.connect(self._range_changed)
self._value_widget.invertChanged.connect(self._inverted_changed)
-
+
# output range buttons
widget, layout = gremlin.ui.ui_common.getHContainer()
self._range_button_container_widget = widget
@@ -2815,26 +2898,32 @@ def _create_ui(self):
self._output_range_ref_text_widget = QtWidgets.QLabel()
self._output_range_container_layout.addWidget(self._value_widget)
- self._output_range_container_layout.addWidget(self._range_button_container_widget)
+ self._output_range_container_layout.addWidget(
+ self._range_button_container_widget
+ )
w = gremlin.ui.ui_common.get_text_width("0000000.0000")
-
self._reset_range_widget = QtWidgets.QPushButton("Reset")
self._reset_range_widget.setToolTip("Reset the range to -1 +1")
self._reset_range_widget.clicked.connect(self._reset_range)
# output axis repeater
self.container_repeater_widget = QtWidgets.QWidget()
- self.container_repeater_layout = QtWidgets.QHBoxLayout(self.container_repeater_widget)
+ self.container_repeater_layout = QtWidgets.QHBoxLayout(
+ self.container_repeater_widget
+ )
-
self.curve_button_widget = QtWidgets.QPushButton("Output Curve")
active_color = gremlin.ui.ui_common.Color.activeColor()
normal_color = gremlin.ui.ui_common.Color.normalColor()
- self.curve_icon_inactive = gremlin.util.load_icon("mdi.chart-bell-curve",qta_color=normal_color)
- self.curve_icon_active = gremlin.util.load_icon("mdi.chart-bell-curve",qta_color=active_color)
-
+ self.curve_icon_inactive = gremlin.util.load_icon(
+ "mdi.chart-bell-curve", qta_color=normal_color
+ )
+ self.curve_icon_active = gremlin.util.load_icon(
+ "mdi.chart-bell-curve", qta_color=active_color
+ )
+
self.curve_button_widget.setToolTip("Curve output")
self.curve_button_widget.clicked.connect(self._curve_button_cb)
@@ -2844,14 +2933,15 @@ def _create_ui(self):
self.curve_clear_widget.setToolTip("Removes the curve output")
self.curve_clear_widget.clicked.connect(self._curve_delete_button_cb)
- self._axis_repeater_widget = gremlin.ui.ui_common.AxisStateWidget(show_percentage=True,orientation=QtCore.Qt.Orientation.Horizontal)
+ self._axis_repeater_widget = gremlin.ui.ui_common.AxisStateWidget(
+ show_percentage=True, orientation=QtCore.Qt.Orientation.Horizontal
+ )
self._axis_value_widget = gremlin.ui.ui_common.QFloatLineEdit()
self._axis_value_widget.setRange(-16383, 16384)
self._axis_value_widget.setReadOnly(True)
self._axis_value_widget.setMinimumWidth(w)
self._axis_value_widget.setDecimals(0)
-
# self._axis_alt_repeater_widget = gremlin.ui.ui_common.AxisStateWidget(show_percentage=True,orientation=QtCore.Qt.Orientation.Horizontal)
# self._axis_alt_value_widget = gremlin.ui.ui_common.QFloatLineEdit()
# self._axis_alt_value_widget.setReadOnly(True)
@@ -2861,126 +2951,145 @@ def _create_ui(self):
self._calculator_value_widget = QtWidgets.QPlainTextEdit()
self._calculator_value_widget.setReadOnly(True)
-
self.container_repeater_layout.addWidget(self.curve_button_widget)
self.container_repeater_layout.addWidget(self.curve_clear_widget)
self.container_repeater_layout.addWidget(self._axis_repeater_widget)
- #self.container_repeater_layout.addWidget(self._axis_alt_repeater_widget)
+ # self.container_repeater_layout.addWidget(self._axis_alt_repeater_widget)
self.container_repeater_layout.addWidget(QtWidgets.QLabel("SimConnect Output:"))
self.container_repeater_layout.addWidget(self._axis_value_widget)
self.container_repeater_layout.addWidget(self._calculator_value_widget)
self.container_repeater_layout.addStretch()
self._update_curve_icon()
-
if self.action_data.input_type == InputType.JoystickAxis:
self._update_axis_widget()
-
self._range_button_container_layout.addWidget(QtWidgets.QLabel("Presets:"))
-
- widget = gremlin.ui.ui_common.QDataPushButton("Percent", data = (0, 100))
+ widget = gremlin.ui.ui_common.QDataPushButton("Percent", data=(0, 100))
widget.clicked.connect(self._set_command_range)
self._range_button_container_layout.addWidget(widget)
-
- widget = gremlin.ui.ui_common.QDataPushButton("-16K..+16K", data = (-16383, 16384))
+ widget = gremlin.ui.ui_common.QDataPushButton(
+ "-16K..+16K", data=(-16383, 16384)
+ )
widget.clicked.connect(self._set_command_range)
self._range_button_container_layout.addWidget(widget)
-
- widget = gremlin.ui.ui_common.QDataPushButton("0..16K", data = (0, 16384))
+ widget = gremlin.ui.ui_common.QDataPushButton("0..16K", data=(0, 16384))
widget.clicked.connect(self._set_command_range)
self._range_button_container_layout.addWidget(widget)
self._range_button_container_layout.addStretch()
-
-
-
self._output_value_description_widget = QtWidgets.QLabel()
-
+
self.command_header_container_widget = QtWidgets.QWidget()
- self.command_header_container_layout = QtWidgets.QVBoxLayout(self.command_header_container_widget)
-
+ self.command_header_container_layout = QtWidgets.QVBoxLayout(
+ self.command_header_container_widget
+ )
self.command_text_widget = QtWidgets.QLabel()
- self.command_header_container_layout.addWidget(QtWidgets.QLabel("Command:"))
+ self.command_header_container_layout.addWidget(
+ QtWidgets.QLabel("Command:")
+ )
self.command_header_container_layout.addWidget(self.command_text_widget)
-
self.description_text_widget = QtWidgets.QLabel()
- self.command_header_container_layout.addWidget(QtWidgets.QLabel("Description"))
+ self.command_header_container_layout.addWidget(
+ QtWidgets.QLabel("Description")
+ )
self.command_header_container_layout.addWidget(self.description_text_widget)
- self.command_header_container_layout.setContentsMargins(0,0,0,0)
-
+ self.command_header_container_layout.setContentsMargins(0, 0, 0, 0)
self.command_header_container_layout.addWidget(self._output_data_type_widget)
self.command_header_container_layout.addWidget(self._output_data_2_type_widget)
self.command_header_container_layout.addStretch(1)
-
- self._output_trigger_description_widget = QtWidgets.QLabel()
+ self._output_trigger_description_widget = QtWidgets.QLabel()
self._output_trigger_bool_noop_widget = QtWidgets.QRadioButton("Trigger Only")
- self._output_trigger_bool_noop_widget.clicked.connect(self._trigger_noop_changed_cb)
-
+ self._output_trigger_bool_noop_widget.clicked.connect(
+ self._trigger_noop_changed_cb
+ )
+
self._output_trigger_bool_toggle_widget = QtWidgets.QRadioButton("Toggle")
- self._output_trigger_bool_toggle_widget.clicked.connect(self._trigger_toggle_changed_cb)
-
+ self._output_trigger_bool_toggle_widget.clicked.connect(
+ self._trigger_toggle_changed_cb
+ )
+
self._output_trigger_bool_on_widget = QtWidgets.QRadioButton("On")
self._output_trigger_bool_on_widget.clicked.connect(self._trigger_turnon_cb)
-
+
self._output_trigger_bool_off_widget = QtWidgets.QRadioButton("Off")
self._output_trigger_bool_off_widget.clicked.connect(self._trigger_turnoff_cb)
- self._output_trigger_bool_input_value_widget = QtWidgets.QRadioButton("Input Value")
- self._output_trigger_bool_input_value_widget.clicked.connect(self._trigger_input_value_cb)
-
+ self._output_trigger_bool_input_value_widget = QtWidgets.QRadioButton(
+ "Input Value"
+ )
+ self._output_trigger_bool_input_value_widget.clicked.connect(
+ self._trigger_input_value_cb
+ )
self._output_trigger_bool_container_widget = QtWidgets.QWidget()
- self._output_trigger_bool_container_widget.setContentsMargins(0,0,0,0)
- self._output_trigger_bool_container_layout = QtWidgets.QHBoxLayout(self._output_trigger_bool_container_widget)
- self._output_trigger_bool_container_layout.setContentsMargins(0,0,0,0)
-
- self._output_trigger_bool_container_layout.addWidget(QtWidgets.QLabel("Trigger Mode:"))
- self._output_trigger_bool_container_layout.addWidget(self._output_trigger_bool_noop_widget)
- self._output_trigger_bool_container_layout.addWidget(self._output_trigger_bool_input_value_widget)
- self._output_trigger_bool_container_layout.addWidget(self._output_trigger_bool_toggle_widget)
- self._output_trigger_bool_container_layout.addWidget(self._output_trigger_bool_on_widget)
- self._output_trigger_bool_container_layout.addWidget(self._output_trigger_bool_off_widget)
- self._output_trigger_bool_container_layout.addWidget(self._output_trigger_description_widget)
+ self._output_trigger_bool_container_widget.setContentsMargins(0, 0, 0, 0)
+ self._output_trigger_bool_container_layout = QtWidgets.QHBoxLayout(
+ self._output_trigger_bool_container_widget
+ )
+ self._output_trigger_bool_container_layout.setContentsMargins(0, 0, 0, 0)
+
+ self._output_trigger_bool_container_layout.addWidget(
+ QtWidgets.QLabel("Trigger Mode:")
+ )
+ self._output_trigger_bool_container_layout.addWidget(
+ self._output_trigger_bool_noop_widget
+ )
+ self._output_trigger_bool_container_layout.addWidget(
+ self._output_trigger_bool_input_value_widget
+ )
+ self._output_trigger_bool_container_layout.addWidget(
+ self._output_trigger_bool_toggle_widget
+ )
+ self._output_trigger_bool_container_layout.addWidget(
+ self._output_trigger_bool_on_widget
+ )
+ self._output_trigger_bool_container_layout.addWidget(
+ self._output_trigger_bool_off_widget
+ )
+ self._output_trigger_bool_container_layout.addWidget(
+ self._output_trigger_description_widget
+ )
self._output_trigger_bool_container_layout.addStretch()
# status widget
self.status_text_widget = gremlin.ui.ui_common.QIconLabel()
-
-
-
-
# output options container - shows below selector - visible when a command is selected and changes with the active mode
widget, layout = gremlin.ui.ui_common.getVContainer()
self._output_container_widget = widget
self._output_container_layout = layout
-
self._output_container_layout.addWidget(self.command_header_container_widget)
self._output_container_layout.addWidget(QHLine())
self._output_container_layout.addWidget(self._output_mode_container_widget)
self._output_container_layout.addWidget(self._output_range_container_widget)
- self._output_container_layout.addWidget(self._output_trigger_bool_container_widget)
+ self._output_container_layout.addWidget(
+ self._output_trigger_bool_container_widget
+ )
self._output_container_layout.addWidget(self.status_text_widget)
self._output_container_layout.addStretch()
-
-
- #self.main_layout.addWidget(self._toolbar_container_widget)
+ # self.main_layout.addWidget(self._toolbar_container_widget)
warning_color = gremlin.ui.ui_common.Color.warningColor()
- warning_widget = gremlin.ui.ui_common.QIconLabel("ph.shield-warning-fill",use_qta=True,icon_color=QtGui.QColor(warning_color),text="This function is experimental and still in development, and not necessary feature complete", use_wrap=False)
+ warning_widget = gremlin.ui.ui_common.QIconLabel(
+ "ph.shield-warning-fill",
+ use_qta=True,
+ icon_color=QtGui.QColor(warning_color),
+ text="This function is experimental and still in development, and not necessary feature complete",
+ use_wrap=False,
+ )
self.main_layout.addWidget(warning_widget)
self.main_layout.addWidget(self._mode_container_widget)
@@ -2994,7 +3103,6 @@ def _create_ui(self):
self.main_layout.addWidget(self._output_container_widget)
self.main_layout.addWidget(self._button_mode_container_widget)
self.main_layout.addWidget(self.container_repeater_widget)
-
# hook the inputs and profile
el = gremlin.event_handler.EventListener()
@@ -3004,12 +3112,11 @@ def _create_ui(self):
el.profile_start.connect(self._profile_start)
el.profile_stop.connect(self._profile_stop)
# refresh the UI on profile mode changes
- el.edit_mode_changed.connect(self._populate_ui)
+ el.edit_mode_changed.connect(self._populate_ui)
# update from ui
self._update_ui()
-
@QtCore.Slot()
def _set_command_range(self):
widget = self.sender()
@@ -3030,7 +3137,6 @@ def _update_curve_icon(self):
self.curve_button_widget.setIcon(self.curve_icon_inactive)
self.curve_clear_widget.setEnabled(False)
-
@QtCore.Slot()
def _command_type_changed(self):
widget = self.sender()
@@ -3047,7 +3153,6 @@ def _is_release_command_changed(self, checked):
self.action_data.is_release_command = checked
self._update_visible()
-
@QtCore.Slot()
def _auto_repeat_delay_changed(self):
self.action_data.auto_repeat_interval = self._autorepeat_delay_widget.value()
@@ -3057,11 +3162,10 @@ def _command_mode_changed_cb(self):
widget = self.sender()
mode = widget.data
self.action_data.command_mode = mode
- SimconnectOptions().last_command_mode = mode # remember for next time
-
-
+ SimconnectOptions().last_command_mode = mode # remember for next time
+
self._update_visible()
-
+
@QtCore.Slot()
def _lvar_selected_cb(self):
lvar = self._lvar_selector_widget.currentText()
@@ -3070,27 +3174,37 @@ def _lvar_selected_cb(self):
@QtCore.Slot()
def _expression_changed_cb(self):
- ''' expression changed '''
+ """expression changed"""
self.action_data.command = self._calculator_entry_widget.toPlainText()
- warning_visible = self.action_data.command_mode == SimConnectCommandMode.CalculatorParam and not self.action_data._is_value_command()
+ warning_visible = (
+ self.action_data.command_mode == SimConnectCommandMode.CalculatorParam
+ and not self.action_data._is_value_command()
+ )
self._warning_widget.setVisible(warning_visible)
@QtCore.Slot()
def _expression_release_changed_cb(self):
- ''' expression changed '''
- self.action_data.command_release = self._calculator_release_entry_widget.toPlainText()
- warning_visible = self.action_data.command_mode == SimConnectCommandMode.CalculatorParam and not self.action_data._is_value_command()
+ """expression changed"""
+ self.action_data.command_release = (
+ self._calculator_release_entry_widget.toPlainText()
+ )
+ warning_visible = (
+ self.action_data.command_mode == SimConnectCommandMode.CalculatorParam
+ and not self.action_data._is_value_command()
+ )
self._warning_widget.setVisible(warning_visible)
- QtCore.Slot()
+ QtCore.Slot()
+
def _reset_range(self):
with QtCore.QSignalBlocker(self.action_data.events):
self.action_data.output_min_range = -1.0
self.action_data.output_max_range = 1.0
- self._value_widget.setRange(-1,1)
+ self._value_widget.setRange(-1, 1)
self._update_repeater()
-
+
QtCore.Slot(object)
+
def _value_changed(self, data):
# normalized value (-1 to +1)
verbose = gremlin.config.Configuration().verbose
@@ -3098,41 +3212,46 @@ def _value_changed(self, data):
min_value, max_value = data
self.action_data.normalized_min_range = min_value
self.action_data.normalized_max_range = max_value
- if verbose: syslog.info (f"Range Value (normalized): {min_value:0.3f} {max_value:0.3f}")
+ if verbose:
+ syslog.info(
+ f"Range Value (normalized): {min_value:0.3f} {max_value:0.3f}"
+ )
else:
# single mode
min_value = data
max_value = self.action_data.limit_max_range
self.action_data.normalized_min_range = min_value
- if verbose: syslog.info(f"Single value (normalized): {min_value:0.3f}")
-
+ if verbose:
+ syslog.info(f"Single value (normalized): {min_value:0.3f}")
+
self._update_repeater()
-
-
+
QtCore.Slot(object)
+
def _range_changed(self, data):
verbose = gremlin.config.Configuration().verbose
if self._value_widget.isRange:
min_cmd, max_cmd = data
self.action_data.command_min_range = min_cmd
self.action_data.command_max_range = max_cmd
- if verbose: syslog.info (f"Set Range Value (command): {min_cmd:0.3f} {max_cmd:0.3f}")
+ if verbose:
+ syslog.info(f"Set Range Value (command): {min_cmd:0.3f} {max_cmd:0.3f}")
else:
# single mode
value = data
self.action_data.command_min_range = value
- if verbose: syslog.info(f"Set single value (command): {value:0.3f}")
-
- self._update_repeater()
+ if verbose:
+ syslog.info(f"Set single value (command): {value:0.3f}")
+ self._update_repeater()
QtCore.Slot()
+
def _inverted_changed(self):
self.action_data.inverted = self._value_widget.inverted
self._update_repeater()
-
@QtCore.Slot(object)
def _command_range_changed(self, data):
if self._value_widget.isRange:
@@ -3142,13 +3261,19 @@ def _command_range_changed(self, data):
self._update_repeater()
QtCore.Slot()
+
def _curve_button_cb(self):
if not self.action_data.curve_data:
curve_data = gremlin.curve_handler.AxisCurveData()
- curve_data.calibration = gremlin.ui.axis_calibration.CalibrationManager().getCalibration(self.action_data.hardware_device_guid, self.action_data.hardware_input_id)
+ curve_data.calibration = (
+ gremlin.ui.axis_calibration.CalibrationManager().getCalibration(
+ self.action_data.hardware_device_guid,
+ self.action_data.hardware_input_id,
+ )
+ )
curve_data.curve_update()
self.action_data.curve_data = curve_data
-
+
dialog = gremlin.curve_handler.AxisCurveDialog(self.action_data.curve_data)
gremlin.util.centerDialog(dialog, dialog.width(), dialog.height())
self.curve_update_handler = dialog.curve_update_handler
@@ -3162,17 +3287,16 @@ def _curve_button_cb(self):
self._update_curve_icon()
-
-
QtCore.Slot()
+
def _curve_delete_button_cb(self):
- ''' removes the curve data '''
+ """removes the curve data"""
message_box = QtWidgets.QMessageBox()
message_box.setText("Confirmation")
message_box.setInformativeText("Delete curve data for this output?")
message_box.setStandardButtons(
- QtWidgets.QMessageBox.StandardButton.Ok |
- QtWidgets.QMessageBox.StandardButton.Cancel
+ QtWidgets.QMessageBox.StandardButton.Ok
+ | QtWidgets.QMessageBox.StandardButton.Cancel
)
message_box.setDefaultButton(QtWidgets.QMessageBox.StandardButton.Ok)
gremlin.util.centerDialog(message_box)
@@ -3184,67 +3308,59 @@ def _curve_delete_button_cb(self):
gremlin.util.pushCursor()
if response == QtWidgets.QMessageBox.StandardButton.Ok:
self.action_data.curve_data = None
- self._update_curve_icon()
-
+ self._update_curve_icon()
def _profile_start(self):
- ''' called when the profile starts '''
+ """called when the profile starts"""
el = gremlin.event_handler.EventListener()
el.custom_joystick_event.disconnect(self._joystick_event_handler)
if not self.chained_input:
el.joystick_event.disconnect(self._joystick_event_handler)
-
+
def _profile_stop(self):
- ''' called when the profile stops'''
+ """called when the profile stops"""
self._update_axis_widget()
el = gremlin.event_handler.EventListener()
el.custom_joystick_event.connect(self._joystick_event_handler)
if not self.chained_input:
el.joystick_event.connect(self._joystick_event_handler)
-
def _joystick_event_handler(self, event):
- ''' handles joystick events in the UI (functor handles the output when profile is running) so we see the output at design time '''
+ """handles joystick events in the UI (functor handles the output when profile is running) so we see the output at design time"""
if gremlin.shared_state.is_running:
- return
+ return
if not event.is_axis:
- return
-
+ return
+
value = None
-
+
if event.device_guid != self.action_data.hardware_device_guid:
return
if event.identifier != self.action_data.hardware_input_id:
return
if event.is_custom:
value = event.value
-
- self._update_axis_widget(value)
-
+ self._update_axis_widget(value)
def _current_input_axis(self):
- ''' gets the current input axis value '''
- return gremlin.joystick_handling.get_curved_axis(self.action_data.hardware_device_guid,
- self.action_data.hardware_input_id)
+ """gets the current input axis value"""
+ return gremlin.joystick_handling.get_curved_axis(
+ self.action_data.hardware_device_guid, self.action_data.hardware_input_id
+ )
-
+ def _update_axis_widget(self, value: float = None):
+ """updates the axis output repeater with the value
-
- def _update_axis_widget(self, value : float = None):
- ''' updates the axis output repeater with the value
-
:param value: the floating point normalized input value, if None uses the cached value -1 to +1 range
-
- '''
- # always read the current input as the value could be from another device for merged inputs
+ """
+ # always read the current input as the value could be from another device for merged inputs
verbose = gremlin.config.Configuration().verbose_mode_simconnect
-
+
if self.input_type == InputType.JoystickAxis:
-
raw_value = self.action_data.get_raw_axis_value()
if value is None:
# filter and merge the data
@@ -3255,16 +3371,15 @@ def _update_axis_widget(self, value : float = None):
filtered_value = self.action_data.get_local_curve_value(filtered_value)
normalized = filtered_value
value = filtered_value
-
# if the output is ranged apply that range
-
+
if self.action_data.mode == SimConnectActionMode.Ranged:
# scale up to apply the block range
filtered_value = self.action_data.get_filtered_axis_value(value)
- raw = filtered_value # -1 to +1
- normalized = raw
+ raw = filtered_value # -1 to +1
+ normalized = raw
# apply local curve to the range -1 to + 1
curved = self.action_data.get_local_curve_value(normalized)
@@ -3272,16 +3387,32 @@ def _update_axis_widget(self, value : float = None):
# compute the output value based on the range setup
min_range = self.action_data.command_min_range
max_range = self.action_data.command_max_range
- percent = gremlin.util.scale_to_range(curved, target_min = 0, target_max = 100)
- output_value = gremlin.util.scale_to_range(curved, target_min = min_range, target_max = max_range, invert = self.action_data.inverted)
-
- if verbose: syslog.info(f"SIMCONNECT UI: {value:0.3f} output range: [{self.action_data.output_min_range:0.3f}, {self.action_data.output_max_range:0.3f}] normalized range: [{self.action_data.normalized_min_range:0.4f}, {self.action_data.normalized_max_range:0.4f}] normalized {normalized:0.4f} curved {curved:0.3f} percent: {percent:0.3f} output: {output_value}")
+ percent = gremlin.util.scale_to_range(
+ curved, target_min=0, target_max=100
+ )
+ output_value = gremlin.util.scale_to_range(
+ curved,
+ target_min=min_range,
+ target_max=max_range,
+ invert=self.action_data.inverted,
+ )
+
+ if verbose:
+ syslog.info(
+ f"SIMCONNECT UI: {value:0.3f} output range: [{self.action_data.output_min_range:0.3f}, {self.action_data.output_max_range:0.3f}] normalized range: [{self.action_data.normalized_min_range:0.4f}, {self.action_data.normalized_max_range:0.4f}] normalized {normalized:0.4f} curved {curved:0.3f} percent: {percent:0.3f} output: {output_value}"
+ )
else:
output_value = value
- percent = gremlin.util.scale_to_range(value, source_min = self.action_data.output_min_range, source_max = self.action_data.output_max_range, target_min=0, target_max=100) # convert to percent
+ percent = gremlin.util.scale_to_range(
+ value,
+ source_min=self.action_data.output_min_range,
+ source_max=self.action_data.output_max_range,
+ target_min=0,
+ target_max=100,
+ ) # convert to percent
if self.action_data.curve_data is not None:
- # curve the data
+ # curve the data
self._axis_repeater_widget.show_curved = True
else:
self._axis_repeater_widget.show_curved = False
@@ -3295,7 +3426,10 @@ def _update_axis_widget(self, value : float = None):
self._calculator_value_widget.setVisible(False)
else:
# calculator mode
- if self.action_data.mode in (SimConnectActionMode.Ranged, SimConnectActionMode.SetValue):
+ if self.action_data.mode in (
+ SimConnectActionMode.Ranged,
+ SimConnectActionMode.SetValue,
+ ):
command = self.action_data._get_value_command(output_value)
else:
command = self.action_data.command
@@ -3307,46 +3441,46 @@ def _update_axis_widget(self, value : float = None):
# update curve dialog if it's open
self.curve_update_handler(normalized)
-
-
@QtCore.Slot()
def _show_options_dialog_cb(self):
- ''' displays the simconnect options dialog'''
+ """displays the simconnect options dialog"""
from action_plugins.map_to_simconnect.SimConnectManager import SimConnectManager
+
profile = gremlin.shared_state.current_profile
profile_file = profile.profile_file
if not profile_file or not os.path.isfile(profile_file):
- gremlin.ui.ui_common.MessageBox(prompt="Please save the current profile before accessing Simconnect options.")
- return
+ gremlin.ui.ui_common.MessageBox(
+ prompt="Please save the current profile before accessing Simconnect options."
+ )
+ return
dialog = SimconnectOptionsUi(SimConnectManager().simconnect)
dialog.exec()
@QtCore.Slot()
def _refresh_lvar_cb(self):
- ''' refreshes the list of lvars from the sim '''
+ """refreshes the list of lvars from the sim"""
self.manager.refreshLvars()
-
@QtCore.Slot(object)
def _lvars_udpated_cb(self, lvars):
- ''' called when new LVARs are received '''
+ """called when new LVARs are received"""
with QtCore.QSignalBlocker(self._lvar_selector_widget):
self._lvar_selector_widget.clear()
self._lvar_selector_widget.addItems(self.manager.lvars)
-
-
def _output_normalized_value_changed_cb(self):
normalized = self._output_value_normalized_widget.value()
if normalized:
- scaled = gremlin.util.scale_to_range(normalized, target_min = -16368, target_max = 16367)
+ scaled = gremlin.util.scale_to_range(
+ normalized, target_min=-16368, target_max=16367
+ )
value = int(scaled)
with QtCore.QSignalBlocker(self._output_value_widget):
self._output_value_widget.setValue(value)
self._update_output_value(normalized)
def _output_value_changed_cb(self):
- ''' occurs when the output value has changed '''
+ """occurs when the output value has changed"""
value = self._output_value_widget.value()
if value is not None:
normalized = gremlin.util.scale_to_range(value, -16368, 16367)
@@ -3354,20 +3488,17 @@ def _output_value_changed_cb(self):
self._output_value_normalized_widget.setValue(normalized)
self._update_output_value(normalized)
-
def _update_output_value(self, value):
# store to profile
self.action_data.value = value
- percent = gremlin.util.scale_to_range(value, target_min = 0.0, target_max = 100.0)
+ percent = gremlin.util.scale_to_range(value, target_min=0.0, target_max=100.0)
with QtCore.QSignalBlocker(self._output_value_percent_widget):
self._output_value_percent_widget.setValue(percent)
-
-
-
-
def _update_repeater(self):
- value = gremlin.joystick_handling.get_axis(self.action_data.hardware_device_guid, self.action_data.hardware_input_id)
+ value = gremlin.joystick_handling.get_axis(
+ self.action_data.hardware_device_guid, self.action_data.hardware_input_id
+ )
self._update_axis_widget(value)
def _update_max_range(self, value):
@@ -3375,7 +3506,7 @@ def _update_max_range(self, value):
assert value >= -1.0 and value <= 1.0
self.action_data.output_max_range = value
- percent = gremlin.util.scale_to_range(value, target_min = 0.0, target_max = 100.0)
+ percent = gremlin.util.scale_to_range(value, target_min=0.0, target_max=100.0)
with QtCore.QSignalBlocker(self._output_max_percent_range_widget):
self._output_max_percent_range_widget.setValue(percent)
@@ -3384,7 +3515,7 @@ def _output_invert_axis_cb(self, checked):
self.action_data.inverted = checked
self._axis_repeater_widget.setReverse(checked)
# update the repeater
-
+
@QtCore.Slot(bool)
def _trigger_on_release_cb(self, checked):
self.action_data.trigger_on_release = checked
@@ -3392,14 +3523,14 @@ def _trigger_on_release_cb(self, checked):
@QtCore.Slot(bool)
def _trigger_on_press_cb(self, checked):
self.action_data.trigger_on_press = checked
-
def _command_changed_cb(self, index):
- ''' called when selected command changes '''
+ """called when selected command changes"""
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_simconnect
command = self._command_selector_widget.currentText()
- if verbose: syslog.info(f"Command changed to: {command}")
+ if verbose:
+ syslog.info(f"Command changed to: {command}")
if command:
self.action_data.command = command
self._update_ui()
@@ -3409,20 +3540,20 @@ def _lvar_changed_cb(self):
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_simconnect
command = self._lvar_command_widget.text()
- if verbose: syslog.info(f"Command changed to: {command}")
+ if verbose:
+ syslog.info(f"Command changed to: {command}")
self.action_data.command = command
-
-
def _update_ui(self):
- ''' updates the UI with a data block '''
+ """updates the UI with a data block"""
with QtCore.QSignalBlocker(self._trigger_on_release_widget):
- self._trigger_on_release_widget.setChecked(self.action_data.trigger_on_release)
+ self._trigger_on_release_widget.setChecked(
+ self.action_data.trigger_on_release
+ )
with QtCore.QSignalBlocker(self._trigger_on_press_widget):
self._trigger_on_press_widget.setChecked(self.action_data.trigger_on_press)
-
# enabled = self.action_data._command_type == SimConnectCommandType.SimVar
# self._action_selector_widget.setEnabled(enabled)
# self._output_mode_container_widget.setEnabled(enabled)
@@ -3434,34 +3565,20 @@ def _update_ui(self):
# self.action_data.command_min_range = block.command_min_range
# self.action_data.command_max_range = block.command_max_range
self.description_text_widget.setText(block.description)
-
+
else:
-
self.description_text_widget.setText("No description found")
-
-
-
output_mode = self.action_data.mode
- min_range = self.action_data.output_min_range
- max_range = self.action_data.output_max_range
- min_command_range = self.action_data.command_min_range
- max_command_range = self.action_data.command_max_range
-
value = self.action_data.value
- inverted = self.action_data.inverted
trigger_mode = self.action_data.trigger_mode
-
-
-
-
-
-
# calculator mode
with QtCore.QSignalBlocker(self._type_is_calculator_widget):
- self._type_is_calculator_widget.setChecked(self.action_data._command_type == SimConnectCommandType.Calculator)
+ self._type_is_calculator_widget.setChecked(
+ self.action_data._command_type == SimConnectCommandType.Calculator
+ )
# settable flag
with QtCore.QSignalBlocker(self._type_is_settable_widget):
@@ -3469,16 +3586,12 @@ def _update_ui(self):
with QtCore.QSignalBlocker(self._type_datatype_widget):
self._type_datatype_widget.setText(self.action_data.data_type)
-
+
with QtCore.QSignalBlocker(self._type_units_widget):
self._type_units_widget.setText(self.action_data.units)
-
-
match output_mode:
-
case SimConnectActionMode.Ranged:
-
with QtCore.QSignalBlocker(self._output_mode_ranged_widget):
self._output_mode_ranged_widget.setChecked(True)
@@ -3486,24 +3599,20 @@ def _update_ui(self):
self._value_widget.isRange = True
self._update_repeater()
-
case SimConnectActionMode.SetValue:
with QtCore.QSignalBlocker(self._output_mode_set_value_widget):
self._output_mode_set_value_widget.setChecked(True)
-
+
with QtCore.QSignalBlocker(self._value_widget):
self._value_widget.isRange = False
self._update_repeater()
-
-
case SimConnectActionMode.Trigger:
-
with QtCore.QSignalBlocker(self._output_mode_trigger_widget):
self._output_mode_trigger_widget.setChecked(True)
self.action_data.trigger_mode = trigger_mode
-
+
# trigger mode options
match trigger_mode:
case SimConnectTriggerMode.NotSet:
@@ -3522,17 +3631,14 @@ def _update_ui(self):
with QtCore.QSignalBlocker(self._output_trigger_bool_on_widget):
self._output_trigger_bool_noop_widget.setChecked(True)
case SimConnectTriggerMode.InputValue:
- with QtCore.QSignalBlocker(self._output_trigger_bool_input_value_widget):
+ with QtCore.QSignalBlocker(
+ self._output_trigger_bool_input_value_widget
+ ):
self._output_trigger_bool_input_value_widget.setChecked(True)
-
-
-
-
-
input_desc = ""
input_type = self.action_data.input_type
-
+
if input_type == InputType.JoystickAxis:
input_desc = "axis"
elif input_type in (InputType.JoystickButton, InputType.VirtualButton):
@@ -3544,7 +3650,6 @@ def _update_ui(self):
elif input_type in (InputType.Midi, InputType.OpenSoundControl):
input_desc = "button or slider"
-
match output_mode:
case SimConnectActionMode.Ranged:
desc = f"Maps an input {input_desc} to a SimConnect ranged event, such as an axis"
@@ -3560,7 +3665,7 @@ def _update_ui(self):
# command description
with QtCore.QSignalBlocker(self.command_text_widget):
self.command_text_widget.setText(self.action_data.command)
-
+
if input_type == InputType.JoystickAxis:
# input drives the outputs
self._output_mode_trigger_widget.setVisible(False)
@@ -3573,11 +3678,13 @@ def _update_ui(self):
self._output_mode_ranged_widget.setVisible(False)
self._trigger_on_release_widget.setVisible(True)
-
-
self._output_container_widget.setVisible(True)
self._output_mode_readonly_widget.setVisible(self.action_data.is_readonly)
- self.output_readonly_status_widget.setText("Block: read/only" if self.action_data.is_readonly else "Block: read/write")
+ self.output_readonly_status_widget.setText(
+ "Block: read/only"
+ if self.action_data.is_readonly
+ else "Block: read/write"
+ )
# display range information if the command is a ranged command
self._output_range_container_widget.setVisible(self.action_data.is_ranged)
@@ -3586,39 +3693,40 @@ def _update_ui(self):
eh = SimConnectEventHandler()
eh.range_changed.connect(self._range_changed_cb)
-
-
# update UI based on block information ``
self._output_data_type_label_widget.setText(self.action_data.data_type)
-
+
self._update_visible()
if not self.action_data.command:
-
# clear the data
self._output_container_widget.setVisible(False)
self.status_text_widget.setText("Please select a command")
if self.action_data.input_type == InputType.JoystickAxis:
- value = gremlin.joystick_handling.get_axis(self.action_data.hardware_device_guid, self.action_data.hardware_input_id)
+ value = gremlin.joystick_handling.get_axis(
+ self.action_data.hardware_device_guid,
+ self.action_data.hardware_input_id,
+ )
self._update_axis_widget(value)
-
+
# update visibility
self._update_visible()
-
def _update_visible(self):
- ''' updates the UI based on the output mode selected '''
+ """updates the UI based on the output mode selected"""
mode = self.action_data.command_mode
calc_visible = mode != SimConnectCommandMode.Simvar
simvar_visible = not calc_visible
- warning_visible = mode == SimConnectCommandMode.CalculatorParam and not self.action_data._is_value_command()
+ warning_visible = (
+ mode == SimConnectCommandMode.CalculatorParam
+ and not self.action_data._is_value_command()
+ )
block_visible = mode == SimConnectCommandMode.Simvar
-
release_command_visible = self.action_data.is_release_command
-
+
lvar_lookup_visible = calc_visible
if self.action_data.command_type == SimConnectCommandType.SimVar:
# known simvar (block) mode
@@ -3635,97 +3743,95 @@ def _update_visible(self):
self._lvar_lookup_container_widget.setVisible(lvar_lookup_visible)
self._calculator_container_widget.setVisible(calc_visible)
- self._type_container_widget.setVisible(False) # disable for now as it doesn't serve a value until we have an edit / entry mode
+ self._type_container_widget.setVisible(
+ False
+ ) # disable for now as it doesn't serve a value until we have an edit / entry mode
self._command_container_widget.setVisible(simvar_visible)
- self._calculator_release_container_widget.setVisible(release_command_visible and calc_visible)
-
-
- #self._button_mode_container_widget.setVisible(simvar_visible) # always visible
+ self._calculator_release_container_widget.setVisible(
+ release_command_visible and calc_visible
+ )
+
+ # self._button_mode_container_widget.setVisible(simvar_visible) # always visible
input_type = self.action_data.input_type
repeater_visible = False
output_mode = self.action_data.mode
trigger_visible = output_mode == SimConnectActionMode.Trigger
if input_type == InputType.JoystickAxis:
- range_visible = output_mode in (SimConnectActionMode.Ranged, SimConnectActionMode.SetValue) or mode == SimConnectCommandMode.CalculatorParam
+ range_visible = (
+ output_mode
+ in (SimConnectActionMode.Ranged, SimConnectActionMode.SetValue)
+ or mode == SimConnectCommandMode.CalculatorParam
+ )
repeater_visible = True
-
+
else:
# momentary
range_visible = self.action_data.command_type == SimConnectCommandType.LVar
-
+
range_visible = True
self._output_container_widget.setVisible(simvar_visible or range_visible)
self._output_range_container_widget.setVisible(simvar_visible or range_visible)
self._output_trigger_bool_container_widget.setVisible(trigger_visible)
-
self.container_repeater_widget.setVisible(repeater_visible)
output_mode_enabled = not self.action_data.is_readonly
-
+
self._output_mode_container_widget.setVisible(output_mode_enabled)
self._output_mode_set_value_widget.setEnabled(output_mode_enabled)
self._output_mode_trigger_widget.setEnabled(output_mode_enabled)
self._output_data_type_label_widget.setText(self.action_data.data_type)
- self.output_readonly_status_widget.setText("(command is Read/Only)" if self.action_data.is_readonly else '')
+ self.output_readonly_status_widget.setText(
+ "(command is Read/Only)" if self.action_data.is_readonly else ""
+ )
self.command_header_container_widget.setVisible(block_visible)
self.output_readonly_status_widget.setVisible(block_visible)
-
self._warning_widget.setVisible(warning_visible)
-
-
@QtCore.Slot(bool)
def _trigger_noop_changed_cb(self, checked):
if checked:
self.action_data.trigger_mode = SimConnectTriggerMode.NoOp
-
@QtCore.Slot(bool)
def _trigger_toggle_changed_cb(self, checked):
if checked:
self.action_data.trigger_mode = SimConnectTriggerMode.Toggle
-
@QtCore.Slot(bool)
def _trigger_turnon_cb(self, checked):
if checked:
self.action_data.trigger_mode = SimConnectTriggerMode.TurnOn
-
@QtCore.Slot(bool)
def _trigger_turnoff_cb(self, checked):
if checked:
self.action_data.trigger_mode = SimConnectTriggerMode.TurnOff
-
@QtCore.Slot(bool)
def _trigger_input_value_cb(self, checked):
if checked:
self.action_data.trigger_mode = SimConnectTriggerMode.InputValue
-
-
@QtCore.Slot(object, object)
- def _range_changed_cb(self, block, event : RangeEvent):
- ''' called when range information changes on the current simconnect command block '''
+ def _range_changed_cb(self, block, event: RangeEvent):
+ """called when range information changes on the current simconnect command block"""
if block == self.action_data.block:
self._output_min_range_widget.setValue(event.min)
self._output_max_range_widget.setValue(event.max)
self._output_min_range_widget.setValue(event.min_custom)
self._output_max_range_widget.setValue(event.max_custom)
-
@QtCore.Slot(bool)
def _mode_ranged_cb(self, value):
if value:
self.action_data.output_mode = SimConnectActionMode.Ranged
self.action_data.mode = SimConnectActionMode.Ranged
- self._value_widget.isRange = True # enable ranged mode
+ self._value_widget.isRange = True # enable ranged mode
self._update_visible()
self._update_repeater()
@@ -3735,10 +3841,10 @@ def _mode_value_cb(self, value):
if self.action_data.block:
self.action_data.block.output_mode = SimConnectActionMode.SetValue
self.action_data.mode = SimConnectActionMode.SetValue
- self._value_widget.isRange = False # disable ranged mode
+ self._value_widget.isRange = False # disable ranged mode
self._update_visible()
self._update_axis_widget(self._value_widget.value())
-
+
@QtCore.Slot(bool)
def _mode_trigger_cb(self, value):
if value:
@@ -3748,44 +3854,42 @@ def _mode_trigger_cb(self, value):
self._update_visible()
def _readonly_cb(self):
- block : SimConnectBlock
+ block: SimConnectBlock
block = self.action_data.block
-
+
readonly = block is not None and block.is_readonly
checked = self.output_readonly_status_widget.isChecked()
if readonly != checked:
with QtCore.QSignalBlocker(self.output_readonly_status_widget):
self.output_readonly_status_widget.setChecked(readonly)
-
+
self.action_data.is_readonly = readonly
def _populate_ui(self):
"""Populates the UI components."""
-
+
command = self._command_selector_widget.currentText()
if self.action_data.command != command:
with QtCore.QSignalBlocker(self._command_selector_widget):
index = self._command_selector_widget.findText(self.action_data.command)
self._command_selector_widget.setCurrentIndex(index)
-
-class MapToSimConnectFunctor(gremlin.base_profile.AbstractContainerActionFunctor):
+class MapToSimConnectFunctor(gremlin.base_profile.AbstractContainerActionFunctor):
# macro_manager = gremlin.macro.MacroManager()
- def __init__(self, action, parent = None):
-
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
- self.action_data : MapToSimConnect = action
- self.command = action.command # the command to execute
- self.value = action.value # the value to send (None if no data to send)
- self.manager : SimConnectManager = SimConnectManager()
- self.monitor : SimconnectMonitor = SimconnectMonitor()
+ self.action_data: MapToSimConnect = action
+ self.command = action.command # the command to execute
+ self.value = action.value # the value to send (None if no data to send)
+ self.manager: SimConnectManager = SimConnectManager()
+ self.monitor: SimconnectMonitor = SimconnectMonitor()
self.valid = False
self._significant = gremlin.input_devices.JoystickInputSignificant()
self._profile_started = False
-
+
self.reconnect_timeout = 5
self.last_reconnect_time = None
@@ -3794,41 +3898,34 @@ def __init__(self, action, parent = None):
syslog.error(f"Simconnect: invalid block: {self.command}")
self.valid = False
return
-
+
self._auto_repeat_thread = None
self._auto_repeat_event = threading.Event()
- self._auto_repeat_thread = threading.Thread(target = self._auto_repeat_command, daemon=True)
-
+ self._auto_repeat_thread = threading.Thread(
+ target=self._auto_repeat_command, daemon=True
+ )
+
self.valid = True
-
def profile_start(self):
- ''' occurs when the profile starts '''
-
+ """occurs when the profile starts"""
if not self._profile_started:
-
self._profile_started = True
self.reconnect_timeout = 5
self.last_reconnect_time = None
-
+
self.manager.activate()
# update the loaded aircraft so this sets the profile mode if needed
name = self.manager.get_loaded_aircraft()
if name:
self.monitor.changeModeForAicraft(name)
- else:
+ else:
self.manager.request_loaded_aircraft()
-
-
-
-
-
-
def profile_stop(self):
- ''' occurs wen the profile stops'''
+ """occurs wen the profile stops"""
if self._profile_started:
self._profile_started = False
self._auto_repeat_event.set()
@@ -3836,18 +3933,17 @@ def profile_stop(self):
# clear any running autorepeat
if self._auto_repeat_thread.is_alive():
self._auto_repeat_thread.join()
-
+
# unregister any prior requests
self.manager.clearRequests()
eh = SimConnectEventHandler()
eh.request_disconnect.emit()
-
-
-
- def process_event(self, event, action_value : gremlin.actions.Value, extra_data = None):
- ''' runs when a joystick event occurs like a button press or axis movement when a profile is running '''
+ def process_event(
+ self, event, action_value: gremlin.actions.Value, extra_data=None
+ ):
+ """runs when a joystick event occurs like a button press or axis movement when a profile is running"""
if not gremlin.shared_state.is_running or gremlin.shared_state.abort:
return
@@ -3858,67 +3954,80 @@ def process_event(self, event, action_value : gremlin.actions.Value, extra_data
if not self.manager.is_running:
# sim is not running - attempt to reconnect every few seconds
syslog.info("SIMCONNECT: manager not running - connecting....")
- if self.last_reconnect_time is None or self.last_reconnect_time + self.reconnect_timeout > time.time():
+ if (
+ self.last_reconnect_time is None
+ or self.last_reconnect_time + self.reconnect_timeout > time.time()
+ ):
self.last_reconnect_time = time.time()
eh = SimConnectEventHandler()
eh.request_connect.emit()
return True
- return self._process_event(event, action_value)
-
+ return self._process_event(event, action_value)
-
- def _process_event(self, event, action_value : gremlin.actions.Value):
- ''' handles default input data '''
+ def _process_event(self, event, action_value: gremlin.actions.Value):
+ """handles default input data"""
# execute the nested functors for this action
super().process_event(event, action_value)
config = gremlin.config.Configuration()
verbose = config.verbose_mode_simconnect
- verbose_details = False # config.verbose_mode_details
- #verbose = True
- manager : SimConnectManager = self.manager
+ verbose_details = False # config.verbose_mode_details
+ # verbose = True
+ manager: SimConnectManager = self.manager
# syslog = logging.getLogger("system")
-
if not self.manager.is_running:
# sim is not running
- syslog.warning("Simconnect Functor: event ignored, simconnect not connected")
+ syslog.warning(
+ "Simconnect Functor: event ignored, simconnect not connected"
+ )
return
-
if not self.manager.is_bridge_alive:
# sim is not running
- syslog.warning("Simconnect Functor: event ignored, simconnect bridge not connected")
+ syslog.warning(
+ "Simconnect Functor: event ignored, simconnect bridge not connected"
+ )
return
-
+
block = self.action_data.block
output_mode = self.action_data.mode
-
+
command_mode = self.action_data.command_mode
command_type = self.action_data.command_type
- trigger = self.action_data.trigger_on_press and event.is_pressed or self.action_data.trigger_on_release and not event.is_pressed
+ trigger = (
+ self.action_data.trigger_on_press
+ and event.is_pressed
+ or self.action_data.trigger_on_release
+ and not event.is_pressed
+ )
- if command_mode in (SimConnectCommandMode.Calculator, SimConnectCommandMode.CalculatorParam):
+ if command_mode in (
+ SimConnectCommandMode.Calculator,
+ SimConnectCommandMode.CalculatorParam,
+ ):
# RPN modes
if event.is_axis:
-
if not self.action_data.command:
# nothing to calculate
return True
-
+
process_input = self._significant.should_process_axis(event, 0.01)
if process_input:
-
-
command = None
if command_mode == SimConnectCommandMode.CalculatorParam:
if self.action_data.mode == SimConnectActionMode.Ranged:
# compute the output value based on the range setup
min_range = self.action_data.command_min_range
max_range = self.action_data.command_max_range
- value = gremlin.util.scale_to_range(event.value, target_min = min_range, target_max = max_range, invert = self.action_data.inverted)
+ value = gremlin.util.scale_to_range(
+ event.value,
+ target_min=min_range,
+ target_max=max_range,
+ invert=self.action_data.inverted,
+ )
command = self.action_data._get_value_command(value)
elif self.action_data.mode == SimConnectActionMode.SetValue:
value = self.action_data.value
@@ -3928,9 +4037,9 @@ def _process_event(self, event, action_value : gremlin.actions.Value):
elif command_mode == SimConnectCommandMode.Calculator:
command = self.action_data.command
if command:
- self.manager.calculate(command) # run RPN script
+ self.manager.calculate(command) # run RPN script
return True
-
+
else:
# non axis command
if self.action_data.auto_repeat:
@@ -3938,77 +4047,99 @@ def _process_event(self, event, action_value : gremlin.actions.Value):
# calculator expression
if not self._auto_repeat_thread.is_alive():
# command auto repeats while pressed - not started
- if verbose_details: syslog.info("auto repeat start")
+ if verbose_details:
+ syslog.info("auto repeat start")
self._auto_repeat_thread.start()
return True
-
+
if trigger:
# regular calculate
command = self.action_data.command
if command:
- if verbose_details: syslog.info(f"Simconnect: calc: execute press command: {command}")
- manager.calculate(command) # run RPN script
+ if verbose_details:
+ syslog.info(
+ f"Simconnect: calc: execute press command: {command}"
+ )
+ manager.calculate(command) # run RPN script
else:
# release calculate auto repeat
if self.action_data.auto_repeat and self._auto_repeat_thread:
# released
- if verbose_details: syslog.info("auto repeat stopping...")
+ if verbose_details:
+ syslog.info("auto repeat stopping...")
self._auto_repeat_event.set()
self._auto_repeat_thread.join()
- if verbose_details: syslog.info("auto repeat stopped")
+ if verbose_details:
+ syslog.info("auto repeat stopped")
if self.action_data.is_release_command:
# execute release command
command = self.action_data.command_release
if command:
- if verbose_details: syslog.info(f"Simconnect: calc: execute release command: {command}")
- manager.calculate(command) # run RPN script
-
-
- else: # command_mode == SimConnectCommandMode.Simvar
+ if verbose_details:
+ syslog.info(
+ f"Simconnect: calc: execute release command: {command}"
+ )
+ manager.calculate(command) # run RPN script
+
+ else: # command_mode == SimConnectCommandMode.Simvar
# non calc modes
if command_type == SimConnectCommandType.SimVar:
if block is None:
- syslog.warning(f"SIMCONNECT: Error: Simvar Block not set")
- return True
+ syslog.warning("SIMCONNECT: Error: Simvar Block not set")
+ return True
if not block.valid:
# invalid command
- syslog.warning(f"SIMCONNECT: Error: invalid block: {block.command} type: {block.command_type}")
- return True
-
- if event.is_axis and output_mode in (SimConnectActionMode.Ranged, SimConnectActionMode.Gated):
+ syslog.warning(
+ f"SIMCONNECT: Error: invalid block: {block.command} type: {block.command_type}"
+ )
+ return True
+
+ if event.is_axis and output_mode in (
+ SimConnectActionMode.Ranged,
+ SimConnectActionMode.Gated,
+ ):
# value = self.action_data.get_filtered_axis_value(action_value.current)
# process input options and any merge and curve operation
- process_input = True # self._significant.should_process_axis(event, 0.001)
+ process_input = (
+ True # self._significant.should_process_axis(event, 0.001)
+ )
if process_input:
-
-
command = self.action_data.command
-
- filtered_value = self.action_data.get_filtered_axis_value(action_value.current)
+
+ filtered_value = self.action_data.get_filtered_axis_value(
+ action_value.current
+ )
action_value = gremlin.actions.Value(filtered_value)
- raw = filtered_value # -1 to +1
- normalized = raw
+ raw = filtered_value # -1 to +1
+ normalized = raw
# apply local curve to the range -1 to + 1
curved = self.action_data.get_local_curve_value(normalized)
-
# compute the output value based on the range setup
-
+
min_range = self.action_data.command_min_range
max_range = self.action_data.command_max_range
- output_value = gremlin.util.scale_to_range(curved, target_min = min_range, target_max = max_range, invert = self.action_data.inverted)
-
-
- if verbose:
- syslog.info(f"SIMCONNECT: send ({command_type.name}) (axis): {command} input: {action_value.current:0.3f} scaled: {normalized:0.3f} curved: {curved:0.3f} min: {self.action_data.output_min_range:0.3f} max: {self.action_data.output_max_range:0.3f} -> scaled: {output_value:0.3f}")
+ output_value = gremlin.util.scale_to_range(
+ curved,
+ target_min=min_range,
+ target_max=max_range,
+ invert=self.action_data.inverted,
+ )
+
+ if verbose:
+ syslog.info(
+ f"SIMCONNECT: send ({command_type.name}) (axis): {command} input: {action_value.current:0.3f} scaled: {normalized:0.3f} curved: {curved:0.3f} min: {self.action_data.output_min_range:0.3f} max: {self.action_data.output_max_range:0.3f} -> scaled: {output_value:0.3f}"
+ )
if command_type == SimConnectCommandType.LVar:
- request = manager.registerRequest(command, "number", settable = True)
+ request = manager.registerRequest(
+ command, "number", settable=True
+ )
request.value = output_value
request.transmit()
else:
@@ -4016,47 +4147,82 @@ def _process_event(self, event, action_value : gremlin.actions.Value):
elif output_mode == SimConnectActionMode.Trigger:
if not event.is_axis:
- value = 1 if self.action_data.trigger_mode != SimConnectTriggerMode.InputValue else action_value.current
- trigger = (self.action_data.trigger_on_press and event.is_pressed) or \
- self.action_data.trigger_on_release and not event.is_pressed
+ value = (
+ 1
+ if self.action_data.trigger_mode
+ != SimConnectTriggerMode.InputValue
+ else action_value.current
+ )
+ trigger = (
+ (self.action_data.trigger_on_press and event.is_pressed)
+ or self.action_data.trigger_on_release
+ and not event.is_pressed
+ )
if trigger:
if command_type == SimConnectCommandType.LVar:
- if verbose_details: syslog.info(f"SIMCONNECT: Trigger singleton {self.action_data.command}")
- request = manager.registerRequest(self.action_data.command, "number", settable = True)
+ if verbose_details:
+ syslog.info(
+ f"SIMCONNECT: Trigger singleton {self.action_data.command}"
+ )
+ request = manager.registerRequest(
+ self.action_data.command, "number", settable=True
+ )
request.value = value
request.transmit()
else:
- if verbose_details: syslog.info(f"SIMCONNECT: Trigger singleton {block.command}")
+ if verbose_details:
+ syslog.info(
+ f"SIMCONNECT: Trigger singleton {block.command}"
+ )
block.execute(value)
elif output_mode == SimConnectActionMode.SetValue:
- # set value mode
+ # set value mode
output_value = self.action_data.value
command = self.action_data.command
-
- trigger = (self.action_data.trigger_on_press and event.is_pressed) or \
- self.action_data.trigger_on_release and not event.is_pressed
+
+ trigger = (
+ (self.action_data.trigger_on_press and event.is_pressed)
+ or self.action_data.trigger_on_release
+ and not event.is_pressed
+ )
if trigger:
if command_type == SimConnectCommandType.LVar:
- if verbose: syslog.info(f"SIMCONNECT: send lvar fixed value (trigger): {command} {output_value:0.3f}")
- request = manager.registerRequest(self.action_data.command, "number", settable = True)
+ if verbose:
+ syslog.info(
+ f"SIMCONNECT: send lvar fixed value (trigger): {command} {output_value:0.3f}"
+ )
+ request = manager.registerRequest(
+ self.action_data.command, "number", settable=True
+ )
request.value = output_value
request.transmit()
else:
- if verbose: syslog.info(f"SIMCONNECT: send block: {block.command} fixed value: {output_value:0.3f}")
- block.execute(output_value)
-
+ if verbose:
+ syslog.info(
+ f"SIMCONNECT: send block: {block.command} fixed value: {output_value:0.3f}"
+ )
+ block.execute(output_value)
+
elif self.action_data.mode == SimConnectActionMode.Trigger:
- # trigger action
+ # trigger action
min_range = self.action_data.command_min_range
max_range = self.action_data.command_max_range
value = action_value.current
- output_value = gremlin.util.scale_to_range(value, target_min = min_range, target_max = max_range, invert = self.action_data.inverted)
- if verbose: syslog.info(f"SIMCONNECT: send block trigger: {block.command} input: {value:0.3f} min: {self.action_data.output_min_range:0.3f} max: {self.action_data.output_max_range:0.3f} -> scaled: {output_value:0.3f}")
+ output_value = gremlin.util.scale_to_range(
+ value,
+ target_min=min_range,
+ target_max=max_range,
+ invert=self.action_data.inverted,
+ )
+ if verbose:
+ syslog.info(
+ f"SIMCONNECT: send block trigger: {block.command} input: {value:0.3f} min: {self.action_data.output_min_range:0.3f} max: {self.action_data.output_max_range:0.3f} -> scaled: {output_value:0.3f}"
+ )
block.execute(output_value)
return True
-
+
def _auto_repeat_command(self):
verbose_details = False
while not self._auto_repeat_event.is_set():
@@ -4064,26 +4230,23 @@ def _auto_repeat_command(self):
time.sleep(self.action_data.auto_repeat_interval)
# syslog = logging.getLogger("system")
- if verbose_details: syslog.info("autorepeat: thread stop")
+ if verbose_details:
+ syslog.info("autorepeat: thread stop")
-
class MapToSimConnectHelper(QtCore.QObject):
- range_changed = QtCore.Signal() # indicates the range was updated
+ range_changed = QtCore.Signal() # indicates the range was updated
+
def __init__(self):
super().__init__()
-
class MapToSimConnect(gremlin.base_profile.AbstractContainerAction):
-
"""Action data for the map to keyboard action.
Map to keyboard presses and releases a set of keys in sync with another
physical input being pressed or released.
"""
-
-
name = "Map to SimConnect"
tag = "map-to-simconnect"
@@ -4119,8 +4282,9 @@ def __init__(self, parent):
self.events = MapToSimConnectHelper()
self._verbose = gremlin.config.Configuration().verbose_mode_details
- #eh = SimConnectEventHandler()
+ # eh = SimConnectEventHandler()
from .SimConnectManager import SimConnectManager
+
self._manager = SimConnectManager()
options = SimconnectOptions()
@@ -4130,45 +4294,53 @@ def __init__(self, parent):
self._command_type = SimConnectCommandType.SimVar
self.units = "Number"
self.data_type = "int"
- self.is_ranged = False # true if ranged data
+ self.is_ranged = False # true if ranged data
# the current command category if the command is an event
self.category = SimConnectEventCategory.NotSet
# the current command name
self._command = None
- self._command_release = None # command on release if any provided
+ self._command_release = None # command on release if any provided
self._command_mode = options.last_command_mode
- self.is_release_command = False # true if the action has a command to execute on release
+ self.is_release_command = (
+ False # true if the action has a command to execute on release
+ )
self.auto_repeat = False
- self.auto_repeat_interval = 250 # how often to repeat the command while pressed in ms
+ self.auto_repeat_interval = (
+ 250 # how often to repeat the command while pressed in ms
+ )
# the value to output if any
-
- self._output_min_range = -16383 # min range for ranged output
- self._output_max_range = 16384 # max range for ranged output
- self._normalized_min_range = -1 # normalized range min (-1 to +1)
- self._normalized_max_range = 1 # normalized range max (-1 to +1)
- self._command_min_range = -16383 # simconnect min range for the command (if known - can be manually input)
- self._command_max_range = 16384 # simconnect max range for the command (if known - can be manually input)
+
+ self._output_min_range = -16383 # min range for ranged output
+ self._output_max_range = 16384 # max range for ranged output
+ self._normalized_min_range = -1 # normalized range min (-1 to +1)
+ self._normalized_max_range = 1 # normalized range max (-1 to +1)
+ self._command_min_range = (
+ -16383
+ ) # simconnect min range for the command (if known - can be manually input)
+ self._command_max_range = 16384 # simconnect max range for the command (if known - can be manually input)
self._percent_min_range = 0
self._percent_max_range = 100
self._limit_min_range = -16383
self._limit_max_range = 16384
- self.inverted = False # inversion flag
- self.trigger_mode = SimConnectTriggerMode.NoOp # trigger only
+ self.inverted = False # inversion flag
+ self.trigger_mode = SimConnectTriggerMode.NoOp # trigger only
- self.trigger_on_press = True # true if the action is triggered on a button press
- self.trigger_on_release = False # true if the action is triggered on a button release
-
+ self.trigger_on_press = (
+ True # true if the action is triggered on a button press
+ )
+ self.trigger_on_release = (
+ False # true if the action is triggered on a button release
+ )
# curve data applied to a simconnect axis output
- self.curve_data = None # present if curve data is needed
-
+ self.curve_data = None # present if curve data is needed
- self._block = None # block loaded based on the command
+ self._block = None # block loaded based on the command
# output mode
if self.input_type == InputType.JoystickAxis:
@@ -4182,7 +4354,7 @@ def __init__(self, parent):
self.is_readonly = False
def _get_value_command(self, value):
- ''' gets a value expression for the current command '''
+ """gets a value expression for the current command"""
command = self.command
if command:
if "{#}" in command:
@@ -4192,113 +4364,117 @@ def _get_value_command(self, value):
return command
def _is_value_command(self):
- ''' true if the command is a valid value expression '''
+ """true if the command is a valid value expression"""
command = self.command
if command:
- result = "{#}" in command
+ result = "{#}" in command
if self.is_release_command:
release_command = self._command_release
if release_command:
- result = result and "{#}" in release_command
+ result = result and "{#}" in release_command
return result
return False
-
@property
def command_min_range(self) -> float:
return self._command_min_range
+
@command_min_range.setter
def command_min_range(self, value: float):
self._command_min_range = value
- if self._verbose: syslog.info(f"set command min: {value:0.3f}")
+ if self._verbose:
+ syslog.info(f"set command min: {value:0.3f}")
@property
def command_max_range(self) -> float:
return self._command_max_range
+
@command_max_range.setter
def command_max_range(self, value: float):
self._command_max_range = value
- if self._verbose: syslog.info(f"set command max: {value:0.3f}")
+ if self._verbose:
+ syslog.info(f"set command max: {value:0.3f}")
@property
def command_mode(self) -> SimConnectCommandMode:
return self._command_mode
-
+
@command_mode.setter
- def command_mode(self, value : SimConnectCommandMode):
+ def command_mode(self, value: SimConnectCommandMode):
self._command_mode = value
@property
def command_type(self) -> SimConnectCommandType:
return self._command_type
-
+
@command_type.setter
- def command_type(self, value : SimConnectCommandType):
+ def command_type(self, value: SimConnectCommandType):
self._command_type = value
@property
def command(self):
- ''' active simconnect command for this action '''
+ """active simconnect command for this action"""
return self._command
-
+
@command.setter
def command(self, value):
if value != self._command:
# update command and associated block
-
+
self._command = value
- self.update_block()
+ self.update_block()
@property
def command_release(self):
- ''' active simconnect command for this action '''
+ """active simconnect command for this action"""
return self._command_release
-
+
@command_release.setter
def command_release(self, value):
self._command_release = value
-
@property
def command_description(self) -> str:
if self.block:
return self.block.display_block_type
return ""
-
def scale_output(self, value):
- ''' scales a NORMALIZED output value from a value -1 to +1 '''
- return gremlin.util.scale_to_range(value, target_min = self.command_min_range, target_max = self.command_max_range, invert=self.inverted)
-
-
+ """scales a NORMALIZED output value from a value -1 to +1"""
+ return gremlin.util.scale_to_range(
+ value,
+ target_min=self.command_min_range,
+ target_max=self.command_max_range,
+ invert=self.inverted,
+ )
- def get_filtered_axis_value(self, value : float = None) -> float:
- ''' computes the output value for the current configuration '''
+ def get_filtered_axis_value(self, value: float = None) -> float:
+ """computes the output value for the current configuration"""
if value is None:
# filter input
- value = gremlin.joystick_handling.get_curved_axis(self.hardware_device_guid,
- self.hardware_input_id)
+ value = gremlin.joystick_handling.get_curved_axis(
+ self.hardware_device_guid, self.hardware_input_id
+ )
return value
-
- def get_local_curve_value(self, value : float) -> float:
+
+ def get_local_curve_value(self, value: float) -> float:
# apply local curve if any
if self.curve_data:
value = self.curve_data.curve_value(value)
return value
-
def get_raw_axis_value(self):
if self.input_is_hardware():
- return gremlin.joystick_handling.get_curved_axis(self.hardware_device_guid, self.hardware_input_id)
+ return gremlin.joystick_handling.get_curved_axis(
+ self.hardware_device_guid, self.hardware_input_id
+ )
return self.hardware_input_id.axis_value
-
def display_name(self):
- ''' returns a string for this action for display purposes '''
+ """returns a string for this action for display purposes"""
return self.block.display_name
-
def icon(self):
"""Returns the icon to use for this action.
@@ -4306,20 +4482,18 @@ def icon(self):
:return icon representing this action
"""
return "mdi.airplane"
-
-
def update_block(self):
- ''' updates the data block with the current command '''
+ """updates the data block with the current command"""
if self._command is None:
self._command = self._default_command()
- block : SimConnectBlock = self._manager.block(self._command)
+ block: SimConnectBlock = self._manager.block(self._command)
if block and not block.command_type:
- block.command_type = self.command_type # SimConnectCommandType.Event
+ block.command_type = self.command_type # SimConnectCommandType.Event
self._block = block
if block:
# set data
- #self.command = block.command
+ # self.command = block.command
# self.command_min_range = block.min_range
# self.command_max_range = block.max_range
self.category = block.category
@@ -4327,99 +4501,106 @@ def update_block(self):
self.is_ranged = block.is_ranged
self._is_axis = block.is_axis
-
-
@property
def block(self):
- ''' returns the current data block '''
+ """returns the current data block"""
if self._block is None:
# create it for the current command
self.update_block()
return self._block
-
+
@property
def limit_min_range(self) -> float:
return self._limit_min_range
+
@property
def limit_max_range(self) -> float:
return self._limit_max_range
-
+
@property
def value(self) -> float:
min_norm = self.normalized_min_range
min_range = self.command_min_range
max_range = self.command_max_range
- value = gremlin.util.scale_to_range(min_norm, target_min = min_range, target_max = max_range, invert = self.inverted)
+ value = gremlin.util.scale_to_range(
+ min_norm, target_min=min_range, target_max=max_range, invert=self.inverted
+ )
return value
-
@property
def output_min_range(self) -> float:
return self._output_min_range
+
@output_min_range.setter
- def output_min_range(self, value : float):
+ def output_min_range(self, value: float):
self._output_min_range = value
- if self._verbose: syslog.info(f"set output min: {value:0.3f}")
+ if self._verbose:
+ syslog.info(f"set output min: {value:0.3f}")
@property
def output_max_range(self) -> float:
return self._output_max_range
+
@output_max_range.setter
- def output_max_range(self, value : float):
+ def output_max_range(self, value: float):
self._output_max_range = value
- if self._verbose: syslog.info(f"set output max: {value:0.3f}")
+ if self._verbose:
+ syslog.info(f"set output max: {value:0.3f}")
@property
def normalized_min_range(self) -> float:
return self._normalized_min_range
-
- def setNormalized(self, min_value : float, max_value : float, update = False):
- ''' set normalized values '''
+
+ def setNormalized(self, min_value: float, max_value: float, update=False):
+ """set normalized values"""
self._normalized_min_range = min_value
self._normalized_max_range = max_value
if update:
self._update_from_normalized()
- if self._verbose: syslog.info(f"set norm min max: {min_value:0.3f} {max_value:0.3f}")
-
-
+ if self._verbose:
+ syslog.info(f"set norm min max: {min_value:0.3f} {max_value:0.3f}")
+
@normalized_min_range.setter
- def normalized_min_range(self, value : float):
- ''' normalized output min -1 to +1 '''
+ def normalized_min_range(self, value: float):
+ """normalized output min -1 to +1"""
self._normalized_min_range = value
- if self._verbose: syslog.info(f"set norm min: {value:0.3f}")
+ if self._verbose:
+ syslog.info(f"set norm min: {value:0.3f}")
@property
def normalized_max_range(self) -> float:
return self._normalized_max_range
-
+
@normalized_max_range.setter
- def normalized_max_range(self, value : float):
- ''' normalized output max -1 to +1 '''
+ def normalized_max_range(self, value: float):
+ """normalized output max -1 to +1"""
self._normalized_max_range = value
- if self._verbose: syslog.info(f"set norm min: {value:0.3f}")
+ if self._verbose:
+ syslog.info(f"set norm min: {value:0.3f}")
@property
def percent_min_range(self) -> float:
- ''' percent output min 0 to 100 '''
+ """percent output min 0 to 100"""
return self._percent_min_range
-
+
@percent_min_range.setter
- def percent_min_range(self, value : float):
+ def percent_min_range(self, value: float):
self._percent_min_range = value
- if self._verbose: syslog.info(f"set percent min: {value:0.3f}")
+ if self._verbose:
+ syslog.info(f"set percent min: {value:0.3f}")
@property
def percent_max_range(self) -> float:
- ''' percent output max 0 to 100 '''
+ """percent output max 0 to 100"""
return self._percent_max_range
-
+
@percent_max_range.setter
- def percent_max_range(self, value : float):
+ def percent_max_range(self, value: float):
self._percent_max_range = value
- if self._verbose: syslog.info(f"set percent max: {value:0.3f}")
-
+ if self._verbose:
+ syslog.info(f"set percent max: {value:0.3f}")
def requires_virtual_button(self):
"""Returns whether or not an activation condition is needed.
@@ -4428,102 +4609,145 @@ def requires_virtual_button(self):
action instance, False otherwise
"""
return False
-
def _update_from_percent(self):
- ''' updates output data from percent min/max'''
- self.normalized_min_range = gremlin.util.scale_to_range(self._normalized_min_range, source_min = 0, source_max = 100)
- self.normalized_max_range = gremlin.util.scale_to_range(self._normalized_min_range, source_min = 0, source_max = 100)
+ """updates output data from percent min/max"""
+ self.normalized_min_range = gremlin.util.scale_to_range(
+ self._normalized_min_range, source_min=0, source_max=100
+ )
+ self.normalized_max_range = gremlin.util.scale_to_range(
+ self._normalized_min_range, source_min=0, source_max=100
+ )
self._update_from_normalized()
-
def _update_percent(self):
- ''' updates percent range from normalized data '''
- self.percent_min_range = gremlin.util.scale_to_range(self._normalized_min_range, target_min=0, target_max = 100)
- self.percent_max_range = gremlin.util.scale_to_range(self._normalized_max_range, target_min=0, target_max = 100)
+ """updates percent range from normalized data"""
+ self.percent_min_range = gremlin.util.scale_to_range(
+ self._normalized_min_range, target_min=0, target_max=100
+ )
+ self.percent_max_range = gremlin.util.scale_to_range(
+ self._normalized_max_range, target_min=0, target_max=100
+ )
def _update_from_normalized(self):
- ''' updates output data from normalized min/max'''
- self.output_min_range = gremlin.util.scale_to_range(self._normalized_min_range, source_min = self._normalized_min_range, source_max = self._normalized_max_range, target_min=self._command_min_range, target_max = self._command_max_range)
- self.output_max_range = gremlin.util.scale_to_range(self._normalized_max_range, source_min = self._normalized_min_range, source_max = self._normalized_max_range, target_min=self._command_min_range, target_max = self._command_max_range)
+ """updates output data from normalized min/max"""
+ self.output_min_range = gremlin.util.scale_to_range(
+ self._normalized_min_range,
+ source_min=self._normalized_min_range,
+ source_max=self._normalized_max_range,
+ target_min=self._command_min_range,
+ target_max=self._command_max_range,
+ )
+ self.output_max_range = gremlin.util.scale_to_range(
+ self._normalized_max_range,
+ source_min=self._normalized_min_range,
+ source_max=self._normalized_max_range,
+ target_min=self._command_min_range,
+ target_max=self._command_max_range,
+ )
self._update_percent()
def _update_from_output(self):
- ''' updates normalized range from the output range '''
- self.normalized_min_range = gremlin.util.scale_to_range(self._output_min_range, source_min = self._output_min_range, source_max = self._output_max_range)
- self.normalized_max_range = gremlin.util.scale_to_range(self._output_max_range, source_min = self._output_min_range, source_max = self._output_max_range)
+ """updates normalized range from the output range"""
+ self.normalized_min_range = gremlin.util.scale_to_range(
+ self._output_min_range,
+ source_min=self._output_min_range,
+ source_max=self._output_max_range,
+ )
+ self.normalized_max_range = gremlin.util.scale_to_range(
+ self._output_max_range,
+ source_min=self._output_min_range,
+ source_max=self._output_max_range,
+ )
self._update_percent()
-
-
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Reads the contents of an XML node to populate this instance.
:param node the node whose content should be used to populate this
instance
"""
-
-
default_command = self._default_command()
- self._command = safe_read(node,"command",str, default_command)
- self._command_release = safe_read(node,"command_release",str, "")
- self.is_release_command = safe_read(node,"has_release", bool, False)
+ self._command = safe_read(node, "command", str, default_command)
+ self._command_release = safe_read(node, "command_release", str, "")
+ self.is_release_command = safe_read(node, "has_release", bool, False)
self._block = SimConnectManager().block(self._command)
-
# debug
mode = self.get_mode()
if mode == "duke" and self._command == "THROTTLE1_AXIS_SET_EX1":
for attrib in node.attrib:
- print (f"{attrib}: {node.get(attrib)}")
+ print(f"{attrib}: {node.get(attrib)}")
pass
-
-
update_from_output = False
if "min_range" in node.attrib:
- # old profile
- self.output_min_range = safe_read(node,"min_range", float)
+ # old profile
+ self.output_min_range = safe_read(node, "min_range", float)
update_from_output = True
else:
- self.output_min_range = safe_read(node,"output_min_range", float) if "output_min_range" in node.attrib else -16383
-
-
+ self.output_min_range = (
+ safe_read(node, "output_min_range", float)
+ if "output_min_range" in node.attrib
+ else -16383
+ )
if "max_range" in node.attrib:
# old profile
- self.output_max_range = safe_read(node,"max_range", float)
+ self.output_max_range = safe_read(node, "max_range", float)
update_from_output = True
else:
- self.output_max_range = safe_read(node,"output_max_range", float) if "output_max_range" in node.attrib else 16384
+ self.output_max_range = (
+ safe_read(node, "output_max_range", float)
+ if "output_max_range" in node.attrib
+ else 16384
+ )
if update_from_output:
# old profile
self._update_from_output()
else:
# read data
- norm_min = safe_read(node,"norm_min_range", float) if "norm_min_range" in node.attrib else -1.0
- norm_max = safe_read(node,"norm_max_range", float) if "norm_max_range" in node.attrib else 1.0
-
- if norm_min == norm_max or \
- not gremlin.util.valueInRange(norm_min,-1,1) or \
- not gremlin.util.valueInRange(norm_max,-1,1):
+ norm_min = (
+ safe_read(node, "norm_min_range", float)
+ if "norm_min_range" in node.attrib
+ else -1.0
+ )
+ norm_max = (
+ safe_read(node, "norm_max_range", float)
+ if "norm_max_range" in node.attrib
+ else 1.0
+ )
+
+ if (
+ norm_min == norm_max
+ or not gremlin.util.valueInRange(norm_min, -1, 1)
+ or not gremlin.util.valueInRange(norm_max, -1, 1)
+ ):
# reset bad data
- syslog.error(f"RANGE ERROR: values for min {norm_min:0.3f} and max {norm_max:0.3f} range are identical - reset to -1,+1")
+ syslog.error(
+ f"RANGE ERROR: values for min {norm_min:0.3f} and max {norm_max:0.3f} range are identical - reset to -1,+1"
+ )
norm_min = -1
norm_max = +1
self._normalized_min_range = norm_min
self._normalized_max_range = norm_max
-
- min_value = safe_read(node,"command_min_range", float) if "command_min_range" in node.attrib else -16383
- max_value = safe_read(node,"command_max_range", float) if "command_max_range" in node.attrib else 16384
+ min_value = (
+ safe_read(node, "command_min_range", float)
+ if "command_min_range" in node.attrib
+ else -16383
+ )
+ max_value = (
+ safe_read(node, "command_max_range", float)
+ if "command_max_range" in node.attrib
+ else 16384
+ )
self.command_min_range = min_value
self.command_max_range = max_value
-
s_mode = safe_read(node, "mode", str, "")
if s_mode:
self.mode = SimConnectActionMode.to_enum(s_mode)
@@ -4534,13 +4758,13 @@ def _parse_xml(self, node, data = None):
if "type" in node.attrib:
self._command_type = SimConnectCommandType.to_enum(node.get("type"))
- self.inverted = safe_read(node,"inverted",bool, False)
+ self.inverted = safe_read(node, "inverted", bool, False)
if "trigger" in node.attrib:
- s_trigger = safe_read(node,"trigger",str,"")
+ s_trigger = safe_read(node, "trigger", str, "")
self.trigger_mode = SimConnectTriggerMode.to_enum(s_trigger)
if "units" in node.attrib:
- self.units = safe_read(node,"units",str,"Number")
+ self.units = safe_read(node, "units", str, "Number")
if "data_type" in node.attrib:
self.data_type= safe_read(node,"data_type",str,"int")
diff --git a/action_plugins/map_to_vjoy/__init__.py b/action_plugins/map_to_vjoy/__init__.py
index 2c6d4772..ff9eba0a 100644
--- a/action_plugins/map_to_vjoy/__init__.py
+++ b/action_plugins/map_to_vjoy/__init__.py
@@ -48,7 +48,7 @@
from functools import partial
-IdMapToButton = -2 # map to button special ID
+IdMapToButton = -2 # map to button special ID
import gremlin.ui.input_item
import gremlin.base_profile
import gremlin.shared_state
@@ -57,8 +57,9 @@
syslog = logging.getLogger("system")
+
@SingletonDecorator
-class StepWidgetGroup():
+class StepWidgetGroup:
def __init__(self):
self.group = QtWidgets.QButtonGroup()
@@ -67,22 +68,24 @@ def clear(self):
class StepWidget(gremlin.ui.ui_common.QDataWidget):
- defaultChanged = QtCore.Signal(int, bool) # fires when default flag changes (index, flag)
- valueChanged = QtCore.Signal(int, float) # fires when value changes (index, value)
- deleteRequested = QtCore.Signal(int) # fires when delete is requested
+ defaultChanged = QtCore.Signal(
+ int, bool
+ ) # fires when default flag changes (index, flag)
+ valueChanged = QtCore.Signal(int, float) # fires when value changes (index, value)
+ deleteRequested = QtCore.Signal(int) # fires when delete is requested
def __init__(self, index, value):
super().__init__()
self.index = index
layout = QtWidgets.QHBoxLayout(self)
-
+
self.value_widget = gremlin.ui.ui_common.QFloatLineEdit()
self.value_widget.setValue(value)
self.value_widget.valueChanged.connect(self._step_value_changed)
self.default_cb = gremlin.ui.ui_common.QDataRadioButton("")
bg = StepWidgetGroup()
bg.group.addButton(self.default_cb)
-
+
self.default_cb.setToolTip("Set as profile start value")
self.default_cb.clicked.connect(self._step_default_changed)
@@ -97,7 +100,6 @@ def __init__(self, index, value):
layout.addWidget(self.default_cb)
layout.addStretch()
-
@QtCore.Slot(bool)
def _step_default_changed(self, checked):
self.defaultChanged.emit(self.index, checked)
@@ -112,99 +114,96 @@ def _delete(self):
def value(self) -> float:
return self.value_widget.value()
-
+
def setValue(self, value: float):
with QtCore.QSignalBlocker(self.value_widget):
self.value_widget.setValue(value)
- def setDefault(self, value:bool):
- ''' enable or disable default state '''
+ def setDefault(self, value: bool):
+ """enable or disable default state"""
bg = StepWidgetGroup()
bg.group.buttons()[self.index].setChecked(value)
-
-
-
-class MergeOperationType (enum.IntEnum):
- ''' merge operation method'''
+
+class MergeOperationType(enum.IntEnum):
+ """merge operation method"""
+
NotSet = 0
- Add = 1 # the two inputs are added
- Average = 2 # the two inputs are averaged
- Center = 3 # centered (left - right)/2
- Min = 4 # min of two axes
- Max = 5 # max of two axes
+ Add = 1 # the two inputs are added
+ Average = 2 # the two inputs are averaged
+ Center = 3 # centered (left - right)/2
+ Min = 4 # min of two axes
+ Max = 5 # max of two axes
@staticmethod
- def to_display_name(value : MergeOperationType):
+ def to_display_name(value: MergeOperationType):
return _merge_operation_display_lookup[value]
@staticmethod
- def to_enum(value : str):
+ def to_enum(value: str):
return _merge_operation_to_enum_lookup[value]
@staticmethod
- def to_string(value : MergeOperationType):
+ def to_string(value: MergeOperationType):
return _merge_operation_to_string_lookup[value]
@staticmethod
- def to_description(value : MergeOperationType):
+ def to_description(value: MergeOperationType):
return _merge_operation_to_description_lookup[value]
_merge_operation_to_enum_lookup = {
- "none" : MergeOperationType.NotSet,
- "add" : MergeOperationType.Add,
- "average" : MergeOperationType.Average,
- "center" : MergeOperationType.Center,
- "min" : MergeOperationType.Min,
- "max" : MergeOperationType.Max,
-
+ "none": MergeOperationType.NotSet,
+ "add": MergeOperationType.Add,
+ "average": MergeOperationType.Average,
+ "center": MergeOperationType.Center,
+ "min": MergeOperationType.Min,
+ "max": MergeOperationType.Max,
}
_merge_operation_to_string_lookup = {
- MergeOperationType.NotSet : "none",
- MergeOperationType.Add : "add",
- MergeOperationType.Average : "average",
- MergeOperationType.Center : "center",
- MergeOperationType.Min : "min",
- MergeOperationType.Max : "max",
+ MergeOperationType.NotSet: "none",
+ MergeOperationType.Add: "add",
+ MergeOperationType.Average: "average",
+ MergeOperationType.Center: "center",
+ MergeOperationType.Min: "min",
+ MergeOperationType.Max: "max",
}
_merge_operation_display_lookup = {
- MergeOperationType.NotSet : "N/A",
- MergeOperationType.Add : "Add",
- MergeOperationType.Average : "Average",
- MergeOperationType.Center : "Center",
- MergeOperationType.Min : "Minimum",
- MergeOperationType.Max : "Maximum",
+ MergeOperationType.NotSet: "N/A",
+ MergeOperationType.Add: "Add",
+ MergeOperationType.Average: "Average",
+ MergeOperationType.Center: "Center",
+ MergeOperationType.Min: "Minimum",
+ MergeOperationType.Max: "Maximum",
}
_merge_operation_to_description_lookup = {
- MergeOperationType.NotSet : "Not set",
- MergeOperationType.Add : "A + B",
- MergeOperationType.Average : "Average (A+B)/2",
- MergeOperationType.Center : "Centered (A-B)/2",
- MergeOperationType.Min : "Min(A, B)",
- MergeOperationType.Max : "Max(A, B)",
-
+ MergeOperationType.NotSet: "Not set",
+ MergeOperationType.Add: "A + B",
+ MergeOperationType.Average: "Average (A+B)/2",
+ MergeOperationType.Center: "Centered (A-B)/2",
+ MergeOperationType.Min: "Min(A, B)",
+ MergeOperationType.Max: "Max(A, B)",
}
class GridClickWidget(QtWidgets.QWidget):
- ''' implements a widget that reponds to a mouse click '''
+ """implements a widget that reponds to a mouse click"""
+
pressPos = None
clicked = QtCore.Signal()
- def __init__(self, vjoy_device_id, input_type, vjoy_input_id, parent = None):
+ def __init__(self, vjoy_device_id, input_type, vjoy_input_id, parent=None):
super(GridClickWidget, self).__init__(parent=parent)
self.vjoy_device_id = vjoy_device_id
self.input_type = input_type
self.vjoy_input_id = vjoy_input_id
-
def mousePressEvent(self, event):
- if event.button() == QtCore.Qt.LeftButton :
+ if event.button() == QtCore.Qt.LeftButton:
self.pressPos = event.pos()
def mouseReleaseEvent(self, event):
@@ -213,13 +212,14 @@ def mouseReleaseEvent(self, event):
if self.pressPos is not None and event.button() == QtCore.Qt.LeftButton:
pos = event.pos()
rect = self.rect()
- if rect.contains(pos):
+ if rect.contains(pos):
self.clicked.emit()
self.pressPos = None
+
class GridButton(QtWidgets.QPushButton):
def __init__(self, action):
- super(GridButton,self).__init__()
+ super(GridButton, self).__init__()
self.action = action
def _clicked(self):
@@ -227,8 +227,8 @@ def _clicked(self):
class GridPopupWindow(gremlin.ui.ui_common.QRememberDialog):
- def __init__(self, vjoy_device_id, input_type, vjoy_input_id, parent = None):
- super().__init__(self.__class__.__name__, parent = parent)
+ def __init__(self, vjoy_device_id, input_type, vjoy_input_id, parent=None):
+ super().__init__(self.__class__.__name__, parent=parent)
self.vjoy_device_id = vjoy_device_id
self.input_type = input_type
@@ -237,20 +237,23 @@ def __init__(self, vjoy_device_id, input_type, vjoy_input_id, parent = None):
self.setWindowTitle("Mapping Details")
usage_data = gremlin.joystick_handling.VJoyUsageState()
- action_map = usage_data.get_action_map(vjoy_device_id, input_type, vjoy_input_id)
+ action_map = usage_data.get_action_map(
+ vjoy_device_id, input_type, vjoy_input_id
+ )
if not action_map:
self.close()
box = QtWidgets.QVBoxLayout()
- box.setContentsMargins(0,0,0,0)
+ box.setContentsMargins(0, 0, 0, 0)
self.layout = box
-
- source = QtWidgets.QWidget()
- source.setContentsMargins(0,0,0,0)
+ source = QtWidgets.QWidget()
+ source.setContentsMargins(0, 0, 0, 0)
source_box = QtWidgets.QHBoxLayout(source)
- source_box.setContentsMargins(0,0,0,0)
- source_box.addWidget(QtWidgets.QLabel(f"Vjoy {vjoy_device_id} Button {vjoy_input_id} mapped by:"))
+ source_box.setContentsMargins(0, 0, 0, 0)
+ source_box.addWidget(
+ QtWidgets.QLabel(f"Vjoy {vjoy_device_id} Button {vjoy_input_id} mapped by:")
+ )
box.addWidget(source)
for action in action_map:
@@ -264,31 +267,28 @@ def __init__(self, vjoy_device_id, input_type, vjoy_input_id, parent = None):
elif action.device_input_type == InputType.JoystickHat:
name = f"Hat {action.device_input_id}"
item_box.addWidget(QtWidgets.QLabel(name))
- #item_box.addWidget(GridButton(action))
+ # item_box.addWidget(GridButton(action))
box.addWidget(item)
-
self.setLayout(box)
-
class VJoyWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""Dialog which allows the selection of a vJoy output to use as
as the remapping for the currently selected input.
"""
- locked = False
-
+ locked = False
# all button type inputs (hat is handled separately as is axis)
- input_type_buttons = [InputType.JoystickButton,
- InputType.Keyboard,
- InputType.KeyboardLatched,
- InputType.OpenSoundControl,
- InputType.Midi,
- InputType.ModeControl,
- ]
+ input_type_buttons = [
+ InputType.JoystickButton,
+ InputType.Keyboard,
+ InputType.KeyboardLatched,
+ InputType.OpenSoundControl,
+ InputType.Midi,
+ InputType.ModeControl,
+ ]
def __init__(self, action_data, parent=None):
"""Creates a new VjoyRemapWidget.
@@ -297,24 +297,25 @@ def __init__(self, action_data, parent=None):
:param parent the parent of this widget
"""
super().__init__(action_data, parent=parent)
- assert(isinstance(action_data, VjoyRemap))
+ assert isinstance(action_data, VjoyRemap)
def _create(self, action_data):
pass
-
-
def _create_ui(self):
"""Creates the UI components."""
if VJoyWidget.locked:
return
-
+
if not gremlin.shared_state.vjoy_enabled:
- self.main_layout.addWidget(QtWidgets.QLabel("VJOY is not available. Ensure VJOY is installed and configured."))
+ self.main_layout.addWidget(
+ QtWidgets.QLabel(
+ "VJOY is not available. Ensure VJOY is installed and configured."
+ )
+ )
return
-
veh = gremlin.event_handler.VjoyRemapEventHandler()
veh.grid_visible_changed.connect(self.grid_visible_changed)
@@ -322,32 +323,33 @@ def _create_ui(self):
VJoyWidget.locked = True
self.valid_types = [
- InputType.JoystickAxis,
- InputType.JoystickButton,
- InputType.JoystickHat,
- InputType.Midi,
- InputType.OpenSoundControl,
- ]
+ InputType.JoystickAxis,
+ InputType.JoystickButton,
+ InputType.JoystickHat,
+ InputType.Midi,
+ InputType.OpenSoundControl,
+ ]
self._axis_tracking_enabled = False
self.grid_visible_widget = None
self.is_button_mode = False # true if the action is mapping to a button
- self._grid_widgets = {} # list of checkboxes in the button grid indexed by button id (1...max_button)
- self.slider_widget = None # slider for stepped setup
+ self._grid_widgets = {} # list of checkboxes in the button grid indexed by button id (1...max_button)
+ self.slider_widget = None # slider for stepped setup
self.usage_state = gremlin.joystick_handling.VJoyUsageState()
self.main_layout.setSpacing(0)
- self._merge_enabled = False # disable merging by default
-
+ self._merge_enabled = False # disable merging by default
# Create UI widgets for absolute / relative axis modes if the remap
# action is being added to an axis input type
- self.input_type = self.action_data.hardware_input_type #._get_input_type() # self.action_data.input_type
+ self.input_type = (
+ self.action_data.hardware_input_type
+ ) # ._get_input_type() # self.action_data.input_type
# init default widget tracking
- self.button_grid_widget = None
+ self.button_grid_widget = None
self.container_axis_widget = None
# handler to update curve widget if displayed
@@ -368,70 +370,68 @@ def _create_ui(self):
self._create_input_grid()
self._create_info()
-
-
self.main_layout.setContentsMargins(0, 0, 0, 0)
-
eh = gremlin.event_handler.EventListener()
eh.button_usage_changed.connect(self._button_usage_changed)
-
# set the action type from the input type
self.load_actions_from_input_type()
-
# self.notify_device_changed()
-
-
# update UI visibility
- #self._update_ui_action_mode(self.action_data)
+ # self._update_ui_action_mode(self.action_data)
finally:
VJoyWidget.locked = False
@QtCore.Slot(int)
def _button_usage_changed(self, vjoy_id):
- ''' button state changed somewhere '''
+ """button state changed somewhere"""
if vjoy_id == self.action_data.vjoy_device_id:
# update if it's our device
self.refresh_grid()
-
def _get_selector_input_type(self):
- ''' gets a modified input type based on the current mode '''
+ """gets a modified input type based on the current mode"""
input_type = self.action_data.hardware_input_type
- if input_type in VJoyWidget.input_type_buttons and \
- self.action_data.action_mode in (VjoyAction.VJoySetAxis,
- VjoyAction.VJoyInvertAxis,
- VjoyAction.VJoyRangeAxis,
- VjoyAction.VJoySetAxisStepped):
+ if (
+ input_type in VJoyWidget.input_type_buttons
+ and self.action_data.action_mode
+ in (
+ VjoyAction.VJoySetAxis,
+ VjoyAction.VJoyInvertAxis,
+ VjoyAction.VJoyRangeAxis,
+ VjoyAction.VJoySetAxisStepped,
+ )
+ ):
return InputType.JoystickAxis
return input_type
-
def _create_hat_mapping(self):
- ''' creates the 8 way hat inputs based on the hat input value '''
+ """creates the 8 way hat inputs based on the hat input value"""
self.container_hat_widget = QtWidgets.QWidget()
self.container_hat_widget.setVisible(False)
- self.container_hat_widget.setContentsMargins(0,0,0,0)
+ self.container_hat_widget.setContentsMargins(0, 0, 0, 0)
self.container_hat_layout = QtWidgets.QVBoxLayout(self.container_hat_widget)
- self.container_hat_layout.setContentsMargins(0,0,0,0)
+ self.container_hat_layout.setContentsMargins(0, 0, 0, 0)
self.container_hat_grid_widget = QtWidgets.QWidget()
- self.container_hat_grid_layout = QtWidgets.QGridLayout(self.container_hat_grid_widget)
+ self.container_hat_grid_layout = QtWidgets.QGridLayout(
+ self.container_hat_grid_widget
+ )
self.container_hat_options_widget = QtWidgets.QWidget()
- self.container_hat_options_widget.setContentsMargins(0,0,0,0)
- self.container_hat_options_layout = QtWidgets.QHBoxLayout(self.container_hat_options_widget)
+ self.container_hat_options_widget.setContentsMargins(0, 0, 0, 0)
+ self.container_hat_options_layout = QtWidgets.QHBoxLayout(
+ self.container_hat_options_widget
+ )
self.main_layout.addWidget(self.container_hat_widget)
-
-
self.cb_hat_list = []
self.rb_hat_hold_list = []
self.rb_hat_pulse_list = []
@@ -440,10 +440,12 @@ def _create_hat_mapping(self):
self.hat_pulse_widget.setToolTip("Sets all mappings to pulse mode")
self.hat_hold_widget = QtWidgets.QPushButton("All Hold")
self.hat_hold_widget.setToolTip("Sets all mappings to hold mode")
- self.hat_unmap_widget = QtWidgets.QPushButton("Clear Buttons")
+ self.hat_unmap_widget = QtWidgets.QPushButton("Clear Buttons")
self.hat_unmap_widget.setToolTip("Clears all mappings")
- self.hat_map_widget = QtWidgets.QPushButton("Map Buttons")
- self.hat_map_widget.setToolTip("Maps all positions sequentially using the first button as the reference if set.")
+ self.hat_map_widget = QtWidgets.QPushButton("Map Buttons")
+ self.hat_map_widget.setToolTip(
+ "Maps all positions sequentially using the first button as the reference if set."
+ )
self.hat_hold_widget.clicked.connect(self._set_all_hold)
self.hat_pulse_widget.clicked.connect(self._set_all_pulse)
@@ -451,12 +453,12 @@ def _create_hat_mapping(self):
self.hat_map_widget.clicked.connect(self._auto_map)
self.hat_stick_widget = QtWidgets.QCheckBox("Sticky mode")
- self.hat_stick_widget.setToolTip("When enabled, all pressed hat positions will stick until the hat returns to the center position")
+ self.hat_stick_widget.setToolTip(
+ "When enabled, all pressed hat positions will stick until the hat returns to the center position"
+ )
self.hat_stick_widget.setChecked(self.action_data.hat_sticky)
self.hat_stick_widget.clicked.connect(self._hat_sticky_changed)
-
-
self.container_hat_options_layout.addWidget(self.hat_pulse_widget)
self.container_hat_options_layout.addWidget(self.hat_hold_widget)
self.container_hat_options_layout.addWidget(self.hat_unmap_widget)
@@ -464,29 +466,34 @@ def _create_hat_mapping(self):
self.container_hat_options_layout.addWidget(self.hat_stick_widget)
self.container_hat_options_layout.addStretch()
-
positions = self.action_data.hat_positions
-
self.container_hat_layout.addWidget(self.container_hat_options_widget)
self.container_hat_layout.addWidget(self.container_hat_grid_widget)
row = 0
- for position in positions: # 9 positions - 8 cardinal and center push
+ for position in positions: # 9 positions - 8 cardinal and center push
cb = gremlin.ui.ui_common.NoWheelComboBox()
cb.data = position
name = vjoy.vjoy.Hat.direction_to_name[position]
icon = vjoy.vjoy.Hat.direction_to_icon[position]
- lbl = gremlin.ui.ui_common.QIconLabel(icon_path=icon, text = f"{name}:", use_wrap= False, icon_color=QtGui.QColor(gremlin.ui.ui_common.Color.activeColor()),icon_size=32, use_qta=True)
+ lbl = gremlin.ui.ui_common.QIconLabel(
+ icon_path=icon,
+ text=f"{name}:",
+ use_wrap=False,
+ icon_color=QtGui.QColor(gremlin.ui.ui_common.Color.activeColor()),
+ icon_size=32,
+ use_qta=True,
+ )
lbl.setIcon(icon)
self.container_hat_grid_layout.addWidget(lbl, row, 0)
- self.container_hat_grid_layout.addWidget(cb, row,1)
+ self.container_hat_grid_layout.addWidget(cb, row, 1)
self.cb_hat_list.append(cb)
cb.currentIndexChanged.connect(self._hat_mapping_changed)
mode_container_widget = QtWidgets.QWidget()
- mode_container_widget.setContentsMargins(0,0,0,0)
+ mode_container_widget.setContentsMargins(0, 0, 0, 0)
mode_container_layout = QtWidgets.QHBoxLayout(mode_container_widget)
rb_hold = gremlin.ui.ui_common.QDataRadioButton("Hold")
@@ -506,19 +513,17 @@ def _create_hat_mapping(self):
row += 1
-
self.container_hat_grid_layout.addWidget(QtWidgets.QLabel(), 0, 4)
- self.container_hat_grid_layout.setColumnStretch(4,3)
+ self.container_hat_grid_layout.setColumnStretch(4, 3)
self._update_hat_mapping()
-
@QtCore.Slot(bool)
- def _hat_sticky_changed(self, checked : bool):
+ def _hat_sticky_changed(self, checked: bool):
self.action_data.hat_sticky = checked
@QtCore.Slot()
def _set_all_hold(self):
- ''' sets all mappings to hold mode '''
+ """sets all mappings to hold mode"""
positions = self.action_data.hat_positions
for position in positions:
self.action_data.hat_pulse_map[position] = False
@@ -526,7 +531,7 @@ def _set_all_hold(self):
@QtCore.Slot()
def _set_all_pulse(self):
- ''' sets all mappings to pulse mode '''
+ """sets all mappings to pulse mode"""
positions = self.action_data.hat_positions
for position in positions:
self.action_data.hat_pulse_map[position] = True
@@ -534,8 +539,10 @@ def _set_all_pulse(self):
@QtCore.Slot()
def _clear_map(self):
- ''' sets all mappings to pulse mode '''
- msgbox = gremlin.ui.ui_common.ConfirmBox(prompt = "Clear all hat button mappings?")
+ """sets all mappings to pulse mode"""
+ msgbox = gremlin.ui.ui_common.ConfirmBox(
+ prompt="Clear all hat button mappings?"
+ )
result = msgbox.show()
if result == QtWidgets.QMessageBox.StandardButton.Ok:
positions = self.action_data.hat_positions
@@ -545,8 +552,10 @@ def _clear_map(self):
@QtCore.Slot()
def _auto_map(self):
- ''' sets all mappings to pulse mode '''
- msgbox = gremlin.ui.ui_common.ConfirmBox(prompt = "Remap all hat button mappings?")
+ """sets all mappings to pulse mode"""
+ msgbox = gremlin.ui.ui_common.ConfirmBox(
+ prompt="Remap all hat button mappings?"
+ )
result = msgbox.show()
if result == QtWidgets.QMessageBox.StandardButton.Ok:
positions = self.action_data.hat_positions
@@ -568,19 +577,17 @@ def _auto_map(self):
self._update_hat_mapping()
-
@QtCore.Slot()
def _hat_mapping_changed(self):
- ''' updates a hat button mapping selection '''
+ """updates a hat button mapping selection"""
cb = self.sender()
position = cb.data
button_id = cb.currentData()
self.action_data.hat_map[position] = button_id
-
@QtCore.Slot()
def _hat_hold_changed(self):
- ''' updates a hat button mapping selection '''
+ """updates a hat button mapping selection"""
widget = self.sender()
if widget.isChecked():
position = widget.data
@@ -588,24 +595,26 @@ def _hat_hold_changed(self):
@QtCore.Slot()
def _hat_pulse_changed(self):
- ''' updates a hat button mapping selection '''
+ """updates a hat button mapping selection"""
widget = self.sender()
if widget.isChecked():
position = widget.data
self.action_data.hat_pulse_map[position] = True
def _update_hat_mapping(self):
- ''' updates the hat button options for hat to button mapping '''
+ """updates the hat button options for hat to button mapping"""
dev = self.action_data.vjoy_map[self.action_data.vjoy_device_id]
count = dev.button_count
positions = self.action_data.hat_positions
- for index, position in enumerate(positions): # 9 positions - 8 cardinal and center push
+ for index, position in enumerate(
+ positions
+ ): # 9 positions - 8 cardinal and center push
cb = self.cb_hat_list[index]
with QtCore.QSignalBlocker(cb):
cb.clear()
cb.addItem("Not mapped", 0)
- for id in range(1, count+1):
- cb.addItem(f"Button {id}",id)
+ for id in range(1, count + 1):
+ cb.addItem(f"Button {id}", id)
is_pulsed = self.action_data.hat_pulse_map[position]
if is_pulsed:
@@ -620,33 +629,27 @@ def _update_hat_mapping(self):
self._load_hat_mapping()
def _load_hat_mapping(self):
- ''' loads the hat data into the UI '''
+ """loads the hat data into the UI"""
positions = self.action_data.hat_positions
- for index, position in enumerate(positions): # 9 positions - 8 cardinal and center push
- button_id = self.action_data.hat_map[position] # 0 means disabled
+ for index, position in enumerate(
+ positions
+ ): # 9 positions - 8 cardinal and center push
+ button_id = self.action_data.hat_map[position] # 0 means disabled
button_index = button_id
cb = self.cb_hat_list[index]
if button_index < cb.count():
with QtCore.QSignalBlocker(cb):
cb.setCurrentIndex(button_index)
-
-
-
-
-
-
def _create_input_axis(self):
- ''' creates the axis input widget '''
-
-
+ """creates the axis input widget"""
self.container_axis_widget = QtWidgets.QWidget()
- self.container_axis_widget.setContentsMargins(0,0,0,0)
+ self.container_axis_widget.setContentsMargins(0, 0, 0, 0)
self.container_axis_layout = QtWidgets.QGridLayout(self.container_axis_widget)
- self.container_axis_layout.setColumnStretch(8,1)
- #self.container_axis_layout.setContentsMargins(0,0,0,0)
+ self.container_axis_layout.setColumnStretch(8, 1)
+ # self.container_axis_layout.setContentsMargins(0,0,0,0)
self.reverse_checkbox = QtWidgets.QCheckBox("Reverse")
@@ -654,30 +657,35 @@ def _create_input_axis(self):
self.absolute_checkbox.setChecked(True)
self.relative_checkbox = QtWidgets.QRadioButton("Relative")
-
-
-
-
self.b_min_value = QtWidgets.QPushButton("-1")
w = 32
- self.set_width(self.b_min_value,w)
+ self.set_width(self.b_min_value, w)
self.b_center_value = QtWidgets.QPushButton("0")
- self.set_width(self.b_center_value,w)
+ self.set_width(self.b_center_value, w)
self.b_max_value = QtWidgets.QPushButton("+1")
- self.set_width(self.b_max_value,w)
-
+ self.set_width(self.b_max_value, w)
# output axis repeater
self.container_repeater_widget = QtWidgets.QWidget()
- self.container_repeater_layout = QtWidgets.QHBoxLayout(self.container_repeater_widget)
- self._axis_repeater_widget = gremlin.ui.ui_common.AxisStateWidget(show_percentage=False,orientation=QtCore.Qt.Orientation.Horizontal, parent= self.container_repeater_widget)
+ self.container_repeater_layout = QtWidgets.QHBoxLayout(
+ self.container_repeater_widget
+ )
+ self._axis_repeater_widget = gremlin.ui.ui_common.AxisStateWidget(
+ show_percentage=False,
+ orientation=QtCore.Qt.Orientation.Horizontal,
+ parent=self.container_repeater_widget,
+ )
self.curve_button_widget = QtWidgets.QPushButton("Output Curve")
active_color = gremlin.ui.ui_common.Color.activeColor()
normal_color = gremlin.ui.ui_common.Color.normalColor()
- self.curve_icon_inactive = util.load_icon("mdi.chart-bell-curve",qta_color=normal_color)
- self.curve_icon_active = util.load_icon("mdi.chart-bell-curve",qta_color=active_color)
+ self.curve_icon_inactive = util.load_icon(
+ "mdi.chart-bell-curve", qta_color=normal_color
+ )
+ self.curve_icon_active = util.load_icon(
+ "mdi.chart-bell-curve", qta_color=active_color
+ )
self.curve_button_widget.setToolTip("Curve output")
self.curve_button_widget.clicked.connect(self._curve_button_cb)
@@ -687,101 +695,114 @@ def _create_input_axis(self):
self.curve_clear_widget.setToolTip("Removes the curve output")
self.curve_clear_widget.clicked.connect(self._curve_delete_button_cb)
-
self.container_repeater_layout.addWidget(self.curve_button_widget)
self.container_repeater_layout.addWidget(self.curve_clear_widget)
self.container_repeater_layout.addWidget(self._axis_repeater_widget)
self.container_repeater_layout.addStretch()
self._update_curve_icon()
-
row = 0
col = 0
- self.container_axis_layout.addWidget(QtWidgets.QLabel("Reverse Axis:"),row,col)
- row+=1
- self.container_axis_layout.addWidget(self.reverse_checkbox,row,col)
+ self.container_axis_layout.addWidget(
+ QtWidgets.QLabel("Reverse Axis:"), row, col
+ )
+ row += 1
+ self.container_axis_layout.addWidget(self.reverse_checkbox, row, col)
row = 0
- col+=1
- self.container_axis_layout.addWidget(QtWidgets.QLabel("Output Mode:"),row,col)
- row+=1
- self.container_axis_layout.addWidget(self.absolute_checkbox,row,col)
- row+=1
- self.container_axis_layout.addWidget(self.relative_checkbox,row,col)
-
+ col += 1
+ self.container_axis_layout.addWidget(QtWidgets.QLabel("Output Mode:"), row, col)
+ row += 1
+ self.container_axis_layout.addWidget(self.absolute_checkbox, row, col)
+ row += 1
+ self.container_axis_layout.addWidget(self.relative_checkbox, row, col)
row = 0
- col+=1
+ col += 1
self._axis_start_value_enabled_widget = QtWidgets.QCheckBox("Start Value:")
- self._axis_start_value_enabled_widget.setChecked(self.action_data.axis_start_value_enabled)
- self._axis_start_value_enabled_widget.clicked.connect(self._axis_start_value_enabled)
- self._axis_start_value_enabled_widget.setToolTip("When set, sets the axis to the specified startup value on profile start.\nIf not set, uses the current axis input value on start.")
- self.container_axis_layout.addWidget(self._axis_start_value_enabled_widget,row,col,1,3)
- row+=1
-
+ self._axis_start_value_enabled_widget.setChecked(
+ self.action_data.axis_start_value_enabled
+ )
+ self._axis_start_value_enabled_widget.clicked.connect(
+ self._axis_start_value_enabled
+ )
+ self._axis_start_value_enabled_widget.setToolTip(
+ "When set, sets the axis to the specified startup value on profile start.\nIf not set, uses the current axis input value on start."
+ )
+ self.container_axis_layout.addWidget(
+ self._axis_start_value_enabled_widget, row, col, 1, 3
+ )
+ row += 1
- self.sb_start_value = gremlin.ui.ui_common.DynamicDoubleSpinBox(parent = self.container_axis_widget)
+ self.sb_start_value = gremlin.ui.ui_common.DynamicDoubleSpinBox(
+ parent=self.container_axis_widget
+ )
# w = 100
# self.set_width(self.sb_start_value,w)
self.sb_start_value.setMinimum(-1.0)
self.sb_start_value.setMaximum(1.0)
self.sb_start_value.setDecimals(3)
-
- self.container_axis_layout.addWidget(self.sb_start_value,row,col,1,3)
- row+=1
- self.container_axis_layout.addWidget(self.b_min_value,row,col)
- col+=1
- self.container_axis_layout.addWidget(self.b_center_value,row,col)
- col+=1
- self.container_axis_layout.addWidget(self.b_max_value,row,col)
+ self.container_axis_layout.addWidget(self.sb_start_value, row, col, 1, 3)
+
+ row += 1
+ self.container_axis_layout.addWidget(self.b_min_value, row, col)
+ col += 1
+ self.container_axis_layout.addWidget(self.b_center_value, row, col)
+ col += 1
+ self.container_axis_layout.addWidget(self.b_max_value, row, col)
row = 0
- col+=1
- self.container_axis_layout.addWidget(QtWidgets.QLabel(" "),row,col)
- col+=1
- self.container_axis_layout.addWidget(QtWidgets.QLabel("Axis"),row,col)
- row+=1
- self.container_axis_layout.addWidget(QtWidgets.QLabel("Scale:"),row,col)
- row+=1
+ col += 1
+ self.container_axis_layout.addWidget(QtWidgets.QLabel(" "), row, col)
+ col += 1
+ self.container_axis_layout.addWidget(QtWidgets.QLabel("Axis"), row, col)
+ row += 1
+ self.container_axis_layout.addWidget(QtWidgets.QLabel("Scale:"), row, col)
+ row += 1
self.relative_scaling_widget = gremlin.ui.ui_common.QFloatLineEdit()
self.relative_scaling_widget.setMinimum(0)
self.relative_scaling_widget.setMaximum(1000.0)
self.relative_scaling_widget.setDecimals(3)
- self.container_axis_layout.addWidget(self.relative_scaling_widget,row,col)
+ self.container_axis_layout.addWidget(self.relative_scaling_widget, row, col)
row = 0
- col+=1
- self.container_axis_layout.addWidget(QtWidgets.QLabel(" "),row,col)
- col+=1
- self.container_axis_layout.addWidget(QtWidgets.QLabel("Axis Output Range:"),row,col,1,2)
- row+=1
- self.container_axis_layout.addWidget(QtWidgets.QLabel("Min:"),row,col)
- row+=1
- self.sb_axis_range_low_widget = gremlin.ui.ui_common.QFloatLineEdit(parent = self.container_axis_widget)
+ col += 1
+ self.container_axis_layout.addWidget(QtWidgets.QLabel(" "), row, col)
+ col += 1
+ self.container_axis_layout.addWidget(
+ QtWidgets.QLabel("Axis Output Range:"), row, col, 1, 2
+ )
+ row += 1
+ self.container_axis_layout.addWidget(QtWidgets.QLabel("Min:"), row, col)
+ row += 1
+ self.sb_axis_range_low_widget = gremlin.ui.ui_common.QFloatLineEdit(
+ parent=self.container_axis_widget
+ )
self.sb_axis_range_low_widget.setMinimum(-1.0)
self.sb_axis_range_low_widget.setMaximum(1.0)
self.sb_axis_range_low_widget.setDecimals(3)
- self.container_axis_layout.addWidget(self.sb_axis_range_low_widget,row,col)
-
- col+=1
- row=1
- self.container_axis_layout.addWidget(QtWidgets.QLabel("Max:"),row,col)
- row+=1
+ self.container_axis_layout.addWidget(self.sb_axis_range_low_widget, row, col)
+ col += 1
+ row = 1
+ self.container_axis_layout.addWidget(QtWidgets.QLabel("Max:"), row, col)
+ row += 1
- self.sb_axis_range_high_widget = gremlin.ui.ui_common.QFloatLineEdit(parent = self.container_axis_widget)
+ self.sb_axis_range_high_widget = gremlin.ui.ui_common.QFloatLineEdit(
+ parent=self.container_axis_widget
+ )
self.sb_axis_range_high_widget.setMinimum(-1.0)
self.sb_axis_range_high_widget.setMaximum(1.0)
self.sb_axis_range_high_widget.setDecimals(3)
- self.container_axis_layout.addWidget(self.sb_axis_range_high_widget,row,col)
+ self.container_axis_layout.addWidget(self.sb_axis_range_high_widget, row, col)
row = 0
- col+=1
- self.container_axis_layout.addWidget(QtWidgets.QLabel(" "),row,col)
- self.container_axis_layout.setColumnStretch(col,2)
+ col += 1
+ self.container_axis_layout.addWidget(QtWidgets.QLabel(" "), row, col)
+ self.container_axis_layout.setColumnStretch(col, 2)
self.main_layout.addWidget(self.container_axis_widget)
self.main_layout.addWidget(self.container_repeater_widget)
@@ -796,16 +817,15 @@ def _create_input_axis(self):
self.b_center_value.clicked.connect(self._b_center_start_value_clicked)
self.b_max_value.clicked.connect(self._b_max_start_value_clicked)
-
self.sb_axis_range_low_widget.valueChanged.connect(self._axis_range_low_changed)
- self.sb_axis_range_high_widget.valueChanged.connect(self._axis_range_high_changed)
-
+ self.sb_axis_range_high_widget.valueChanged.connect(
+ self._axis_range_high_changed
+ )
# hook the inputs and profile
self._enable_axis_tracking()
self._update_axis_widget()
-
def _enable_axis_tracking(self):
if not self._axis_tracking_enabled:
self._axis_tracking_enabled = True
@@ -817,7 +837,7 @@ def _enable_axis_tracking(self):
el.profile_stop.connect(self._profile_stop)
def _disable_axis_tracking(self):
- ''' disables tracking '''
+ """disables tracking"""
if self._axis_tracking_enabled:
el = gremlin.event_handler.EventListener()
el.custom_joystick_event.disconnect(self._joystick_event_handler)
@@ -825,9 +845,8 @@ def _disable_axis_tracking(self):
el.joystick_event.disconnect(self._joystick_event_handler)
self._axis_tracking_enabled = False
-
def _create_merge_ui(self):
- ''' creates the axis merging UI components '''
+ """creates the axis merging UI components"""
# merge operations
self.container_merge_widget = QtWidgets.QWidget()
self.container_merge_layout = QtWidgets.QVBoxLayout(self.container_merge_widget)
@@ -837,42 +856,62 @@ def _create_merge_ui(self):
device_widget = QtWidgets.QWidget()
device_layout = QtWidgets.QGridLayout(device_widget)
- device_layout.addWidget(QtWidgets.QLabel("Merge Device:"),0,0)
- device_layout.addWidget(self.merge_selector_device_widget,0,1)
- device_layout.addWidget(QtWidgets.QLabel(" "),0,2)
- device_layout.addWidget(QtWidgets.QLabel("Merge Axis:"),1,0)
- device_layout.addWidget(self.merge_selector_input_widget,1,1)
- device_layout.setColumnStretch(2,2)
+ device_layout.addWidget(QtWidgets.QLabel("Merge Device:"), 0, 0)
+ device_layout.addWidget(self.merge_selector_device_widget, 0, 1)
+ device_layout.addWidget(QtWidgets.QLabel(" "), 0, 2)
+ device_layout.addWidget(QtWidgets.QLabel("Merge Axis:"), 1, 0)
+ device_layout.addWidget(self.merge_selector_input_widget, 1, 1)
+ device_layout.setColumnStretch(2, 2)
self.container_merge_layout.addWidget(device_widget)
- self.merge_selector_device_widget.currentIndexChanged.connect(self._merged_device_changed_cb)
- self.merge_selector_input_widget.currentIndexChanged.connect(self._merged_input_changed_cb)
+ self.merge_selector_device_widget.currentIndexChanged.connect(
+ self._merged_device_changed_cb
+ )
+ self.merge_selector_input_widget.currentIndexChanged.connect(
+ self._merged_input_changed_cb
+ )
# populate the selector with hardware inputs
- self.merge_device_map = {} # holds the device information keyed by device_id (str)
- self.merge_input_map = {} # holds the list of axes for the given device by device_id(str)
- devices = sorted(joystick_handling.axis_input_devices(),key=lambda x: x.name)
+ self.merge_device_map = {} # holds the device information keyed by device_id (str)
+ self.merge_input_map = {} # holds the list of axes for the given device by device_id(str)
+ devices = sorted(joystick_handling.axis_input_devices(), key=lambda x: x.name)
- self._merge_enabled = len(devices) > 0 # assume enabled
+ self._merge_enabled = len(devices) > 0 # assume enabled
# figure out the default device to use
default_device = None
selected_input_id = 1
if self.action_data.merge_device_id:
- default_device = next((dev for dev in devices if dev.device_id == self.action_data.merge_device_id), None)
+ default_device = next(
+ (
+ dev
+ for dev in devices
+ if dev.device_id == self.action_data.merge_device_id
+ ),
+ None,
+ )
if default_device:
-
if default_device.device_guid == self.action_data.hardware_device_guid:
# the merge device to pick is the same as the current device
if default_device.axis_count == 1:
# there is only one input which is already used
self._merge_enabled = False
- if self.action_data.merge_input_id and self.action_data.merge_input_id <= default_device.axis_count :
+ if (
+ self.action_data.merge_input_id
+ and self.action_data.merge_input_id <= default_device.axis_count
+ ):
selected_input_id = self.action_data.merge_input_id
if not default_device:
- default_device = next((dev for dev in devices if dev.device_guid == self.action_data.hardware_device_guid), None)
+ default_device = next(
+ (
+ dev
+ for dev in devices
+ if dev.device_guid == self.action_data.hardware_device_guid
+ ),
+ None,
+ )
if default_device:
axis_count = default_device.axis_count
if axis_count == 1:
@@ -889,7 +928,6 @@ def _create_merge_ui(self):
# pick one below if next not available
selected_input_id = input_id - 1
-
if not self._merge_enabled:
return
@@ -897,17 +935,16 @@ def _create_merge_ui(self):
# pick the first one if nothing else got selected
default_device = devices[0]
-
-
-
selected_device_index = devices.index(default_device)
for dev in devices:
self.merge_device_map[dev.device_id] = dev
axis_list = {}
- for input_id in range(1, dev.axis_count+1):
- if dev.device_guid == self.action_data.hardware_device_guid and \
- input_id == self.action_data.hardware_input_id:
+ for input_id in range(1, dev.axis_count + 1):
+ if (
+ dev.device_guid == self.action_data.hardware_device_guid
+ and input_id == self.action_data.hardware_input_id
+ ):
# skip self as a possible input
continue
axis_name = self.get_axis_name(input_id)
@@ -917,16 +954,18 @@ def _create_merge_ui(self):
self.merge_input_map[dev.device_id] = axis_list
self.merge_selector_device_widget.addItem(dev.name, dev.device_id)
-
# merge operation mode
self.container_merge_options_widget = QtWidgets.QWidget()
- self.container_merge_options_layout = QtWidgets.QHBoxLayout(self.container_merge_options_widget)
+ self.container_merge_options_layout = QtWidgets.QHBoxLayout(
+ self.container_merge_options_widget
+ )
self._merge_widgets_map = {}
-
for merge_type in MergeOperationType:
if merge_type != MergeOperationType.NotSet:
- rb = gremlin.ui.ui_common.QDataRadioButton(text = MergeOperationType.to_display_name(merge_type), data = merge_type)
+ rb = gremlin.ui.ui_common.QDataRadioButton(
+ text=MergeOperationType.to_display_name(merge_type), data=merge_type
+ )
self.container_merge_options_layout.addWidget(rb)
self._merge_widgets_map[merge_type] = rb
if merge_type == self.action_data.merge_mode:
@@ -938,11 +977,12 @@ def _create_merge_ui(self):
self.merge_invert_widget.clicked.connect(self._merge_invert_changed_cb)
self.container_merge_options_layout.addWidget(self.merge_invert_widget)
-
# ranged merged output
self.container_output_range_widget = QtWidgets.QWidget()
- self.container_output_range_layout = QtWidgets.QHBoxLayout(self.container_output_range_widget)
- self.container_output_range_widget.setContentsMargins(0,0,0,0)
+ self.container_output_range_layout = QtWidgets.QHBoxLayout(
+ self.container_output_range_widget
+ )
+ self.container_output_range_widget.setContentsMargins(0, 0, 0, 0)
self.sb_range_min_widget = gremlin.ui.ui_common.QFloatLineEdit()
self.sb_range_min_widget.setValue(self.action_data.output_range_min)
@@ -958,7 +998,6 @@ def _create_merge_ui(self):
self.container_output_range_layout.addWidget(self.sb_range_max_widget)
self.container_output_range_layout.addStretch()
-
self.container_merge_options_layout.addStretch()
self.container_merge_layout.addWidget(self.container_merge_options_widget)
@@ -969,14 +1008,13 @@ def _create_merge_ui(self):
# select the default device
self.merge_selector_device_widget.setCurrentIndex(selected_device_index)
- selected_input_index = self.merge_selector_input_widget.findData(selected_input_id)
+ selected_input_index = self.merge_selector_input_widget.findData(
+ selected_input_id
+ )
if selected_input_index == -1:
selected_input_index = 0
self.merge_selector_input_widget.setCurrentIndex(selected_input_index)
-
-
-
def _update_curve_icon(self):
if self.action_data.curve_data:
self.curve_button_widget.setIcon(self.curve_icon_active)
@@ -986,10 +1024,16 @@ def _update_curve_icon(self):
self.curve_clear_widget.setEnabled(False)
QtCore.Slot()
+
def _curve_button_cb(self):
if not self.action_data.curve_data:
curve_data = gremlin.curve_handler.AxisCurveData()
- curve_data.calibration = gremlin.ui.axis_calibration.CalibrationManager().getCalibration(self.action_data.hardware_device_guid, self.action_data.hardware_input_id)
+ curve_data.calibration = (
+ gremlin.ui.axis_calibration.CalibrationManager().getCalibration(
+ self.action_data.hardware_device_guid,
+ self.action_data.hardware_input_id,
+ )
+ )
curve_data.curve_update()
self.action_data.curve_data = curve_data
@@ -1007,14 +1051,15 @@ def _curve_button_cb(self):
self._update_curve_icon()
QtCore.Slot()
+
def _curve_delete_button_cb(self):
- ''' removes the curve data '''
+ """removes the curve data"""
message_box = QtWidgets.QMessageBox()
message_box.setText("Confirmation")
message_box.setInformativeText("Delete curve data for this output?")
message_box.setStandardButtons(
- QtWidgets.QMessageBox.StandardButton.Ok |
- QtWidgets.QMessageBox.StandardButton.Cancel
+ QtWidgets.QMessageBox.StandardButton.Ok
+ | QtWidgets.QMessageBox.StandardButton.Cancel
)
message_box.setDefaultButton(QtWidgets.QMessageBox.StandardButton.Ok)
gremlin.util.centerDialog(message_box)
@@ -1028,21 +1073,22 @@ def _curve_delete_button_cb(self):
self.action_data.curve_data = None
self._update_curve_icon()
-
QtCore.Slot()
+
def _range_min_changed_cb(self):
value = self.sb_range_min_widget.value()
self.action_data.output_range_min = value
self._update_axis_widget()
QtCore.Slot()
+
def _range_max_changed_cb(self):
self.action_data.output_range_max = self.sb_range_max_widget.value()
self._update_axis_widget()
@QtCore.Slot()
def _merged_device_changed_cb(self):
- ''' merge device changed '''
+ """merge device changed"""
index = self.merge_selector_device_widget.currentIndex()
device_id = self.merge_selector_device_widget.itemData(index)
with QtCore.QSignalBlocker(self.merge_selector_input_widget):
@@ -1056,56 +1102,61 @@ def _merged_device_changed_cb(self):
self.action_data.merge_input_id = first_input_id
self.action_modified.emit()
-
-
@QtCore.Slot()
def _merged_input_changed_cb(self):
- ''' merge input changed '''
+ """merge input changed"""
index = self.merge_selector_input_widget.currentIndex()
input_id = self.merge_selector_input_widget.itemData(index)
self.action_data.merge_input_id = input_id
self.action_modified.emit()
-
-
-
def _profile_start(self):
- ''' called when the profile starts '''
+ """called when the profile starts"""
self._disable_axis_tracking()
def _profile_stop(self):
- ''' called when the profile stops'''
+ """called when the profile stops"""
self._update_axis_widget()
self._enable_axis_tracking()
-
def _joystick_event_handler(self, event):
- ''' handles joystick events in the UI (functor handles the output when profile is running) so we see the output at design time '''
+ """handles joystick events in the UI (functor handles the output when profile is running) so we see the output at design time"""
if gremlin.shared_state.is_running:
return
if not event.is_axis:
return
-
+
value = event.value
if self.action_data.action_mode == VjoyAction.VJoyMergeAxis:
# merge - check two sets
- if event.device_guid == self.action_data.hardware_device_guid and event.device_guid == self.action_data.merge_device_guid:
+ if (
+ event.device_guid == self.action_data.hardware_device_guid
+ and event.device_guid == self.action_data.merge_device_guid
+ ):
# merge hardware is the same as current input - accept only the two input itds
- if event.identifier != self.action_data.hardware_input_id and event.identifier != self.action_data.merge_input_id:
+ if (
+ event.identifier != self.action_data.hardware_input_id
+ and event.identifier != self.action_data.merge_input_id
+ ):
return
else:
# not the same:
- if event.device_guid == self.action_data.hardware_device_guid and event.identifier != self.action_data.hardware_input_id:
+ if (
+ event.device_guid == self.action_data.hardware_device_guid
+ and event.identifier != self.action_data.hardware_input_id
+ ):
return
- if event.device_guid == self.action_data.merge_device_guid and event.identifier != self.action_data.merge_input_id:
+ if (
+ event.device_guid == self.action_data.merge_device_guid
+ and event.identifier != self.action_data.merge_input_id
+ ):
return
- # compute the merged value
+ # compute the merged value
value = self.action_data.get_filtered_axis_value(value)
-
else:
if event.device_guid != self.action_data.hardware_device_guid:
return
@@ -1114,22 +1165,20 @@ def _joystick_event_handler(self, event):
self._update_axis_widget(value)
-
def _current_input_axis(self):
- ''' gets the current input axis value '''
- return gremlin.joystick_handling.get_curved_axis(self.action_data.hardware_device_guid,
- self.action_data.hardware_input_id)
-
+ """gets the current input axis value"""
+ return gremlin.joystick_handling.get_curved_axis(
+ self.action_data.hardware_device_guid, self.action_data.hardware_input_id
+ )
- def _update_axis_widget(self, value : float = None):
- ''' updates the axis output repeater with the value
+ def _update_axis_widget(self, value: float = None):
+ """updates the axis output repeater with the value
:param value: the floating point input value, if None uses the cached value
- '''
+ """
# always read the current input as the value could be from another device for merged inputs
- if self.action_data.input_is_axis(): # == InputType.JoystickAxis:
-
+ if self.action_data.input_is_axis(): # == InputType.JoystickAxis:
raw_value = self.action_data.get_raw_axis_value()
if value is None:
# filter and merge the data
@@ -1151,10 +1200,6 @@ def _update_axis_widget(self, value : float = None):
if self.curve_update_handler is not None:
self.curve_update_handler(raw_value)
-
-
-
-
@QtCore.Slot(bool)
def _merge_invert_changed_cb(self, checked):
self.action_data.merge_invert = checked
@@ -1162,7 +1207,7 @@ def _merge_invert_changed_cb(self, checked):
@QtCore.Slot(bool)
def _merge_mode_changed_cb(self, checked):
- ''' merge mode selection change '''
+ """merge mode selection change"""
widget = self.sender()
self.merge_type = widget.data
@@ -1171,7 +1216,7 @@ def merge_type(self) -> MergeOperationType:
return self.action_data.merge_type
@merge_type.setter
- def merge_type(self, value : MergeOperationType):
+ def merge_type(self, value: MergeOperationType):
if self.action_data.merge_mode != value:
self.action_data.merge_mode = value
widget = self._merge_widgets_map[value]
@@ -1179,10 +1224,8 @@ def merge_type(self, value : MergeOperationType):
widget.setChecked(True)
self._update_axis_widget()
-
-
def get_axis_name(self, input_id):
- ''' gets the axis name based on the input # '''
+ """gets the axis name based on the input #"""
if input_id == 1:
axis_name = "X"
elif input_id == 2:
@@ -1204,9 +1247,9 @@ def get_axis_name(self, input_id):
return axis_name
def _create_info(self):
- ''' shows what device is currently selected '''
+ """shows what device is currently selected"""
state = gremlin.joystick_handling.VJoyUsageState()
- header = QtWidgets.QWidget()
+ header = QtWidgets.QWidget()
box = QtWidgets.QVBoxLayout(header)
box.addWidget(QtWidgets.QLabel(state._active_device_name))
input_type = state._active_device_input_type
@@ -1214,19 +1257,18 @@ def _create_info(self):
vjoy_device_id = self.action_data.vjoy_device_id
vjoy_input_id = self.action_data.vjoy_input_id
-
# command modes
value = self.action_data.action_mode
if value in (
- VjoyAction.VJoyDisableLocal,
- VjoyAction.VJoyDisableRemote,
- VjoyAction.VJoyEnableLocalOnly,
- VjoyAction.VJoyEnableRemoteOnly,
- VjoyAction.VJoyEnableLocalAndRemote,
- VjoyAction.VJoyEnableLocal,
- VjoyAction.VJoyEnableRemote,
- VjoyAction.VJoyToggleRemote,
- ):
+ VjoyAction.VJoyDisableLocal,
+ VjoyAction.VJoyDisableRemote,
+ VjoyAction.VJoyEnableLocalOnly,
+ VjoyAction.VJoyEnableRemoteOnly,
+ VjoyAction.VJoyEnableLocalAndRemote,
+ VjoyAction.VJoyEnableLocal,
+ VjoyAction.VJoyEnableRemote,
+ VjoyAction.VJoyToggleRemote,
+ ):
action_name = "GremlinEx Command"
else:
action_name = None
@@ -1235,7 +1277,6 @@ def _create_info(self):
case VjoyAction.VJoyAxisToButton:
action_name = f"Vjoy device {vjoy_device_id} button {vjoy_input_id}"
-
is_axis = self.action_data.input_is_axis()
if is_axis:
if not action_name:
@@ -1258,26 +1299,20 @@ def _create_info(self):
action_name = f"Vjoy device {vjoy_device_id} button {vjoy_input_id}"
name = f"Input trigger -> {action_name}"
-
box.addWidget(QtWidgets.QLabel(name))
box.addStretch()
self.main_layout.addWidget(header)
-
- def set_width(self, widget, width, height = 22):
+ def set_width(self, widget, width, height=22):
widget.setFixedSize(width, height)
-
-
def _create_selector(self):
- ''' creates the button option panel '''
-
+ """creates the button option panel"""
- self.selector_widget = QtWidgets.QWidget()
+ self.selector_widget = QtWidgets.QWidget()
grid = QtWidgets.QGridLayout(self.selector_widget)
- grid.setColumnStretch(3,1)
-
+ grid.setColumnStretch(3, 1)
# behavior combo box - lets the user select the output behavior
self.cb_action_list = gremlin.ui.ui_common.NoWheelComboBox()
@@ -1285,74 +1320,86 @@ def _create_selector(self):
lbl = QtWidgets.QLabel("Mode:")
row = 0
- grid.addWidget(lbl,row,0)
+ grid.addWidget(lbl, row, 0)
grid.addWidget(self.cb_action_list, row, 1)
self.action_label = QtWidgets.QLabel()
- grid.addWidget(self.action_label,row,2,1,3)
-
+ grid.addWidget(self.action_label, row, 2, 1, 3)
# vjoy device selection - display vjoy target ID and vjoy target input - the input changes based on the behavior
-
row += 1
self.lbl_vjoy_device_selector = QtWidgets.QLabel("Device:")
- grid.addWidget(self.lbl_vjoy_device_selector,row,0)
+ grid.addWidget(self.lbl_vjoy_device_selector, row, 0)
self.cb_vjoy_device_selector = gremlin.ui.ui_common.NoWheelComboBox()
- grid.addWidget(self.cb_vjoy_device_selector,row,1)
+ grid.addWidget(self.cb_vjoy_device_selector, row, 1)
for dev in self.action_data.vjoy_map.values():
self.cb_vjoy_device_selector.addItem(dev.name, dev.vjoy_id)
-
-
row += 1
self.cb_vjoy_input_selector = gremlin.ui.ui_common.NoWheelComboBox()
self.lbl_vjoy_input_selector = QtWidgets.QLabel("Output:")
- grid.addWidget(self.lbl_vjoy_input_selector,row,0)
- grid.addWidget(self.cb_vjoy_input_selector,row,1)
+ grid.addWidget(self.lbl_vjoy_input_selector, row, 0)
+ grid.addWidget(self.cb_vjoy_input_selector, row, 1)
row += 1
warning_color = gremlin.ui.ui_common.Color.warningColor()
- self.warning_widget = gremlin.ui.ui_common.QIconLabel("ph.shield-warning-fill",use_qta=True,icon_color=QtGui.QColor(warning_color),text="", use_wrap=False)
- warning_container, warning_layout = gremlin.ui.ui_common.getHContainer(self.warning_widget)
- grid.addWidget(warning_container, row,1,1,-1)
+ self.warning_widget = gremlin.ui.ui_common.QIconLabel(
+ "ph.shield-warning-fill",
+ use_qta=True,
+ icon_color=QtGui.QColor(warning_color),
+ text="",
+ use_wrap=False,
+ )
+ warning_container, warning_layout = gremlin.ui.ui_common.getHContainer(
+ self.warning_widget
+ )
+ grid.addWidget(warning_container, row, 1, 1, -1)
row += 1
- source = QtWidgets.QWidget()
+ source = QtWidgets.QWidget()
box = QtWidgets.QHBoxLayout(source)
-
+
self.chkb_exec_on_release = QtWidgets.QCheckBox("Exec on release")
- self.chkb_exec_on_release.setToolTip("If enabled, the trigger will execute on input release")
- #self.chkb_exec_on_release.setStyleSheet(css)
+ self.chkb_exec_on_release.setToolTip(
+ "If enabled, the trigger will execute on input release"
+ )
+ # self.chkb_exec_on_release.setStyleSheet(css)
box.addWidget(self.chkb_exec_on_release)
self.chkb_ignore_release = QtWidgets.QCheckBox("Ignore release")
- self.chkb_ignore_release.setToolTip("If enabled, the action will ignore release triggers (this is input and container dependent) - normal is OFF")
+ self.chkb_ignore_release.setToolTip(
+ "If enabled, the action will ignore release triggers (this is input and container dependent) - normal is OFF"
+ )
self.chkb_ignore_release.setStyleSheet("")
-
+
box.addWidget(self.chkb_ignore_release)
self.chkb_paired = QtWidgets.QCheckBox("Paired Group Member")
- #self.chkb_paired.setStyleSheet(css)
- self.chkb_paired.setToolTip("Paired groups with a remote client - when enabled - sends a remote signal and a local signal (this is seldom used).")
+ # self.chkb_paired.setStyleSheet(css)
+ self.chkb_paired.setToolTip(
+ "Paired groups with a remote client - when enabled - sends a remote signal and a local signal (this is seldom used)."
+ )
box.addWidget(self.chkb_paired)
-
self.chkb_auto_release_widget = QtWidgets.QCheckBox("Auto Release")
- self.chkb_auto_release_widget.setToolTip("Autorelease will trigger a release action when the input is released if the input does not issue one and that is the desired behavior.")
+ self.chkb_auto_release_widget.setToolTip(
+ "Autorelease will trigger a release action when the input is released if the input does not issue one and that is the desired behavior."
+ )
self.chkb_auto_release_widget.setChecked(self.action_data.auto_release)
-
box.addWidget(self.chkb_auto_release_widget)
-
-
grid.addWidget(source, row, 1)
# selector hooks
- self.cb_vjoy_device_selector.currentIndexChanged.connect(self._vjoy_device_id_changed)
- self.cb_vjoy_input_selector.currentIndexChanged.connect(self._vjoy_input_id_changed)
+ self.cb_vjoy_device_selector.currentIndexChanged.connect(
+ self._vjoy_device_id_changed
+ )
+ self.cb_vjoy_input_selector.currentIndexChanged.connect(
+ self._vjoy_input_id_changed
+ )
# pulse panel
@@ -1388,13 +1435,15 @@ def _create_selector(self):
self.rb_start_released.setChecked(True)
start_layout.addWidget(QtWidgets.QLabel("Start Mode:"))
- start_layout.addWidget(self.rb_start_released )
- start_layout.addWidget(self.rb_start_pressed )
+ start_layout.addWidget(self.rb_start_released)
+ start_layout.addWidget(self.rb_start_pressed)
grid_visible_container_widget = QtWidgets.QWidget()
- grid_visible_container_widget.setContentsMargins(0,0,0,0)
- self.grid_visible_container_layout = QtWidgets.QHBoxLayout(grid_visible_container_widget)
- self.grid_visible_container_layout.setContentsMargins(0,0,0,0)
+ grid_visible_container_widget.setContentsMargins(0, 0, 0, 0)
+ self.grid_visible_container_layout = QtWidgets.QHBoxLayout(
+ grid_visible_container_widget
+ )
+ self.grid_visible_container_layout.setContentsMargins(0, 0, 0, 0)
start_layout.addWidget(grid_visible_container_widget)
start_layout.addStretch()
@@ -1417,7 +1466,6 @@ def _create_selector(self):
self.b_range_top = QtWidgets.QPushButton("Top")
self.b_range_bottom = QtWidgets.QPushButton("Bot")
-
box.addWidget(QtWidgets.QLabel("Range Min:"))
box.addWidget(self.sb_button_range_low)
box.addWidget(QtWidgets.QLabel("Max:"))
@@ -1432,19 +1480,22 @@ def _create_selector(self):
# button to axis value widget
self.target_value_container_widget = QtWidgets.QWidget()
- self.target_value_container_layout = QtWidgets.QHBoxLayout(self.target_value_container_widget)
+ self.target_value_container_layout = QtWidgets.QHBoxLayout(
+ self.target_value_container_widget
+ )
self.sb_button_to_axis_value = gremlin.ui.ui_common.DynamicDoubleSpinBox()
self.sb_button_to_axis_value.setMinimum(-1.0)
self.sb_button_to_axis_value.setMaximum(1.0)
self.sb_button_to_axis_value.setDecimals(3)
self.sb_button_to_axis_value.setValue(self.action_data.target_value)
-
+
self.target_is_relative = QtWidgets.QCheckBox("Relative")
- self.target_is_relative.setToolTip("When enabled, the value is added to the current axis (relative value)")
+ self.target_is_relative.setToolTip(
+ "When enabled, the value is added to the current axis (relative value)"
+ )
self.target_is_relative.setChecked(self.action_data.target_is_relative)
self.target_is_relative.clicked.connect(self._target_relative_changed)
-
self.target_value_container_layout.addWidget(QtWidgets.QLabel("Set Value:"))
self.target_value_container_layout.addWidget(self.sb_button_to_axis_value)
self.target_value_container_layout.addWidget(self.target_is_relative)
@@ -1458,18 +1509,18 @@ def _create_selector(self):
# hook events
-
self.chkb_exec_on_release.clicked.connect(self._exec_on_release_changed)
self.chkb_ignore_release.clicked.connect(self._ignore_release_changed)
self.chkb_paired.clicked.connect(self._paired_changed)
self.chkb_auto_release_widget.clicked.connect(self._autorelease_changed)
-
+
self.pulse_spin_widget.valueChanged.connect(self._pulse_value_changed)
self.start_button_group.buttonClicked.connect(self._start_changed)
self.sb_button_range_low.valueChanged.connect(self._button_range_low_changed)
self.sb_button_range_high.valueChanged.connect(self._button_range_high_changed)
- self.sb_button_to_axis_value.valueChanged.connect(self._button_to_axis_value_changed)
-
+ self.sb_button_to_axis_value.valueChanged.connect(
+ self._button_to_axis_value_changed
+ )
self.b_range_reset.clicked.connect(self._b_range_reset_clicked)
self.b_range_half.clicked.connect(self._b_range_half_clicked)
@@ -1478,14 +1529,13 @@ def _create_selector(self):
self.b_range_bottom.clicked.connect(self._b_range_bot_clicked)
self.b_range_top.clicked.connect(self._b_range_top_clicked)
-
def setWarning(self, text):
- ''' updates warning'''
+ """updates warning"""
self.warning_widget.setText(text)
self.warning_widget.setVisible(text is not None and len(text))
def update_steps(self):
- ''' updates the stepped list widgets '''
+ """updates the stepped list widgets"""
steps = len(self.action_data.target_step_list)
enabled = steps > 0
self.step_start_index_widget.setEnabled(enabled)
@@ -1497,11 +1547,13 @@ def update_steps(self):
with QtCore.QSignalBlocker(self.step_start_index_widget):
self.step_start_index_widget.setValue(index)
- self.step_start_index_widget.setRange(1, steps+1)
+ self.step_start_index_widget.setRange(1, steps + 1)
self.action_data.target_step_list.sort()
with QtCore.QSignalBlocker(self.step_list_widget):
- csv = gremlin.util.floatlist_to_csv(self.action_data.target_step_list, decimals = 3)
+ csv = gremlin.util.floatlist_to_csv(
+ self.action_data.target_step_list, decimals=3
+ )
self.step_list_widget.setText(csv)
# updates individual step widgets and layout
@@ -1510,29 +1562,31 @@ def update_steps(self):
with QtCore.QSignalBlocker(self.step_count_widget):
self.step_count_widget.setValue(steps)
- if not self.action_data.target_step_index in self.action_data.target_step_list:
+ if (
+ self.action_data.target_step_index
+ not in self.action_data.target_step_list
+ ):
# reset the default if no longer in the list
self.action_data.target_step_index = 0
- self.step_start_value_widget.setValue(self.action_data.target_step_list[self.action_data.target_step_index])
+ self.step_start_value_widget.setValue(
+ self.action_data.target_step_list[self.action_data.target_step_index]
+ )
self.slider_widget.setTickMarks(self.action_data.target_step_list)
self._update_start_value()
-
-
def _create_step_widget(self, id, value):
- ''' creates a step widget for the step value '''
+ """creates a step widget for the step value"""
widget = StepWidget(id, value)
widget.valueChanged.connect(self._step_value_changed)
widget.defaultChanged.connect(self._step_default_changed)
widget.deleteRequested.connect(self._step_delete)
return widget
-
def _ensure_step_widgets(self):
- ''' ensures we have a widget for each defined step value '''
+ """ensures we have a widget for each defined step value"""
widgets = []
self.target_step_index_map.clear()
bg = StepWidgetGroup()
@@ -1547,36 +1601,27 @@ def _ensure_step_widgets(self):
# redo the layout in sorted order if needed
self._update_step_widget_layout()
-
-
-
-
-
def _update_step_widget_layout(self):
- ''' ensures the step widgets appear in the step sort order '''
+ """ensures the step widgets appear in the step sort order"""
gremlin.ui.ui_common.clear_layout(self.step_widget_layout)
row = 0
col = 0
max_col = 5
for index, widget in self.target_step_index_map.items():
self.step_widget_layout.addWidget(widget, row, col)
- col+=1
+ col += 1
if col > max_col:
- row+=1
+ row += 1
col = 0
-
-
-
-
@QtCore.Slot()
def _add_step(self):
- ''' adds new step '''
+ """adds new step"""
data = self.action_data.target_step_list
count = len(data)
if count >= 20:
# syslog = logging.getLogger("system")
- syslog.error(f"VJOY: unable to add more than 20 steps.")
+ syslog.error("VJOY: unable to add more than 20 steps.")
return
if count > 1:
v1 = data[0]
@@ -1598,8 +1643,9 @@ def _add_step(self):
@QtCore.Slot()
def _step_count_changed(self):
- ''' called when the count of steps changes '''
+ """called when the count of steps changes"""
import random
+
target_count = self.step_count_widget.value()
current_count = len(self.action_data.target_step_list)
if target_count < current_count:
@@ -1609,16 +1655,15 @@ def _step_count_changed(self):
else:
count = len(self.action_data.target_step_list)
while count < target_count:
- value = random.randrange(-100,100) / 100
+ value = random.randrange(-100, 100) / 100
while value in self.action_data.target_step_list:
- value = random.randrange(-100,100) / 100
-
+ value = random.randrange(-100, 100) / 100
+
self.action_data.target_step_list.append(value)
count = len(self.action_data.target_step_list)
self.update_steps()
-
@QtCore.Slot()
def _step_start_index_changed(self):
index = self.step_start_index_widget.value()
@@ -1628,9 +1673,11 @@ def _step_start_index_changed(self):
with QtCore.QSignalBlocker(self.step_start_index_widget):
self.step_start_index_widget.setValue(new_index)
index = new_index
-
- self.action_data.target_step_start_index = index-1
- value = self.action_data.target_step_list[self.action_data.target_step_start_index]
+
+ self.action_data.target_step_start_index = index - 1
+ value = self.action_data.target_step_list[
+ self.action_data.target_step_start_index
+ ]
self.step_start_value_widget.setText(f"{value:0.3f}")
self.update_steps()
@@ -1642,9 +1689,8 @@ def _step_list_changed(self):
self.action_data.target_step_list = steps
self.update_steps()
-
@QtCore.Slot(int, float)
- def _step_value_changed(self, index : int, value: float):
+ def _step_value_changed(self, index: int, value: float):
# reorder the widgets if the value changed
self.action_data.target_step_list[index] = value
@@ -1652,56 +1698,59 @@ def _step_value_changed(self, index : int, value: float):
# re-order the widgets based on the sorted steps
self.update_steps()
-
-
-
@QtCore.Slot(int, bool)
- def _step_default_changed(self, index : int, flag: bool):
+ def _step_default_changed(self, index: int, flag: bool):
if flag:
self.action_data.target_step_start_index = index
self._update_start_value()
-
@QtCore.Slot(int)
def _step_delete(self, index: int):
- ''' delete requested '''
+ """delete requested"""
msgbox = gremlin.ui.ui_common.ConfirmBox(f"Delete step {index}?")
result = msgbox.show()
if result == QtWidgets.QMessageBox.StandardButton.Ok:
del self.action_data.target_step_list[index]
- self._ensure_step_widgets() # redo the layout
+ self._ensure_step_widgets() # redo the layout
self.slider_widget.setTickMarks(self.action_data.target_step_list)
-
-
def _create_step_ui(self):
- ''' creates the axis merging UI components '''
- # stepped output
+ """creates the axis merging UI components"""
+ # stepped output
- self.target_step_index_map = {} # map of step index to step widget ID keyed by index in the step list
+ self.target_step_index_map = {} # map of step index to step widget ID keyed by index in the step list
self.container_stepped_widget = QtWidgets.QWidget()
- self.container_stepped_layout = QtWidgets.QVBoxLayout(self.container_stepped_widget)
+ self.container_stepped_layout = QtWidgets.QVBoxLayout(
+ self.container_stepped_widget
+ )
self.step_value_container_widget = QtWidgets.QWidget()
- self.step_value_container_layout = QtWidgets.QHBoxLayout(self.step_value_container_widget)
+ self.step_value_container_layout = QtWidgets.QHBoxLayout(
+ self.step_value_container_widget
+ )
self.progression_container_widget = QtWidgets.QWidget()
- self.progression_container_layout = QtWidgets.QHBoxLayout(self.progression_container_widget)
+ self.progression_container_layout = QtWidgets.QHBoxLayout(
+ self.progression_container_widget
+ )
self.step_start_index_widget = gremlin.ui.ui_common.QIntLineEdit()
self.step_count_widget = gremlin.ui.ui_common.QIntLineEdit()
- self.step_count_widget.setRange(0,100)
+ self.step_count_widget.setRange(0, 100)
self.step_count_widget.valueChanged.connect(self._step_count_changed)
self.step_start_value_widget = gremlin.ui.ui_common.QFloatLineEdit()
self.step_start_value_widget.setReadOnly(True)
- value = self.action_data.target_step_list[self.action_data.target_step_start_index]
+ value = self.action_data.target_step_list[
+ self.action_data.target_step_start_index
+ ]
self.step_start_value_widget.setText(f"{value:0.3f}")
-
- self.step_start_index_widget.setRange(1,100)
- self.step_start_index_widget.valueChanged.connect(self._step_start_index_changed)
+ self.step_start_index_widget.setRange(1, 100)
+ self.step_start_index_widget.valueChanged.connect(
+ self._step_start_index_changed
+ )
self.step_list_widget = gremlin.ui.ui_common.QDataLineEdit()
self.step_list_widget.lostFocus.connect(self._step_list_changed)
@@ -1711,7 +1760,7 @@ def _create_step_ui(self):
self.add_step_widget.clicked.connect(self._add_step)
self.slider_widget = gremlin.ui.qsliderwidget.QSliderWidget()
- self.slider_widget.setRange(-1,1)
+ self.slider_widget.setRange(-1, 1)
self.slider_widget.setReadOnly(True)
self.slider_widget.setDrawHandles(False)
self.slider_widget.setMinimumWidth(200)
@@ -1727,23 +1776,34 @@ def _create_step_ui(self):
self.normalize_widget.clicked.connect(self._normalize_steps)
self.low_progression_widget = QtWidgets.QPushButton("Low")
- self.low_progression_widget.setToolTip("Steps follow a linear progression from the low range. Most steps are in the lower half.")
+ self.low_progression_widget.setToolTip(
+ "Steps follow a linear progression from the low range. Most steps are in the lower half."
+ )
self.low_progression_widget.clicked.connect(self._low_progression_steps)
self.high_progression_widget = QtWidgets.QPushButton("High")
- self.high_progression_widget.setToolTip("Steps follow a linear progression from the high range. Most steps are in the higher half.")
+ self.high_progression_widget.setToolTip(
+ "Steps follow a linear progression from the high range. Most steps are in the higher half."
+ )
self.high_progression_widget.clicked.connect(self._high_progression_steps)
self.cubic_progression_low_widget = QtWidgets.QPushButton("Geometric Low")
- self.cubic_progression_low_widget.setToolTip("Steps follow a geometric (log) progression.")
- self.cubic_progression_low_widget.clicked.connect(self._geometric_progression_steps_low)
+ self.cubic_progression_low_widget.setToolTip(
+ "Steps follow a geometric (log) progression."
+ )
+ self.cubic_progression_low_widget.clicked.connect(
+ self._geometric_progression_steps_low
+ )
self.cubic_progression_high_widget = QtWidgets.QPushButton("Geometric High")
- self.cubic_progression_high_widget.setToolTip("Steps follow a geometric (log) progression.")
- self.cubic_progression_high_widget.clicked.connect(self._geometric_progression_steps_high)
-
+ self.cubic_progression_high_widget.setToolTip(
+ "Steps follow a geometric (log) progression."
+ )
+ self.cubic_progression_high_widget.clicked.connect(
+ self._geometric_progression_steps_high
+ )
- #self.step_value_container_layout.addWidget(self.grab_widget)
+ # self.step_value_container_layout.addWidget(self.grab_widget)
self.step_value_container_layout.addWidget(self.add_step_widget)
self.step_value_container_layout.addWidget(QtWidgets.QLabel("Start index:"))
self.step_value_container_layout.addWidget(self.step_start_index_widget)
@@ -1751,7 +1811,7 @@ def _create_step_ui(self):
self.step_value_container_layout.addWidget(self.step_start_value_widget)
self.step_value_container_layout.addWidget(QtWidgets.QLabel("Steps:"))
self.step_value_container_layout.addWidget(self.step_count_widget)
-
+
self.step_value_container_layout.addStretch()
self.progression_container_layout.addWidget(QtWidgets.QLabel("Distribution:"))
@@ -1764,8 +1824,8 @@ def _create_step_ui(self):
self.step_widget_container = QtWidgets.QWidget()
self.step_widget_layout = QtWidgets.QGridLayout(self.step_widget_container)
- self.step_widget_layout.addWidget(QtWidgets.QWidget(),0,6)
- self.step_widget_layout.setColumnStretch(6,2)
+ self.step_widget_layout.addWidget(QtWidgets.QWidget(), 0, 6)
+ self.step_widget_layout.setColumnStretch(6, 2)
self.stepped_selector_device_widget = gremlin.ui.ui_common.NoWheelComboBox()
self.stepped_selector_input_widget = gremlin.ui.ui_common.NoWheelComboBox()
@@ -1775,41 +1835,49 @@ def _create_step_ui(self):
device_widget = QtWidgets.QWidget()
device_layout = QtWidgets.QGridLayout(device_widget)
- device_layout.addWidget(QtWidgets.QLabel("Down Device:"),0,0)
- device_layout.addWidget(self.stepped_selector_device_widget,0,1)
- device_layout.addWidget(listen_widget,0,3)
- device_layout.addWidget(QtWidgets.QLabel(" "),0,4)
- device_layout.addWidget(QtWidgets.QLabel("Down Input:"),1,0)
- device_layout.addWidget(self.stepped_selector_input_widget,1,1)
- device_layout.setColumnStretch(4,2)
+ device_layout.addWidget(QtWidgets.QLabel("Down Device:"), 0, 0)
+ device_layout.addWidget(self.stepped_selector_device_widget, 0, 1)
+ device_layout.addWidget(listen_widget, 0, 3)
+ device_layout.addWidget(QtWidgets.QLabel(" "), 0, 4)
+ device_layout.addWidget(QtWidgets.QLabel("Down Input:"), 1, 0)
+ device_layout.addWidget(self.stepped_selector_input_widget, 1, 1)
+ device_layout.setColumnStretch(4, 2)
self.container_stepped_layout.addWidget(device_widget)
self.container_stepped_layout.addWidget(self.step_value_container_widget)
self.container_stepped_layout.addWidget(self.progression_container_widget)
self.container_stepped_layout.addWidget(self.step_widget_container)
- #self.container_stepped_layout.addWidget(self.container_stepped_widget)
-
-
- self.stepped_selector_device_widget.currentIndexChanged.connect(self._stepped_device_changed_cb)
- self.stepped_selector_input_widget.currentIndexChanged.connect(self._stepped_input_changed_cb)
+ # self.container_stepped_layout.addWidget(self.container_stepped_widget)
+ self.stepped_selector_device_widget.currentIndexChanged.connect(
+ self._stepped_device_changed_cb
+ )
+ self.stepped_selector_input_widget.currentIndexChanged.connect(
+ self._stepped_input_changed_cb
+ )
- self.stepped_device_map = {} # holds the device information keyed by device_id (str)
- self.stepped_input_map = {} # holds the list of buttons for the given device by device_id(str)
- devices = sorted(joystick_handling.button_input_devices(),key=lambda x: x.name)
+ self.stepped_device_map = {} # holds the device information keyed by device_id (str)
+ self.stepped_input_map = {} # holds the list of buttons for the given device by device_id(str)
+ devices = sorted(joystick_handling.button_input_devices(), key=lambda x: x.name)
# default device
- device_guid = self.action_data.hardware_device_guid if self.action_data.stepped_device_id is None else self.action_data.stepped_device_id
+ device_guid = (
+ self.action_data.hardware_device_guid
+ if self.action_data.stepped_device_id is None
+ else self.action_data.stepped_device_id
+ )
device_index = None
current_index = 0
for dev in devices:
self.stepped_device_map[dev.device_id] = dev
button_list = {}
- for input_id in range(1, dev.button_count+1):
- if dev.device_guid == self.action_data.hardware_device_guid and \
- input_id == self.action_data.hardware_input_id:
+ for input_id in range(1, dev.button_count + 1):
+ if (
+ dev.device_guid == self.action_data.hardware_device_guid
+ and input_id == self.action_data.hardware_input_id
+ ):
# skip self as a possible input
continue
button_list[input_id] = f"Button {input_id}"
@@ -1819,51 +1887,48 @@ def _create_step_ui(self):
self.stepped_selector_device_widget.addItem(dev.name, dev.device_id)
if device_index is None and dev.device_id == device_guid:
device_index = current_index
- current_index +=1
+ current_index += 1
if device_index is not None:
self.stepped_selector_device_widget.setCurrentIndex(device_index)
-
self.main_layout.addWidget(self.container_stepped_widget)
self._enable_axis_tracking()
self.update_steps()
-
def get_axis_value(self):
- ''' gets the current axis value'''
- return gremlin.joystick_handling.get_curved_axis(self.action_data.hardware_device_guid, self.action_data.hardware_input_id)
-
+ """gets the current axis value"""
+ return gremlin.joystick_handling.get_curved_axis(
+ self.action_data.hardware_device_guid, self.action_data.hardware_input_id
+ )
+
def _update_start_value(self):
- ''' updates the start value widget repeater '''
+ """updates the start value widget repeater"""
index = self.action_data.target_step_start_index
value = self.action_data.target_step_list[index]
self.step_start_value_widget.setText(f"{value:0.3f}")
-
# check the correct widget
widget = self.target_step_index_map[index]
widget.setDefault(True)
-
@QtCore.Slot()
def _normalize_steps(self):
- ''' normalizes the steps '''
+ """normalizes the steps"""
count = len(self.action_data.target_step_list)
if count == 0:
return
elif count == 2:
- data = [-1,1]
+ data = [-1, 1]
elif count == 1:
data = [0]
else:
data = []
- interval = 2 / (count-1)
+ interval = 2 / (count - 1)
x = -1
for _ in range(count):
data.append(x)
x += interval
-
self.action_data.target_step_list = data
self.update_steps()
@@ -1874,7 +1939,7 @@ def _low_progression_steps(self):
if count == 0:
return
elif count == 2:
- data = [-1,1]
+ data = [-1, 1]
elif count == 1:
data = [0]
else:
@@ -1886,7 +1951,7 @@ def _low_progression_steps(self):
x -= interval
interval /= 2
self.action_data.target_step_list = data
- self.update_steps()
+ self.update_steps()
@QtCore.Slot()
def _high_progression_steps(self):
@@ -1894,7 +1959,7 @@ def _high_progression_steps(self):
if count == 0:
return
elif count == 2:
- data = [-1,1]
+ data = [-1, 1]
elif count == 1:
data = [0]
else:
@@ -1906,54 +1971,56 @@ def _high_progression_steps(self):
x += interval
interval /= 2
self.action_data.target_step_list = data
- self.update_steps()
+ self.update_steps()
@QtCore.Slot()
def _geometric_progression_steps_low(self):
self._geometric_progression(False)
-
@QtCore.Slot()
def _geometric_progression_steps_high(self):
self._geometric_progression(True)
- def _geometric_progression(self, inverted = False):
+ def _geometric_progression(self, inverted=False):
import numpy as np
+
count = len(self.action_data.target_step_list)
if count == 0:
return
elif count == 2:
- data = [-1,1]
+ data = [-1, 1]
elif count == 1:
data = [0]
else:
data = []
- progression = np.geomspace(1,10,count)
+ progression = np.geomspace(1, 10, count)
for n in progression:
- value = gremlin.util.scale_to_range(float(n), source_min = 1, source_max = 10, invert=inverted)
-
+ value = gremlin.util.scale_to_range(
+ float(n), source_min=1, source_max=10, invert=inverted
+ )
+
data.append(value)
data.sort()
-
self.action_data.target_step_list = data
- self.update_steps()
+ self.update_steps()
@QtCore.Slot()
def _grab_handler(self):
- ''' grab the min value from the axis position '''
- value = gremlin.joystick_handling.get_curved_axis(self.action_data.hardware_device_guid, self.action_data.hardware_input_id)
- if not value in self.action_data.target_step_list:
+ """grab the min value from the axis position"""
+ value = gremlin.joystick_handling.get_curved_axis(
+ self.action_data.hardware_device_guid, self.action_data.hardware_input_id
+ )
+ if value not in self.action_data.target_step_list:
self.action_data.target_step_list.append(value)
self.action_data.target_step_list.sort()
self._ensure_step_widgets()
- @QtCore.Slot()
+ @QtCore.Slot()
def _stepped_listen(self):
- ''' listens for the button to use as the down step '''
+ """listens for the button to use as the down step"""
button_press_dialog = gremlin.ui.ui_common.InputListenerWidget(
- [InputType.JoystickButton],
- return_kb_event=False
+ [InputType.JoystickButton], return_kb_event=False
)
button_press_dialog.item_selected.connect(self._update_button)
@@ -1968,23 +2035,21 @@ def _stepped_listen(self):
int(geom.x() + geom.width() / 2 - 150),
int(geom.y() + geom.height() / 2 - 75),
300,
- 150
+ 150,
)
button_press_dialog.show()
@QtCore.Slot()
- def _update_button(self, event : gremlin.event_handler.Event):
- ''' called when a button input is selected '''
+ def _update_button(self, event: gremlin.event_handler.Event):
+ """called when a button input is selected"""
hardware_index = self.stepped_selector_device_widget.findData(event.device_id)
self.stepped_selector_device_widget.setCurrentIndex(hardware_index)
input_index = self.stepped_selector_input_widget.findData(event.identifier)
self.stepped_selector_input_widget.setCurrentIndex(input_index)
-
-
@QtCore.Slot()
def _stepped_device_changed_cb(self):
- ''' stepped device changed '''
+ """stepped device changed"""
index = self.stepped_selector_device_widget.currentIndex()
device_id = self.stepped_selector_device_widget.itemData(index)
active_input_id = self.action_data.stepped_input_id
@@ -1999,63 +2064,67 @@ def _stepped_device_changed_cb(self):
first_input_id = input_id
if selected_input_index is None and input_id == active_input_id:
selected_input_index = current_index
- current_index +=1
+ current_index += 1
if selected_input_index is not None:
- index = self.stepped_selector_input_widget.findData(selected_input_index)
+ index = self.stepped_selector_input_widget.findData(
+ selected_input_index
+ )
if index != -1:
- self.stepped_selector_input_widget.setCurrentIndex(selected_input_index)
+ self.stepped_selector_input_widget.setCurrentIndex(
+ selected_input_index
+ )
else:
self.action_data.stepped_input_id = first_input_id
else:
self.action_data.stepped_input_id = first_input_id
-
self.action_data.stepped_device_id = device_id
-
-
@QtCore.Slot()
def _stepped_input_changed_cb(self):
- ''' stepped input changed '''
+ """stepped input changed"""
index = self.stepped_selector_input_widget.currentIndex()
input_id = self.stepped_selector_input_widget.itemData(index)
self.action_data.stepped_input_id = input_id
- # self.action_modified.emit()
+ # self.action_modified.emit()
def load_actions_from_input_type(self):
- ''' occurs when the type of input is changed '''
+ """occurs when the type of input is changed"""
with QtCore.QSignalBlocker(self.cb_action_list):
self.cb_action_list.clear()
actions = ()
if self.action_data.input_is_axis():
# axis can only set an axis
- actions = (VjoyAction.VJoyAxis, VjoyAction.VJoyAxisToButton, VjoyAction.VJoyMergeAxis)
-
+ actions = (
+ VjoyAction.VJoyAxis,
+ VjoyAction.VJoyAxisToButton,
+ VjoyAction.VJoyMergeAxis,
+ )
elif self.action_data.input_is_button():
# various button modes
- actions = ( VjoyAction.VJoyButton,
- VjoyAction.VJoyButtonRelease,
- VjoyAction.VJoyPulse,
- VjoyAction.VJoyToggle,
- VjoyAction.VJoyInvertAxis,
- VjoyAction.VJoySetAxis,
- VjoyAction.VJoySetAxisStepped,
- VjoyAction.VJoyRangeAxis,
- VjoyAction.VJoyMergeAxis,
- VjoyAction.VJoyEnableLocalOnly,
- VjoyAction.VJoyEnableRemoteOnly,
- VjoyAction.VJoyEnableLocal,
- VjoyAction.VJoyEnableRemote,
- VjoyAction.VJoyEnableLocalAndRemote,
- VjoyAction.VJoyDisableLocal,
- VjoyAction.VJoyDisableRemote,
- VjoyAction.VJoyToggleRemote,
- VjoyAction.VJoyEnablePairedRemote,
- VjoyAction.VJoyDisablePairedRemote,
-
+ actions = (
+ VjoyAction.VJoyButton,
+ VjoyAction.VJoyButtonRelease,
+ VjoyAction.VJoyPulse,
+ VjoyAction.VJoyToggle,
+ VjoyAction.VJoyInvertAxis,
+ VjoyAction.VJoySetAxis,
+ VjoyAction.VJoySetAxisStepped,
+ VjoyAction.VJoyRangeAxis,
+ VjoyAction.VJoyMergeAxis,
+ VjoyAction.VJoyEnableLocalOnly,
+ VjoyAction.VJoyEnableRemoteOnly,
+ VjoyAction.VJoyEnableLocal,
+ VjoyAction.VJoyEnableRemote,
+ VjoyAction.VJoyEnableLocalAndRemote,
+ VjoyAction.VJoyDisableLocal,
+ VjoyAction.VJoyDisableRemote,
+ VjoyAction.VJoyToggleRemote,
+ VjoyAction.VJoyEnablePairedRemote,
+ VjoyAction.VJoyDisablePairedRemote,
)
elif self.action_data.input_type == InputType.JoystickHat:
@@ -2063,13 +2132,15 @@ def load_actions_from_input_type(self):
actions = [VjoyAction.VJoyHat, VjoyAction.VJoyHatToButton]
else:
- log_sys_warn(f"VJOYREMAP: don't know what actions to load for input type: {self.action_data.input_type}")
+ log_sys_warn(
+ f"VJOYREMAP: don't know what actions to load for input type: {self.action_data.input_type}"
+ )
for action in actions:
self.cb_action_list.addItem(VjoyAction.to_name(action), action)
def _vjoy_device_id_changed(self, index):
- ''' occurs when the vjoy output device is changed '''
+ """occurs when the vjoy output device is changed"""
with QtCore.QSignalBlocker(self.cb_vjoy_device_selector):
device_id = self.cb_vjoy_device_selector.itemData(index)
self.action_data.vjoy_device_id = device_id
@@ -2077,9 +2148,8 @@ def _vjoy_device_id_changed(self, index):
self._update_hat_mapping()
self.notify_device_changed()
-
def _vjoy_input_id_changed(self, index):
- ''' occurs when the vjoy output input ID is changed '''
+ """occurs when the vjoy output input ID is changed"""
with QtCore.QSignalBlocker(self.cb_vjoy_input_selector):
input_id = self.cb_vjoy_input_selector.itemData(index)
self.action_data.set_input_id(input_id)
@@ -2087,12 +2157,14 @@ def _vjoy_input_id_changed(self, index):
if self.is_button_mode:
self.select_button(self.action_data.vjoy_device_id, input_id)
- #self._populate_grid(self.action_data.vjoy_device_id, input_id)
+ # self._populate_grid(self.action_data.vjoy_device_id, input_id)
self.notify_device_changed()
def refresh_grid(self):
- ''' refreshes the grid '''
- self._populate_grid(self.action_data.vjoy_device_id, self.action_data.vjoy_input_id )
+ """refreshes the grid"""
+ self._populate_grid(
+ self.action_data.vjoy_device_id, self.action_data.vjoy_input_id
+ )
def notify_device_changed(self):
state = gremlin.joystick_handling.VJoyUsageState()
@@ -2108,45 +2180,62 @@ def notify_device_changed(self):
el.profile_device_changed.emit(event)
el.icon_changed.emit(event)
-
def _update_vjoy_device_input_list(self):
- ''' loads a list of valid outputs for the current vjoy device based on the mode '''
+ """loads a list of valid outputs for the current vjoy device based on the mode"""
with QtCore.QSignalBlocker(self.cb_vjoy_input_selector):
-
self.cb_vjoy_input_selector.clear()
input_type = self._get_selector_input_type()
action_mode = self._get_action_mode()
- self.setWarning(None) # clear any warnings
+ self.setWarning(None) # clear any warnings
- if not self.action_data.vjoy_device_id in self.action_data.vjoy_map:
+ if self.action_data.vjoy_device_id not in self.action_data.vjoy_map:
self.action_data.refresh_vjoy()
- if not self.action_data.vjoy_device_id in self.action_data.vjoy_map:
- self.setWarning(f"VJOY configuration has changed and GremlinEx is unable to find the requested Vjoy device # {self.action_data.vjoy_device_id}")
+ if self.action_data.vjoy_device_id not in self.action_data.vjoy_map:
+ self.setWarning(
+ f"VJOY configuration has changed and GremlinEx is unable to find the requested Vjoy device # {self.action_data.vjoy_device_id}"
+ )
return
-
dev = self.action_data.vjoy_map[self.action_data.vjoy_device_id]
- if action_mode in (VjoyAction.VJoySetAxis, VjoyAction.VJoySetAxisStepped, VjoyAction.VJoyRangeAxis, VjoyAction.VJoyAxis, VjoyAction.VJoyInvertAxis, VjoyAction.VJoyMergeAxis):
+ if action_mode in (
+ VjoyAction.VJoySetAxis,
+ VjoyAction.VJoySetAxisStepped,
+ VjoyAction.VJoyRangeAxis,
+ VjoyAction.VJoyAxis,
+ VjoyAction.VJoyInvertAxis,
+ VjoyAction.VJoyMergeAxis,
+ ):
count = dev.axis_count
- for id in range(1, count+1):
- axis_name = dev.axis_names[id-1]
- self.cb_vjoy_input_selector.addItem(f"Axis {axis_name}",id)
- #self.cb_vjoy_input_selector.addItem(f"Axis {id} ({self.get_axis_name(id)})",id)
- elif input_type in VJoyWidget.input_type_buttons or action_mode in (VjoyAction.VJoyButton, VjoyAction.VJoyButtonRelease, VjoyAction.VJoyPulse, VjoyAction.VJoyToggle, VjoyAction.VJoyAxisToButton, VjoyAction.VJoyHatToButton):
+ for id in range(1, count + 1):
+ axis_name = dev.axis_names[id - 1]
+ self.cb_vjoy_input_selector.addItem(f"Axis {axis_name}", id)
+ # self.cb_vjoy_input_selector.addItem(f"Axis {id} ({self.get_axis_name(id)})",id)
+ elif input_type in VJoyWidget.input_type_buttons or action_mode in (
+ VjoyAction.VJoyButton,
+ VjoyAction.VJoyButtonRelease,
+ VjoyAction.VJoyPulse,
+ VjoyAction.VJoyToggle,
+ VjoyAction.VJoyAxisToButton,
+ VjoyAction.VJoyHatToButton,
+ ):
count = dev.button_count
- for id in range(1, count+1):
- self.cb_vjoy_input_selector.addItem(f"Button {id}",id)
+ for id in range(1, count + 1):
+ self.cb_vjoy_input_selector.addItem(f"Button {id}", id)
input_id = self.action_data.vjoy_input_id
if input_id < 1 or input_id > count:
- self.setWarning(f"VJOY configuration has changed and GremlinEx is unable to find the requested Vjoy button # {input_id}")
+ self.setWarning(
+ f"VJOY configuration has changed and GremlinEx is unable to find the requested Vjoy button # {input_id}"
+ )
return
elif input_type == InputType.JoystickHat:
count = dev.hat_count
- for id in range(1, count+1):
- self.cb_vjoy_input_selector.addItem(f"Hat {id}",id)
+ for id in range(1, count + 1):
+ self.cb_vjoy_input_selector.addItem(f"Hat {id}", id)
input_id = self.action_data.vjoy_input_id
if input_id < 1 or input_id > count:
- self.setWarning(f"VJOY configuration has changed and GremlinEx is unable to find the requested Vjoy hat # {input_id}")
+ self.setWarning(
+ f"VJOY configuration has changed and GremlinEx is unable to find the requested Vjoy hat # {input_id}"
+ )
return
else:
# keyboard, latched keyboard, midi and OSC
@@ -2155,12 +2244,13 @@ def _update_vjoy_device_input_list(self):
index = self.cb_vjoy_input_selector.findData(self.action_data.vjoy_input_id)
if index != -1:
self.cb_vjoy_input_selector.setCurrentIndex(index)
- self._populate_grid(self.action_data.vjoy_device_id, self.action_data.vjoy_input_id)
-
+ self._populate_grid(
+ self.action_data.vjoy_device_id, self.action_data.vjoy_input_id
+ )
@QtCore.Slot(float)
def _target_value_changed(self, value):
- ''' called when the value box changes '''
+ """called when the value box changes"""
if value.isnumeric():
value = float(value)
self.action_data.target_value = value
@@ -2168,13 +2258,12 @@ def _target_value_changed(self, value):
else:
self.target_value_valid = False
-
@QtCore.Slot(bool)
def _target_relative_changed(self, checked):
self.action_data.target_is_relative = checked
def _update_ui(self):
- ''' updates ui based on the current action requested to show/hide needed components '''
+ """updates ui based on the current action requested to show/hide needed components"""
action_data = self.action_data
@@ -2193,33 +2282,58 @@ def _update_ui(self):
exec_on_release_visible = False
paired_visible = False
- merge_visible = False
+ merge_visible = False
repeater_visible = False
stepped_visible = False
- self.chkb_auto_release_widget.setVisible(input_type in (InputType.KeyboardLatched, InputType.Keyboard, InputType.Midi, InputType.OpenSoundControl))
+ self.chkb_auto_release_widget.setVisible(
+ input_type
+ in (
+ InputType.KeyboardLatched,
+ InputType.Keyboard,
+ InputType.Midi,
+ InputType.OpenSoundControl,
+ )
+ )
- axis_repeater_visible = self.action_data.input_is_axis() #input_type == InputType.JoystickAxis
+ axis_repeater_visible = (
+ self.action_data.input_is_axis()
+ ) # input_type == InputType.JoystickAxis
if self._is_axis:
grid_visible = action == VjoyAction.VJoyAxisToButton
- range_visible = action in (VjoyAction.VJoyRangeAxis, VjoyAction.VJoyAxisToButton)
- axis_visible = not (grid_visible or range_visible) # or hardware_widget_visible)
+ range_visible = action in (
+ VjoyAction.VJoyRangeAxis,
+ VjoyAction.VJoyAxisToButton,
+ )
+ axis_visible = not (
+ grid_visible or range_visible
+ ) # or hardware_widget_visible)
merge_visible = action == VjoyAction.VJoyMergeAxis and axis_visible
repeater_visible = True
elif input_type in VJoyWidget.input_type_buttons:
pulse_visible = action == VjoyAction.VJoyPulse
- start_visible = action in (VjoyAction.VJoyButton, VjoyAction.VJoyButtonRelease)
- if action in (VjoyAction.VJoyPulse, VjoyAction.VJoyButton, VjoyAction.VJoyToggle, VjoyAction.VJoyButtonRelease):
+ start_visible = action in (
+ VjoyAction.VJoyButton,
+ VjoyAction.VJoyButtonRelease,
+ )
+ if action in (
+ VjoyAction.VJoyPulse,
+ VjoyAction.VJoyButton,
+ VjoyAction.VJoyToggle,
+ VjoyAction.VJoyButtonRelease,
+ ):
grid_visible = True
start_visible = True
paired_visible = action == VjoyAction.VJoyButton
- exec_on_release_visible = action_data.input_type in VJoyWidget.input_type_buttons
+ exec_on_release_visible = (
+ action_data.input_type in VJoyWidget.input_type_buttons
+ )
elif input_type == InputType.JoystickHat:
if action == VjoyAction.VJoyHatToButton:
- pulse_visible = True
+ pulse_visible = True
grid_visible = False
hat_visible = True
show_grid_visible = False
@@ -2227,9 +2341,6 @@ def _update_ui(self):
input_selector_visible = not hat_visible
pass
-
-
-
match action:
case VjoyAction.VJoyRangeAxis:
range_visible = True
@@ -2245,7 +2356,6 @@ def _update_ui(self):
start_visible = True
grid_visible = True
-
self.container_repeater_widget.setVisible(repeater_visible)
is_command = VjoyAction.is_command(action)
@@ -2300,9 +2410,9 @@ def _update_ui(self):
self.sb_start_value.setEnabled(start_value_enabled)
def _action_mode_changed(self, index):
- ''' called when the drop down value changes '''
+ """called when the drop down value changes"""
with QtCore.QSignalBlocker(self.cb_action_list):
- action : VjoyAction = self.cb_action_list.itemData(index)
+ action: VjoyAction = self.cb_action_list.itemData(index)
self.action_data.action_mode = action
self.action_data.input_id = self.action_data.get_input_id()
self._update_ui()
@@ -2310,38 +2420,38 @@ def _action_mode_changed(self, index):
self.notify_device_changed()
def _get_action_mode(self):
- ''' returns the action mode '''
+ """returns the action mode"""
index = self.cb_action_list.currentIndex()
action = self.cb_action_list.itemData(index)
return action
-
def _pulse_value_changed(self, value):
- ''' called when the pulse value changes '''
+ """called when the pulse value changes"""
if value >= 0:
self.action_data.pulse_delay = value
-
def _start_changed(self, rb):
- ''' called when the start mode is changed '''
+ """called when the start mode is changed"""
id = self.start_button_group.checkedId()
self.action_data.start_pressed = id == 1
-
-
def _create_input_grid(self):
- ''' create a grid of buttons for easy selection'''
+ """create a grid of buttons for easy selection"""
- if not self.action_data.vjoy_device_id in self.action_data.vjoy_map:
- self.action_data.refresh_vjoy()
- if not self.action_data.vjoy_device_id in self.action_data.vjoy_map:
- gremlin.ui.ui_common.MessageBox(prompt=f"VJOY configuration has changed and GremlinEx is unable to find the requested Vjoy device # {self.action_data.vjoy_device_id}")
- return
+ if self.action_data.vjoy_device_id not in self.action_data.vjoy_map:
+ self.action_data.refresh_vjoy()
+ if self.action_data.vjoy_device_id not in self.action_data.vjoy_map:
+ gremlin.ui.ui_common.MessageBox(
+ prompt=f"VJOY configuration has changed and GremlinEx is unable to find the requested Vjoy device # {self.action_data.vjoy_device_id}"
+ )
+ return
grid_visible = self.action_data.grid_visible
if self.grid_visible_widget is None:
self.grid_visible_widget = QtWidgets.QCheckBox("Show button grid")
- self.grid_visible_widget.setToolTip("Sets the button grid visibility, use ctrl+ to enable/disable globally")
+ self.grid_visible_widget.setToolTip(
+ "Sets the button grid visibility, use ctrl+ to enable/disable globally"
+ )
self.grid_visible_widget.clicked.connect(self._grid_visible_cb)
self.grid_visible_container_layout.addWidget(self.grid_visible_widget)
@@ -2350,7 +2460,6 @@ def _create_input_grid(self):
self.button_grid_widget = QtWidgets.QWidget()
-
# link all radio buttons
self.button_group = QtWidgets.QButtonGroup()
self.button_group.buttonClicked.connect(self._select_changed)
@@ -2358,7 +2467,6 @@ def _create_input_grid(self):
self.active_id = -1
-
vjoy_device_id = self.action_data.vjoy_device_id
input_type = self._get_selector_input_type()
dev = self.action_data.vjoy_map[vjoy_device_id]
@@ -2374,26 +2482,25 @@ def _create_input_grid(self):
vjoy_device_id = dev.vjoy_id
input_type = self.action_data.input_type
-
- for id in range(1, count+1):
+ for id in range(1, count + 1):
# container for the vertical box
v_cont = QtWidgets.QWidget()
- #v_cont.setFixedWidth(32)
+ # v_cont.setFixedWidth(32)
v_box = QtWidgets.QVBoxLayout(v_cont)
- v_box.setContentsMargins(0,0,0,5)
+ v_box.setContentsMargins(0, 0, 0, 5)
v_box.setAlignment(QtCore.Qt.AlignCenter)
# line 1
h_cont = QtWidgets.QWidget()
h_cont.setFixedWidth(36)
h_box = QtWidgets.QHBoxLayout(h_cont)
- h_box.setContentsMargins(0,0,0,0)
+ h_box.setContentsMargins(0, 0, 0, 0)
h_box.setAlignment(QtCore.Qt.AlignCenter)
cb = gremlin.ui.ui_common.QDataRadioButton()
self.button_group.addButton(cb)
self.button_group.setId(cb, id)
- cb.data = id # data has the button id
+ cb.data = id # data has the button id
name = str(id)
h_box.addWidget(cb)
@@ -2403,16 +2510,14 @@ def _create_input_grid(self):
line2_cont = GridClickWidget(vjoy_device_id, input_type, id)
line2_cont.setFixedWidth(36)
h_box = QtWidgets.QHBoxLayout(line2_cont)
- h_box.setContentsMargins(0,0,0,0)
+ h_box.setContentsMargins(0, 0, 0, 0)
h_box.setSpacing(0)
-
icon_lbl = QtWidgets.QLabel()
lbl = QtWidgets.QLabel(name)
lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
-
self.icon_map[id] = icon_lbl
h_box.addWidget(icon_lbl)
@@ -2421,16 +2526,14 @@ def _create_input_grid(self):
line2_cont.clicked.connect(self._grid_button_clicked)
-
grid.addWidget(v_cont, row, col)
- col+=1
+ col += 1
if col == max_col:
- row+=1
- col=0
+ row += 1
+ col = 0
self.main_layout.addWidget(self.button_grid_widget)
-
@QtCore.Slot(bool)
def _grid_visible_cb(self, checked):
self.action_data.grid_visible = checked
@@ -2443,11 +2546,10 @@ def _grid_visible_cb(self, checked):
@QtCore.Slot(bool)
def grid_visible_changed(self, visible):
- ''' global grid visible change event '''
+ """global grid visible change event"""
self.action_data.grid_visible = visible
self._update_ui()
-
@QtCore.Slot()
def _grid_button_clicked(self):
sender = self.sender()
@@ -2458,14 +2560,18 @@ def _grid_button_clicked(self):
popup = GridPopupWindow(vjoy_device_id, input_type, vjoy_input_id)
popup.exec()
-
- def select_button(self, vjoy_id, button_id, emit = False):
- ''' selects a button '''
-
+ def select_button(self, vjoy_id, button_id, emit=False):
+ """selects a button"""
if self.active_id != -1:
# clear the old button if it was previously selected
- self.usage_state.set_usage_state(vjoy_id, self.active_id, state = False, action = self.action_data, emit = False)
+ self.usage_state.set_usage_state(
+ vjoy_id,
+ self.active_id,
+ state=False,
+ action=self.action_data,
+ emit=False,
+ )
if self.active_id == button_id:
# already selected
@@ -2477,7 +2583,7 @@ def select_button(self, vjoy_id, button_id, emit = False):
# update the selector
with QtCore.QSignalBlocker(self.cb_vjoy_input_selector):
- self.cb_vjoy_input_selector.setCurrentIndex(button_id-1)
+ self.cb_vjoy_input_selector.setCurrentIndex(button_id - 1)
# update the grid
if button_id in self._grid_widgets:
@@ -2485,34 +2591,32 @@ def select_button(self, vjoy_id, button_id, emit = False):
with QtCore.QSignalBlocker(cb):
cb.setChecked(True)
- self.usage_state.set_usage_state(vjoy_id, self.active_id, state = True, action = self.action_data, emit=True)
+ self.usage_state.set_usage_state(
+ vjoy_id, self.active_id, state=True, action=self.action_data, emit=True
+ )
# update the UI when a state change occurs
if emit:
self.notify_device_changed()
-
def _select_changed(self, rb):
# called when a button is toggled
vjoy_id = self.action_data.vjoy_device_id
button_id = self.button_group.checkedId()
self.select_button(vjoy_id, button_id)
-
-
def _populate_ui(self):
"""Populates the UI components."""
# Get the appropriate vjoy device identifier
vjoy_dev_id = 0
- #log_sys(f"populate vjoy data for action id: {self.action_data.action_id} action mode: {self.action_data.action_mode} vjoy: {self.action_data.vjoy_device_id}")
+ # log_sys(f"populate vjoy data for action id: {self.action_data.action_id} action mode: {self.action_data.action_mode} vjoy: {self.action_data.vjoy_device_id}")
if self.action_data.vjoy_device_id not in [0, None]:
vjoy_dev_id = self.action_data.vjoy_device_id
# Get the input type which can change depending on the container used
input_type = self.action_data.input_type
-
if self.action_data.parent.tag == "hat_buttons":
input_type = InputType.JoystickButton
@@ -2543,8 +2647,6 @@ def _populate_ui(self):
is_button_mode = False
button_id = None
-
-
try:
with QtCore.QSignalBlocker(self.cb_vjoy_device_selector):
index = self.cb_vjoy_device_selector.findData(vjoy_dev_id)
@@ -2555,10 +2657,9 @@ def _populate_ui(self):
if index != -1:
self.cb_vjoy_input_selector.setCurrentIndex(index)
-
index = self.cb_action_list.findData(self.action_data.action_mode)
if index == -1:
- #log_sys_warn(f"Mode not found in drop down: {self.action_data.action_mode.name} - resetting to default mode")
+ # log_sys_warn(f"Mode not found in drop down: {self.action_data.action_mode.name} - resetting to default mode")
self.action_data.action_mode = self.cb_action_list.itemData(0)
index = 0
else:
@@ -2566,7 +2667,6 @@ def _populate_ui(self):
is_axis = self._is_axis
if is_axis and self.action_data.action_mode == VjoyAction.VJoyAxis:
-
with QtCore.QSignalBlocker(self.reverse_checkbox):
self.reverse_checkbox.setChecked(self.action_data.reverse)
@@ -2609,7 +2709,6 @@ def _populate_ui(self):
else:
self.rb_start_released.setChecked(True)
-
with QtCore.QSignalBlocker(self.sb_button_range_low):
self.sb_button_range_low.setValue(self.action_data.range_low)
@@ -2617,18 +2716,16 @@ def _populate_ui(self):
self.sb_button_range_high.setValue(self.action_data.range_high)
with QtCore.QSignalBlocker(self.chkb_exec_on_release):
- self.chkb_exec_on_release.setChecked(self.action_data.exec_on_release)
+ self.chkb_exec_on_release.setChecked(
+ self.action_data.exec_on_release
+ )
with QtCore.QSignalBlocker(self.chkb_exec_on_release):
- self.chkb_ignore_release.setChecked(self.action_data.ignore_release)
+ self.chkb_ignore_release.setChecked(self.action_data.ignore_release)
with QtCore.QSignalBlocker(self.chkb_paired):
self.chkb_paired.setChecked(self.action_data.paired)
-
-
-
-
# # populate hardware devices if in merge mode
# self._populate_hardware()
# self._populate_hardware_axis()
@@ -2638,15 +2735,14 @@ def _populate_ui(self):
self._populate_grid(vjoy_dev_id, button_id)
self._update_vjoy_device_input_list()
-
if is_button_mode:
- self.select_button(vjoy_dev_id, vjoy_input_id, emit = False)
+ self.select_button(vjoy_dev_id, vjoy_input_id, emit=False)
self._update_ui()
except gremlin.error.GremlinError as e:
util.display_error(
- f"A needed vJoy device is not accessible: {e}\n\n" +
+ f"A needed vJoy device is not accessible: {e}\n\n"
"Default values have been set for the input, but they are "
"not what has been specified."
)
@@ -2658,7 +2754,9 @@ def _axis_reverse_changed(self):
@QtCore.Slot()
def _axis_mode_changed(self):
- self.action_data.axis_mode = 'absolute' if self.absolute_checkbox.isChecked() else "relative"
+ self.action_data.axis_mode = (
+ "absolute" if self.absolute_checkbox.isChecked() else "relative"
+ )
@QtCore.Slot()
def _axis_scaling_changed(self):
@@ -2673,7 +2771,6 @@ def _axis_start_value_enabled(self, checked):
self.action_data.axis_start_value_enabled = checked
self._update_ui()
-
@QtCore.Slot()
def _axis_range_high_changed(self):
self.action_data.range_high = self.sb_axis_range_high_widget.value()
@@ -2693,7 +2790,7 @@ def _button_range_high_changed(self):
@QtCore.Slot()
def _button_to_axis_value_changed(self):
self.action_data.target_value = self.sb_button_to_axis_value.value()
-
+
@QtCore.Slot()
def _b_range_reset_clicked(self, value):
self.sb_button_range_low.setValue(-1.0)
@@ -2746,15 +2843,14 @@ def _ignore_release_changed(self, checked):
@QtCore.Slot(bool)
def _paired_changed(self, checked):
- self.action_data.paired = checked # self.chkb_paired.isChecked()
+ self.action_data.paired = checked # self.chkb_paired.isChecked()
@QtCore.Slot(bool)
def _autorelease_changed(self, checked):
self.action_data.auto_release = checked
-
def _populate_grid(self, device_id, button_id):
- ''' updates the usage grid based on current VJOY mappings '''
+ """updates the usage grid based on current VJOY mappings"""
used_pixmap = load_pixmap("used.png")
unused_pixmap = load_pixmap("unused.png")
@@ -2764,7 +2860,7 @@ def _populate_grid(self, device_id, button_id):
id = self.button_group.id(cb)
self._grid_widgets[id] = cb
- used = self.usage_state.get_usage_state(device_id,id)
+ used = self.usage_state.get_usage_state(device_id, id)
if id == button_id:
with QtCore.QSignalBlocker(cb):
@@ -2774,11 +2870,7 @@ def _populate_grid(self, device_id, button_id):
lbl.setPixmap(used_pixmap if used else unused_pixmap)
-
-
-
class VJoyRemapFunctor(gremlin.base_conditions.AbstractFunctor):
-
"""Executes a remap action when called."""
def findMainWindow(self):
@@ -2789,7 +2881,7 @@ def findMainWindow(self):
return widget
return None
- def __init__(self, action_data, parent = None):
+ def __init__(self, action_data, parent=None):
super().__init__(action_data, parent)
self.vjoy_device_id = action_data.vjoy_device_id
self.vjoy_input_id = action_data.vjoy_input_id
@@ -2805,18 +2897,24 @@ def __init__(self, action_data, parent = None):
v1 = action_data.range_low
v2 = action_data.range_high
if v1 > v2:
- # swap range so v1 < v2
- v1,v2 = v2, v1
+ # swap range so v1 < v2
+ v1, v2 = v2, v1
self.range_low = v1
self.range_high = v2
-
self.exec_on_release = action_data.exec_on_release
self.paired = action_data.paired
self.needs_auto_release = self._check_for_auto_release(action_data)
- if self.action_data.hardware_input_type in (InputType.Keyboard, InputType.KeyboardLatched, InputType.Midi, InputType.OpenSoundControl):
- self.action_data.auto_release = self.action_data.auto_release or self.action_data.auto_release
+ if self.action_data.hardware_input_type in (
+ InputType.Keyboard,
+ InputType.KeyboardLatched,
+ InputType.Midi,
+ InputType.OpenSoundControl,
+ ):
+ self.action_data.auto_release = (
+ self.action_data.auto_release or self.action_data.auto_release
+ )
self.thread_running = False
self.should_stop_thread = False
self.thread_last_update = time.time()
@@ -2824,28 +2922,28 @@ def __init__(self, action_data, parent = None):
self.axis_delta_value = 0.0
self.axis_value = 0.0
self.axis_start_value = action_data.axis_start_value
- self.curve_actions = None # list of curve actions that apply to our input
+ self.curve_actions = None # list of curve actions that apply to our input
self.remote_client = input_devices.remote_client
- self.hat_position = (0,0)
- self.in_range = False # true when in axis to button mode and the axis was in range
+ self.hat_position = (0, 0)
+ self.in_range = (
+ False # true when in axis to button mode and the axis was in range
+ )
self.lock = threading.Lock()
self.step_index = self.action_data.target_step_start_index
-
def getCurveActions(self):
- ''' finds curve action siblings to this remap action '''
+ """finds curve action siblings to this remap action"""
actions = []
nodes = []
for node in self.getSiblings():
- if gremlin.base_profile._is_curve_tag(node.action.tag):
+ if gremlin.base_profile._is_curve_tag(node.action.tag):
nodes.append(node)
-
# sort the list in reverse priority order (highest prority runs first)
if nodes:
- nodes.sort(key = lambda x: x.priority)
+ nodes.sort(key=lambda x: x.priority)
nodes.reverse()
for node in nodes:
action = node.action
@@ -2853,7 +2951,7 @@ def getCurveActions(self):
return actions
def getCurveData(self, event, value):
- ''' returns active curve data that applies to the container through included response curve actions '''
+ """returns active curve data that applies to the container through included response curve actions"""
actions = self.getCurveActions()
curves = []
if actions:
@@ -2870,13 +2968,11 @@ def getCurveData(self, event, value):
return curves
def _convert_condition(self, condition):
- ''' converts a base condition to an action condition '''
+ """converts a base condition to an action condition"""
if isinstance(condition, gremlin.base_conditions.KeyboardCondition):
- return gremlin.actions.KeyboardCondition(
- condition.scan_code,
- condition.is_extended,
- condition.comparison
- )
+ return gremlin.actions.KeyboardCondition(
+ condition.scan_code, condition.is_extended, condition.comparison
+ )
elif isinstance(condition, gremlin.base_conditions.JoystickCondition):
return gremlin.actions.JoystickCondition(condition)
@@ -2889,7 +2985,6 @@ def _convert_condition(self, condition):
assert False, f"Invalid base condition to convert: {type(condition).__name__}"
-
def _create_activation_condition(self, activation_condition, target):
"""Creates activation condition objects base on the given data.
@@ -2905,37 +3000,35 @@ def _create_activation_condition(self, activation_condition, target):
conditions.append(self._convert_condition(condition))
return gremlin.actions.ActivationCondition(
- conditions,
- activation_condition.rule,
- target
+ conditions, activation_condition.rule, target
)
def shouldExecute(self, event, value, action) -> bool:
- ''' determines if the given action should execute or not: returns True if the condition is satisfied '''
+ """determines if the given action should execute or not: returns True if the condition is satisfied"""
- activation_condition : gremlin.actions.ActivationCondition = action.activation_condition
+ activation_condition: gremlin.actions.ActivationCondition = (
+ action.activation_condition
+ )
if activation_condition is None or not activation_condition.conditions:
# no condition
return True
- functor = self._create_activation_condition(activation_condition, self.action_data)
+ functor = self._create_activation_condition(
+ activation_condition, self.action_data
+ )
return gremlin.actions.ActivationCondition.rule_function[functor._rule](
[partial(c, event, value) for c in functor._conditions]
)
-
-
-
- def applyContainerCurves(self, value : float):
- ''' applies the container curve data to the curve '''
+ def applyContainerCurves(self, value: float):
+ """applies the container curve data to the curve"""
for action in self.curve_actions:
if action.curve_data:
value = action.curve_data.curve_value(value)
return value
-
@property
def reverse(self):
# axis reversed state
@@ -2947,33 +3040,48 @@ def toggle_reverse(self):
usage_data = gremlin.joystick_handling.VJoyUsageState()
value = usage_data.is_inverted(self.vjoy_device_id, self.vjoy_input_id)
usage_data.set_inverted(self.vjoy_device_id, self.vjoy_input_id, not value)
- log_sys(f"toggle reverse: {self.vjoy_device_id} {self.vjoy_input_id} new state: {self.reverse}")
+ log_sys(
+ f"toggle reverse: {self.vjoy_device_id} {self.vjoy_input_id} new state: {self.reverse}"
+ )
def latch_extra_inputs(self):
- ''' returns the list of extra devices to latch to this functor (device_guid, input_type, input_id) '''
+ """returns the list of extra devices to latch to this functor (device_guid, input_type, input_id)"""
if self.action_data.action_mode == VjoyAction.VJoyMergeAxis:
- return [(self.action_data.merge_device_guid, self.action_data.merge_input_type, self.action_data.merge_input_id)]
+ return [
+ (
+ self.action_data.merge_device_guid,
+ self.action_data.merge_input_type,
+ self.action_data.merge_input_id,
+ )
+ ]
if self.action_data.action_mode == VjoyAction.VJoySetAxisStepped:
- return [(self.action_data.stepped_device_guid, self.action_data.stepped_input_type, self.action_data.stepped_input_id)]
+ return [
+ (
+ self.action_data.stepped_device_guid,
+ self.action_data.stepped_input_type,
+ self.action_data.stepped_input_id,
+ )
+ ]
return []
-
def profile_start(self):
# setup initial state
# syslog = logging.getLogger("system")
- verbose = gremlin.config.Configuration().verbose_mode_outputs
+ gremlin.config.Configuration().verbose_mode_outputs
if self.input_type in VJoyWidget.input_type_buttons:
# set start button state
- joystick_handling.VJoyProxy()[self.vjoy_device_id].button(self.vjoy_input_id).is_pressed = self.start_pressed
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].button(
+ self.vjoy_input_id
+ ).is_pressed = self.start_pressed
if self.input_type == InputType.JoystickAxis:
# send initial axis values to the output
usage_data = gremlin.joystick_handling.VJoyUsageState()
- usage_data.set_range(self.vjoy_device_id, self.vjoy_input_id, self.range_low, self.range_high)
+ usage_data.set_range(
+ self.vjoy_device_id, self.vjoy_input_id, self.range_low, self.range_high
+ )
# print(f"Axis start value: vjoy: {self.vjoy_device_id} axis: {self.vjoy_input_id} value: {self.axis_start_value}")
-
-
match self.action_mode:
case VjoyAction.VJoyAxis:
# straight axis
@@ -2981,49 +3089,63 @@ def profile_start(self):
value = self.axis_start_value
else:
# read the current value
- fake_event = gremlin.event_handler.Event(self.hardware_input_type, self.hardware_input_id, device_guid = self.hardware_device_guid,value = 0,is_axis = True)
+ fake_event = gremlin.event_handler.Event(
+ self.hardware_input_type,
+ self.hardware_input_id,
+ device_guid=self.hardware_device_guid,
+ value=0,
+ is_axis=True,
+ )
action_value = gremlin.actions.Value(0)
curves = self.getCurveData(fake_event, action_value)
- value = self.action_data.get_filtered_axis_value(curves = curves)
+ value = self.action_data.get_filtered_axis_value(curves=curves)
value = self.action_data.get_ranged_axis_value(value)
- joystick_handling.VJoyProxy()[self.vjoy_device_id].axis(self.vjoy_input_id).value = value
- self.remote_client.send_axis(self.vjoy_device_id, self.vjoy_input_id, value)
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].axis(
+ self.vjoy_input_id
+ ).value = value
+ self.remote_client.send_axis(
+ self.vjoy_device_id, self.vjoy_input_id, value
+ )
case VjoyAction.VJoyAxisToButton:
device_guid = self.action_data.hardware_device_guid
input_id = self.action_data.hardware_input_id
value = joystick_handling.get_curved_axis(device_guid, input_id)
action_value = gremlin.actions.Value(value)
- event = gremlin.event_handler.Event(gremlin.input_types.InputType.JoystickAxis,
- device_guid = device_guid,
- identifier=input_id,
- is_axis=True,
- value = action_value)
+ event = gremlin.event_handler.Event(
+ gremlin.input_types.InputType.JoystickAxis,
+ device_guid=device_guid,
+ identifier=input_id,
+ is_axis=True,
+ value=action_value,
+ )
self.process_event(event, action_value)
- elif self.input_type == InputType.JoystickHat and self.action_mode == VjoyAction.VJoyHatToButton:
+ elif (
+ self.input_type == InputType.JoystickHat
+ and self.action_mode == VjoyAction.VJoyHatToButton
+ ):
device_guid = self.action_data.hardware_device_guid
input_id = self.action_data.hardware_input_id
value = joystick_handling.get_hat(device_guid, input_id)
if value in vjoy.vjoy.Hat.to_continuous_position:
self.hat_position = vjoy.vjoy.Hat.to_continuous_position[value]
else:
- self.hat_position = (0,0)
+ self.hat_position = (0, 0)
self.pressed_hat_buttons = {}
if self.action_mode == VjoyAction.VJoySetAxisStepped:
# initial stepped axis value
-
+
self.step_index = self.action_data.target_step_start_index
value = self.action_data.target_step_list[self.step_index]
syslog.info(f"VJOY: step mode initial value: {value:0.3f}")
- joystick_handling.VJoyProxy()[self.vjoy_device_id].axis(self.vjoy_input_id).value = value
-
-
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].axis(
+ self.vjoy_input_id
+ ).value = value
# async routine to pulse a button
def _fire_pulse(self, *args):
-
self.lock.acquire()
vjoy_device_id, vjoy_input_id, duration = args
# vjoy_device_id = args]
@@ -3037,7 +3159,7 @@ def _fire_pulse(self, *args):
button.is_pressed = False
self.remote_client.send_button(vjoy_device_id, vjoy_input_id, False)
self.lock.release()
- self.functor_complete.emit() # indicate completed
+ self.functor_complete.emit() # indicate completed
# def smooth(self, value, reverse = False, power = 3):
# '''
@@ -3062,29 +3184,27 @@ def _fire_pulse(self, *args):
# return (pow((value - v_start) / v_end - 1, power) + 1) * v_end + v_start
# return pow((value - v_start) / v_end, power) * v_end + v_start
-
- def process_event(self, event, action_value : gremlin.actions.Value, extra_data = None):
- ''' runs when a joystick event occurs like a button press or axis movement when a profile is running '''
+ def process_event(
+ self, event, action_value: gremlin.actions.Value, extra_data=None
+ ):
+ """runs when a joystick event occurs like a button press or axis movement when a profile is running"""
# if self.action_data.merged and event.is_axis:
# # merged axis data is handled by the internal hook - ignore
# return True
if event.is_axis:
# process input options and any merge and curve operation - the current value will already be curved by the input curve if one exists
-
-
if event.is_repeater:
# use the repeater value
value = event.value
else:
-
# raw_value = gremlin.joystick_handling.get_axis(self.action_data.hardware_device_guid, self.action_data.hardware_input_id)
# received = action_value.current
# get list of curves that applies to this input
curves = self.getCurveData(event, action_value)
- value = self.action_data.get_filtered_axis_value(curves = curves)
+ value = self.action_data.get_filtered_axis_value(curves=curves)
value = self.action_data.get_ranged_axis_value(value)
@@ -3096,7 +3216,7 @@ def process_event(self, event, action_value : gremlin.actions.Value, extra_data
return self._process_event(event, action_value, extra_data)
def _process_event(self, event, action_value, extra_data):
- ''' runs when a joystick even occurs like a button press or axis movement when a profile is running '''
+ """runs when a joystick even occurs like a button press or axis movement when a profile is running"""
(is_local, is_remote) = input_devices.remote_state.state
usage_data = gremlin.joystick_handling.VJoyUsageState()
verbose = gremlin.config.Configuration().verbose_mode_outputs
@@ -3106,13 +3226,13 @@ def _process_event(self, event, action_value, extra_data):
is_remote = True
is_local = False
- auto_complete = True # assume the functor completes this pass
+ auto_complete = True # assume the functor completes this pass
- #if verbose: syslog.info(f"VJOY MAPPER: local: {is_local} remote: {is_remote}")
+ # if verbose: syslog.info(f"VJOY MAPPER: local: {is_local} remote: {is_remote}")
input_type = event.getInputType()
- if event.is_axis: # self.input_type == InputType.JoystickAxis:
+ if event.is_axis: # self.input_type == InputType.JoystickAxis:
# axis response mode
value = action_value.current
@@ -3128,35 +3248,47 @@ def _process_event(self, event, action_value, extra_data):
self.in_range = True
# print (f"In range {action_value.current:0.3f} range: {r_min:0.3f} {r_max:0.3f}")
if is_local:
- joystick_handling.VJoyProxy()[self.vjoy_device_id].button(self.vjoy_input_id).is_pressed = True
+ joystick_handling.VJoyProxy()[
+ self.vjoy_device_id
+ ].button(self.vjoy_input_id).is_pressed = True
if is_remote:
- self.remote_client.send_button(self.vjoy_device_id, self.vjoy_input_id, True)
+ self.remote_client.send_button(
+ self.vjoy_device_id, self.vjoy_input_id, True
+ )
else:
if self.in_range:
# print (f"out of range {action_value.current:0.3f} range: {r_min:0.3f} {r_max:0.3f}")
if is_local:
- joystick_handling.VJoyProxy()[self.vjoy_device_id].button(self.vjoy_input_id).is_pressed = False
+ joystick_handling.VJoyProxy()[
+ self.vjoy_device_id
+ ].button(self.vjoy_input_id).is_pressed = False
if is_remote:
- self.remote_client.send_button(self.vjoy_device_id, self.vjoy_input_id, False)
+ self.remote_client.send_button(
+ self.vjoy_device_id, self.vjoy_input_id, False
+ )
self.in_range = False
-
case _:
if self.axis_mode == "absolute":
# apply any range function to the raw position
-
- if verbose: syslog.info(f"OUTPUT: send vjoy {self.vjoy_device_id} axis {self.vjoy_input_id} range: [{self.range_low:0.3f},{self.range_high:0.3f}] scale: {self.axis_scaling:0.3f} value: {value:0.3f}")
+ if verbose:
+ syslog.info(
+ f"OUTPUT: send vjoy {self.vjoy_device_id} axis {self.vjoy_input_id} range: [{self.range_low:0.3f},{self.range_high:0.3f}] scale: {self.axis_scaling:0.3f} value: {value:0.3f}"
+ )
if is_local:
- joystick_handling.VJoyProxy()[self.vjoy_device_id].axis(self.vjoy_input_id).value = value
-
-
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].axis(
+ self.vjoy_input_id
+ ).value = value
+
if is_remote:
- self.remote_client.send_axis(self.vjoy_device_id, self.vjoy_input_id, value)
+ self.remote_client.send_axis(
+ self.vjoy_device_id, self.vjoy_input_id, value
+ )
else:
- #value = -target if self.reverse else target
+ # value = -target if self.reverse else target
self.should_stop_thread = abs(event.value) < 0.05
self.axis_delta_value = value * (self.axis_scaling / 1000.0)
@@ -3165,60 +3297,79 @@ def _process_event(self, event, action_value, extra_data):
if isinstance(self.thread, threading.Thread):
self.thread.join()
auto_complete = False
- self.thread = threading.Thread(target=self.relative_axis_thread, daemon=True)
+ self.thread = threading.Thread(
+ target=self.relative_axis_thread, daemon=True
+ )
self.thread.start()
elif self.action_mode == VjoyAction.VJoyHatToButton:
position = action_value.current
pressed_positions = list(self.pressed_hat_buttons.keys())
- is_pressed = position != (0,0)
+ is_pressed = position != (0, 0)
if is_pressed:
is_pulse = self.action_data.hat_pulse_map[position]
input_id = self.action_data.hat_map[position]
sticky = self.action_data.hat_sticky
if input_id > 0:
-
if is_pulse:
if not self.lock.locked():
auto_complete = False
- threading.Timer(0.01, self._fire_pulse, [self.vjoy_device_id, input_id, self.pulse_delay/1000]).start()
+ threading.Timer(
+ 0.01,
+ self._fire_pulse,
+ [
+ self.vjoy_device_id,
+ input_id,
+ self.pulse_delay / 1000,
+ ],
+ ).start()
else:
if not sticky:
# release the prior buttons
for pressed_position in pressed_positions:
if position == pressed_position:
continue
- release_input_id = self.pressed_hat_buttons[pressed_position]
+ release_input_id = self.pressed_hat_buttons[
+ pressed_position
+ ]
if release_input_id > 0:
if is_local:
- joystick_handling.VJoyProxy()[self.vjoy_device_id].button(release_input_id).is_pressed = False
+ joystick_handling.VJoyProxy()[
+ self.vjoy_device_id
+ ].button(release_input_id).is_pressed = False
if is_remote:
- self.remote_client.send_button(self.vjoy_device_id, release_input_id, False)
+ self.remote_client.send_button(
+ self.vjoy_device_id, release_input_id, False
+ )
del self.pressed_hat_buttons[pressed_position]
# press the new button
self.pressed_hat_buttons[position] = input_id
if is_local:
- joystick_handling.VJoyProxy()[self.vjoy_device_id].button(input_id).is_pressed = True
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].button(
+ input_id
+ ).is_pressed = True
if is_remote:
- self.remote_client.send_button(self.vjoy_device_id, input_id, True)
-
+ self.remote_client.send_button(
+ self.vjoy_device_id, input_id, True
+ )
else:
for pressed_position in pressed_positions:
input_id = self.pressed_hat_buttons[pressed_position]
if input_id > 0:
if is_local:
- joystick_handling.VJoyProxy()[self.vjoy_device_id].button(input_id).is_pressed = False
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].button(
+ input_id
+ ).is_pressed = False
if is_remote:
- self.remote_client.send_button(self.vjoy_device_id, input_id, False)
+ self.remote_client.send_button(
+ self.vjoy_device_id, input_id, False
+ )
del self.pressed_hat_buttons[pressed_position]
-
-
-
self.hat_position = position
elif input_type in VJoyWidget.input_type_buttons:
@@ -3226,110 +3377,188 @@ def _process_event(self, event, action_value, extra_data):
force_remote = event.force_remote or is_paired
# determine if event should be fired based on release mode
- fire_event = (self.exec_on_release and not event.is_pressed) or (not self.exec_on_release and event.is_pressed)
+ fire_event = (self.exec_on_release and not event.is_pressed) or (
+ not self.exec_on_release and event.is_pressed
+ )
if self.action_mode == VjoyAction.VJoyButton:
# normal default behavior
- if verbose: syslog.info(f"VJOY: trigger on button press {self.vjoy_input_id} pressed: {event.is_pressed}")
+ if verbose:
+ syslog.info(
+ f"VJOY: trigger on button press {self.vjoy_input_id} pressed: {event.is_pressed}"
+ )
if self.exec_on_release:
if not event.is_pressed:
if is_local:
- joystick_handling.VJoyProxy()[self.vjoy_device_id].button(self.vjoy_input_id).is_pressed = True
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].button(
+ self.vjoy_input_id
+ ).is_pressed = True
if is_remote or is_paired:
- self.remote_client.send_button(self.vjoy_device_id, self.vjoy_input_id, True, force_remote = force_remote )
+ self.remote_client.send_button(
+ self.vjoy_device_id,
+ self.vjoy_input_id,
+ True,
+ force_remote=force_remote,
+ )
else:
-
is_pressed = action_value.is_pressed
auto_release = False
if is_pressed and not self.action_data.ignore_release:
- auto_release = event.event_type in [InputType.Keyboard, InputType.KeyboardLatched, InputType.Midi, InputType.OpenSoundControl] and self.needs_auto_release
+ auto_release = (
+ event.event_type
+ in [
+ InputType.Keyboard,
+ InputType.KeyboardLatched,
+ InputType.Midi,
+ InputType.OpenSoundControl,
+ ]
+ and self.needs_auto_release
+ )
if auto_release:
- if verbose: syslog.info(f"VjoyRemap: autorelease enabled for {str(event)}")
+ if verbose:
+ syslog.info(
+ f"VjoyRemap: autorelease enabled for {str(event)}"
+ )
input_devices.ButtonReleaseActions().register_button_release(
(self.vjoy_device_id, self.vjoy_input_id),
event,
- is_local = is_local,
- is_remote = is_remote,
- force_remote = force_remote,
- activate_on = False # released
+ is_local=is_local,
+ is_remote=is_remote,
+ force_remote=force_remote,
+ activate_on=False, # released
)
- if verbose: syslog.info(f"\t{self.vjoy_input_id} pressed: {is_pressed} ignore release: {self.action_data.ignore_release}")
- trigger = is_pressed or (not auto_release and not is_pressed) # trigger on press, or on release unless an auto-release was already registered for the release action to avoid double releases
+ if verbose:
+ syslog.info(
+ f"\t{self.vjoy_input_id} pressed: {is_pressed} ignore release: {self.action_data.ignore_release}"
+ )
+ trigger = (
+ is_pressed or (not auto_release and not is_pressed)
+ ) # trigger on press, or on release unless an auto-release was already registered for the release action to avoid double releases
if not is_pressed and self.action_data.ignore_release:
# ignore release action on press/release modes
- if verbose: syslog.info("\tignoring release")
+ if verbose:
+ syslog.info("\tignoring release")
trigger = False
-
+
if trigger:
- if verbose: syslog.info(f"\tTrigger {self.vjoy_input_id} pressed: {event.is_pressed}")
+ if verbose:
+ syslog.info(
+ f"\tTrigger {self.vjoy_input_id} pressed: {event.is_pressed}"
+ )
if is_local:
- joystick_handling.VJoyProxy()[self.vjoy_device_id].button(self.vjoy_input_id).is_pressed = is_pressed
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].button(
+ self.vjoy_input_id
+ ).is_pressed = is_pressed
if is_remote or is_paired:
- self.remote_client.send_button(self.vjoy_device_id, self.vjoy_input_id, is_pressed, force_remote = is_paired )
-
-
-
+ self.remote_client.send_button(
+ self.vjoy_device_id,
+ self.vjoy_input_id,
+ is_pressed,
+ force_remote=is_paired,
+ )
elif self.action_mode == VjoyAction.VJoyButtonRelease:
# normal default behavior
- if verbose: syslog.info(f"VJOY: trigger on button release {self.vjoy_input_id} pressed: {event.is_pressed}")
+ if verbose:
+ syslog.info(
+ f"VJOY: trigger on button release {self.vjoy_input_id} pressed: {event.is_pressed}"
+ )
if event.is_pressed:
if is_local:
- joystick_handling.VJoyProxy()[self.vjoy_device_id].button(self.vjoy_input_id).is_pressed = False
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].button(
+ self.vjoy_input_id
+ ).is_pressed = False
if is_remote or is_paired:
- self.remote_client.send_button(self.vjoy_device_id, self.vjoy_input_id, False, force_remote = is_paired )
-
-
-
+ self.remote_client.send_button(
+ self.vjoy_device_id,
+ self.vjoy_input_id,
+ False,
+ force_remote=is_paired,
+ )
elif self.action_mode == VjoyAction.VJoyToggle:
# toggle action
- if verbose: syslog.info(f"VJOY: trigger button toggle {self.vjoy_input_id} pressed: {event.is_pressed}")
+ if verbose:
+ syslog.info(
+ f"VJOY: trigger button toggle {self.vjoy_input_id} pressed: {event.is_pressed}"
+ )
if fire_event:
- if event.event_type in [InputType.JoystickButton, InputType.Keyboard] \
- and event.is_pressed:
+ if (
+ event.event_type
+ in [InputType.JoystickButton, InputType.Keyboard]
+ and event.is_pressed
+ ):
if is_local:
- button = joystick_handling.VJoyProxy()[self.vjoy_device_id].button(self.vjoy_input_id)
+ button = joystick_handling.VJoyProxy()[
+ self.vjoy_device_id
+ ].button(self.vjoy_input_id)
button.is_pressed = not button.is_pressed
if is_remote:
- self.remote_client.toggle_button(self.vjoy_device_id, self.vjoy_input_id)
-
+ self.remote_client.toggle_button(
+ self.vjoy_device_id, self.vjoy_input_id
+ )
elif self.action_mode == VjoyAction.VJoyPulse:
- if verbose: syslog.info(f"VJOY: trigger pulse {self.vjoy_input_id} pressed: {event.is_pressed}")
+ if verbose:
+ syslog.info(
+ f"VJOY: trigger pulse {self.vjoy_input_id} pressed: {event.is_pressed}"
+ )
# pulse action
if fire_event:
auto_complete = False
if not self.lock.locked():
- threading.Timer(0.01, self._fire_pulse, [self.vjoy_device_id, self.vjoy_input_id, self.pulse_delay/1000]).start()
+ threading.Timer(
+ 0.01,
+ self._fire_pulse,
+ [
+ self.vjoy_device_id,
+ self.vjoy_input_id,
+ self.pulse_delay / 1000,
+ ],
+ ).start()
elif self.action_mode == VjoyAction.VJoyInvertAxis:
# invert the specified axis
if fire_event:
self.toggle_reverse()
-
elif self.action_mode == VjoyAction.VJoySetAxis:
# set the value on the specified axis
-
if self.target_value_valid and fire_event:
if is_local:
if self.target_is_relative:
- value = joystick_handling.VJoyProxy()[self.vjoy_device_id].axis(self.vjoy_input_id).value
- value = gremlin.util.clamp(value + self.target_value, -1.0, 1.0)
+ value = (
+ joystick_handling.VJoyProxy()[self.vjoy_device_id]
+ .axis(self.vjoy_input_id)
+ .value
+ )
+ value = gremlin.util.clamp(
+ value + self.target_value, -1.0, 1.0
+ )
else:
value = self.target_value
- joystick_handling.VJoyProxy()[self.vjoy_device_id].axis(self.vjoy_input_id).value = value
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].axis(
+ self.vjoy_input_id
+ ).value = value
if is_remote:
- self.remote_client.send_axis(self.vjoy_device_id, self.vjoy_input_id, None, self.target_value)
-
+ self.remote_client.send_axis(
+ self.vjoy_device_id,
+ self.vjoy_input_id,
+ None,
+ self.target_value,
+ )
elif self.action_mode == VjoyAction.VJoyRangeAxis:
# changes the output range on the target device / axis
if fire_event:
- usage_data.set_range(self.vjoy_device_id, self.vjoy_input_id, self.range_low, self.range_high)
+ usage_data.set_range(
+ self.vjoy_device_id,
+ self.vjoy_input_id,
+ self.range_low,
+ self.range_high,
+ )
elif VjoyAction.is_command(self.action_mode):
# update remote control mode
@@ -3337,50 +3566,71 @@ def _process_event(self, event, action_value, extra_data):
remote_state.mode = self.action_mode
elif self.action_mode == VjoyAction.VJoySetAxisStepped:
- if event.device_guid != self.action_data.hardware_device_guid and event.device_guid != self.action_data.stepped_device_guid:
+ if (
+ event.device_guid != self.action_data.hardware_device_guid
+ and event.device_guid != self.action_data.stepped_device_guid
+ ):
return True
trigger = False
index = self.step_index
- if (event.is_pressed and not self.action_data.exec_on_release) or (not event.is_pressed and self.action_data.exec_on_release):
- if event.device_guid == self.action_data.hardware_device_guid and event.identifier == self.action_data.hardware_input_id:
+ if (event.is_pressed and not self.action_data.exec_on_release) or (
+ not event.is_pressed and self.action_data.exec_on_release
+ ):
+ if (
+ event.device_guid == self.action_data.hardware_device_guid
+ and event.identifier == self.action_data.hardware_input_id
+ ):
# up direction
- syslog.info(f"Step up")
- index +=1
+ syslog.info("Step up")
+ index += 1
trigger = True
- elif event.device_guid == self.action_data.stepped_device_guid and event.identifier == self.action_data.stepped_input_id:
+ elif (
+ event.device_guid == self.action_data.stepped_device_guid
+ and event.identifier == self.action_data.stepped_input_id
+ ):
# down direction
- syslog.info(f"Step down")
+ syslog.info("Step down")
index -= 1
trigger = True
if trigger:
- index = gremlin.util.clamp(index,0,len(self.action_data.target_step_list)-1)
+ index = gremlin.util.clamp(
+ index, 0, len(self.action_data.target_step_list) - 1
+ )
value = self.action_data.target_step_list[index]
- joystick_handling.VJoyProxy()[self.vjoy_device_id].axis(self.vjoy_input_id).value = value
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].axis(
+ self.vjoy_input_id
+ ).value = value
self.step_index = index
syslog.info(f"Step: Index: {index} value: {value:0.3f}")
-
else:
# basic handling of the button
if fire_event:
if is_local:
- joystick_handling.VJoyProxy()[self.vjoy_device_id].button(self.vjoy_input_id).is_pressed = action_value.current
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].button(
+ self.vjoy_input_id
+ ).is_pressed = action_value.current
if is_remote:
- self.remote_client.send_button(self.vjoy_device_id, self.vjoy_input_id, action_value.current)
-
-
+ self.remote_client.send_button(
+ self.vjoy_device_id,
+ self.vjoy_input_id,
+ action_value.current,
+ )
elif input_type == InputType.JoystickHat:
if is_local:
- joystick_handling.VJoyProxy()[self.vjoy_device_id].hat(self.vjoy_input_id).direction = action_value.current
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].hat(
+ self.vjoy_input_id
+ ).direction = action_value.current
if is_remote:
- self.remote_client.send_hat(self.vjoy_device_id, self.vjoy_input_id, action_value.current)
-
+ self.remote_client.send_hat(
+ self.vjoy_device_id, self.vjoy_input_id, action_value.current
+ )
if auto_complete:
- self.functor_complete.emit() # indicate completed
+ self.functor_complete.emit() # indicate completed
return True
def relative_axis_thread(self):
@@ -3399,28 +3649,29 @@ def relative_axis_thread(self):
return
self.axis_value = max(
- -1.0,
- min(1.0, self.axis_value + self.axis_delta_value)
+ -1.0, min(1.0, self.axis_value + self.axis_delta_value)
)
if is_local:
vjoy_dev.axis(self.vjoy_input_id).value = self.axis_value
if is_remote:
- self.remote_client.send_axis(self.vjoy_device_id, self.vjoy_input_id, self.axis_value)
+ self.remote_client.send_axis(
+ self.vjoy_device_id, self.vjoy_input_id, self.axis_value
+ )
- if self.should_stop_thread and \
- self.thread_last_update + 1.0 < time.time():
+ if (
+ self.should_stop_thread
+ and self.thread_last_update + 1.0 < time.time()
+ ):
self.thread_running = False
time.sleep(0.01)
except gremlin.error.VJoyError:
self.thread_running = False
- self.functor_complete.emit() # indicate completed
-
+ self.functor_complete.emit() # indicate completed
class VjoyRemap(gremlin.base_profile.AbstractAction):
-
"""Action remapping physical joystick inputs to vJoy inputs."""
name = "Vjoy Remap"
@@ -3436,115 +3687,136 @@ def priority(self):
return 9
def __init__(self, parent):
- """ vjoyremap action block """
+ """vjoyremap action block"""
super().__init__(parent)
self.parent = parent
# Set vjoy ids to None so we know to pick the next best one
# automatically
- self._vjoy_device_id : int = 1
- self._vjoy_input_id : int = 1
- self.input_type : InputType = self.hardware_input_type
+ self._vjoy_device_id: int = 1
+ self._vjoy_input_id: int = 1
+ self.input_type: InputType = self.hardware_input_type
# default hat map table setup and default mapping for new hats
- self.hat_map = {} # map of button id keyed by hat position tuple
+ self.hat_map = {} # map of button id keyed by hat position tuple
self.hat_positions = list(vjoy.vjoy.Hat.to_continuous_direction.keys())
- self.hat_positions.remove((0,0)) # remove center position
- self.hat_pulse_map = {} # bool table keyed by hat position
- self.hat_sticky = False # determines if hats are sticky or not - sticky means all positions are active until all returns to the center position
+ self.hat_positions.remove((0, 0)) # remove center position
+ self.hat_pulse_map = {} # bool table keyed by hat position
+ self.hat_sticky = False # determines if hats are sticky or not - sticky means all positions are active until all returns to the center position
button_id = 1
for position in self.hat_positions:
self.hat_map[position] = button_id
button_id += 1
- self.hat_pulse_map[position] = False # hold by default
+ self.hat_pulse_map[position] = False # hold by default
self.vjoy_axis_id = 1
self.vjoy_button_id = 1
self.vjoy_hat_id = 1
self.vjoy_device_guid = None
- self._reverse : bool = False
+ self._reverse: bool = False
self.axis_mode = "absolute"
- self.axis_scaling : float = 1.0
- self.axis_start_value : float = 0 # start value
+ self.axis_scaling: float = 1.0
+ self.axis_start_value: float = 0 # start value
self.axis_start_value_enabled = False
- self.curve_data = None # present if curve data is needed
+ self.curve_data = None # present if curve data is needed
config = gremlin.config.Configuration()
- self._grid_visible = config.button_grid_visible # true if the button grid is visible
-
- self._exec_on_release : bool = False
- self._paired : bool = False
-
- self.auto_release = False # true if we should do an auto-release (only means anything on momentary inputs)
- self.ignore_release = False # true if the button release should be ignored
-
- self._merge_device_id : str = None # input guid (str) of the merged device
- self._merge_device_guid : dinput.GUID = None # input guid for the merge device
- self.merge_input_id : int = None # input id of the merged input
- self.merge_input_type : gremlin.input_types.InputType = gremlin.input_types.InputType.JoystickAxis # only merging axes at this point
- self._merge_mode : MergeOperationType = MergeOperationType.Center # default merge method
- self.output_range_min : float = -1.0 # min for merged output
- self.output_range_max : float = 1.0 # max for merged output
- self.merge_invert : bool = False # inversion flag for merged output
+ self._grid_visible = (
+ config.button_grid_visible
+ ) # true if the button grid is visible
+
+ self._exec_on_release: bool = False
+ self._paired: bool = False
+
+ self.auto_release = False # true if we should do an auto-release (only means anything on momentary inputs)
+ self.ignore_release = False # true if the button release should be ignored
+
+ self._merge_device_id: str = None # input guid (str) of the merged device
+ self._merge_device_guid: dinput.GUID = None # input guid for the merge device
+ self.merge_input_id: int = None # input id of the merged input
+ self.merge_input_type: gremlin.input_types.InputType = (
+ gremlin.input_types.InputType.JoystickAxis
+ ) # only merging axes at this point
+ self._merge_mode: MergeOperationType = (
+ MergeOperationType.Center
+ ) # default merge method
+ self.output_range_min: float = -1.0 # min for merged output
+ self.output_range_max: float = 1.0 # max for merged output
+ self.merge_invert: bool = False # inversion flag for merged output
self.merged = False
# default mode
self._action_mode = VjoyAction.VJoyButton
- self.range_low = -1.0 # axis range min
- self.range_high = 1.0 # axis range max
+ self.range_low = -1.0 # axis range min
+ self.range_high = 1.0 # axis range max
is_axis = self.input_is_axis()
# pick an appropriate default action set for the type of input this is
if is_axis:
- # input is setup as an axis
- self._action_mode = VjoyAction.VJoyAxis
+ # input is setup as an axis
+ self._action_mode = VjoyAction.VJoyAxis
elif self.input_type in VJoyWidget.input_type_buttons:
self._action_mode = VjoyAction.VJoyButton
elif self.input_type == InputType.JoystickHat:
self._action_mode = VjoyAction.VJoyHat
- self.current_state = 0 # toggle value for the input 1 means set, any other value means not set for buttons
- self.pulse_delay = 250 # pulse delay
- self.start_pressed = False # true if a button starts as pressed when the profile is loaded
+ self.current_state = 0 # toggle value for the input 1 means set, any other value means not set for buttons
+ self.pulse_delay = 250 # pulse delay
+ self.start_pressed = (
+ False # true if a button starts as pressed when the profile is loaded
+ )
self.target_value = 0.0
- self.target_step_list = [-1,-0.5,0,0.5,1] # list of values to send - if empty - uses the fixed target_value
-
- self.target_step_index = 0 # index of last value sent
- self.target_step_start_index = 0 # start index when profile is loaded (initial step)
- self.target_step_direction = 1 # direction of stepping, +1 or -1
+ self.target_step_list = [
+ -1,
+ -0.5,
+ 0,
+ 0.5,
+ 1,
+ ] # list of values to send - if empty - uses the fixed target_value
+
+ self.target_step_index = 0 # index of last value sent
+ self.target_step_start_index = (
+ 0 # start index when profile is loaded (initial step)
+ )
+ self.target_step_direction = 1 # direction of stepping, +1 or -1
self.target_value_valid = True
- self.target_is_relative = False # true if the set value axis is a relative value (+ or -)
- self._stepped_device_id : str = None # device of the down step action to latch
- self._stepped_device_guid : dinput.GUID = None # device GUID of the down step device
+ self.target_is_relative = (
+ False # true if the set value axis is a relative value (+ or -)
+ )
+ self._stepped_device_id: str = None # device of the down step action to latch
+ self._stepped_device_guid: dinput.GUID = (
+ None # device GUID of the down step device
+ )
self.stepped_input_type = gremlin.input_types.InputType.JoystickButton
- self.stepped_input_id : int = None # input of the down step action to latch
-
+ self.stepped_input_id: int = None # input of the down step action to latch
self.vjoy_map = {} # list of vjoy devices by their vjoy index ID
self.refresh_vjoy()
-
-
-
+
def refresh_vjoy(self):
- ''' updates vjoy devices device map '''
- self.vjoy_map = {} # holds the map of devices keyed by VJOYID
- devices = sorted(joystick_handling.vjoy_devices(),key=lambda x: x.vjoy_id)
+ """updates vjoy devices device map"""
+ self.vjoy_map = {} # holds the map of devices keyed by VJOYID
+ devices = sorted(joystick_handling.vjoy_devices(), key=lambda x: x.vjoy_id)
for dev in devices:
self.vjoy_map[dev.vjoy_id] = dev
-
-
def get_raw_axis_value(self):
if self.input_is_hardware():
- return gremlin.joystick_handling.get_curved_axis(self.hardware_device_guid, self.hardware_input_id)
+ return gremlin.joystick_handling.get_curved_axis(
+ self.hardware_device_guid, self.hardware_input_id
+ )
return self.hardware_input_id.getAxisValue()
- def get_filtered_axis_value(self, value : float = None, curves : list = None) -> float:
- ''' computes the output value for the current configuration '''
+ def get_filtered_axis_value(
+ self, value: float = None, curves: list = None
+ ) -> float:
+ """computes the output value for the current configuration"""
if value is None:
- value = gremlin.joystick_handling.get_curved_axis(self.hardware_device_guid, self.hardware_input_id)
+ value = gremlin.joystick_handling.get_curved_axis(
+ self.hardware_device_guid, self.hardware_input_id
+ )
# if self.input_is_hardware():
# value = gremlin.joystick_handling.get_curved_axis(self.hardware_device_guid, self.hardware_input_id)
# else:
@@ -3555,28 +3827,41 @@ def get_filtered_axis_value(self, value : float = None, curves : list = None) ->
for curve_data in curves:
value = curve_data.curve_value(value)
- if self.action_mode == VjoyAction.VJoyMergeAxis and self.merge_mode != MergeOperationType.NotSet:
+ if (
+ self.action_mode == VjoyAction.VJoyMergeAxis
+ and self.merge_mode != MergeOperationType.NotSet
+ ):
if self.merge_device_id and self.merge_input_id:
# always read v1 and v2 because the input value may be of either inputs
v1 = None
v2 = None
- if gremlin.joystick_handling.is_hardware_device(self.hardware_device_guid):
- v1 = gremlin.joystick_handling.get_curved_axis(self.hardware_device_guid, self.hardware_input_id)
+ if gremlin.joystick_handling.is_hardware_device(
+ self.hardware_device_guid
+ ):
+ v1 = gremlin.joystick_handling.get_curved_axis(
+ self.hardware_device_guid, self.hardware_input_id
+ )
else:
v1 = self.hardware_input_id.axis_value
if gremlin.joystick_handling.is_hardware_device(self.merge_device_guid):
- v2 = gremlin.joystick_handling.get_curved_axis(self.merge_device_guid, self.merge_input_id)
+ v2 = gremlin.joystick_handling.get_curved_axis(
+ self.merge_device_guid, self.merge_input_id
+ )
else:
# find the merged device
ec = gremlin.execution_graph.ExecutionContext()
- input_item = ec.findInputItem(self.merge_device_guid, self.merge_input_id)
+ input_item = ec.findInputItem(
+ self.merge_device_guid, self.merge_input_id
+ )
if input_item:
v2 = input_item.axis_value
if v1 is None or v2 is None:
# something wasn't found
- syslog.error("VjoyRemap: merge: unable to get an axis value, one of the inputs was not found.")
+ syslog.error(
+ "VjoyRemap: merge: unable to get an axis value, one of the inputs was not found."
+ )
return 0.0
# apply any local curves to the values
@@ -3585,45 +3870,55 @@ def get_filtered_axis_value(self, value : float = None, curves : list = None) ->
v1 = curve_data.curve_value(v1)
v2 = curve_data.curve_value(v2)
-
-
match self.merge_mode:
case MergeOperationType.Add:
- value = scale_to_range(v1+v2,
- target_min=self.output_range_min,
- target_max=self.output_range_max,
- invert = self.merge_invert)
+ value = scale_to_range(
+ v1 + v2,
+ target_min=self.output_range_min,
+ target_max=self.output_range_max,
+ invert=self.merge_invert,
+ )
case MergeOperationType.Average:
- value = scale_to_range((v1+v2)/2,
- target_min=self.output_range_min,
- target_max=self.output_range_max,
- invert = self.merge_invert)
+ value = scale_to_range(
+ (v1 + v2) / 2,
+ target_min=self.output_range_min,
+ target_max=self.output_range_max,
+ invert=self.merge_invert,
+ )
case MergeOperationType.Center:
- value = scale_to_range((v1-v2)/2,
- target_min=self.output_range_min,
- target_max=self.output_range_max,
- invert = self.merge_invert)
+ value = scale_to_range(
+ (v1 - v2) / 2,
+ target_min=self.output_range_min,
+ target_max=self.output_range_max,
+ invert=self.merge_invert,
+ )
case MergeOperationType.Min:
- value = scale_to_range(min(v1,v2),
- target_min=self.output_range_min,
- target_max=self.output_range_max,
- invert = self.merge_invert)
+ value = scale_to_range(
+ min(v1, v2),
+ target_min=self.output_range_min,
+ target_max=self.output_range_max,
+ invert=self.merge_invert,
+ )
case MergeOperationType.Max:
- value = scale_to_range(max(v1,v2),
- target_min=self.output_range_min,
- target_max=self.output_range_max,
- invert = self.merge_invert)
+ value = scale_to_range(
+ max(v1, v2),
+ target_min=self.output_range_min,
+ target_max=self.output_range_max,
+ invert=self.merge_invert,
+ )
return value
-
- def get_ranged_axis_value(self, value : float) -> float:
- ''' get scaled and ranged and inverted axis value'''
+
+ def get_ranged_axis_value(self, value: float) -> float:
+ """get scaled and ranged and inverted axis value"""
v1 = self.range_low
v2 = self.range_high
s = self.axis_scaling
inverted = self.reverse
if v1 != -1.0 or v2 != 1.0 or s != 1.0:
- value = gremlin.util.scale_to_range(value*s,target_min=v1,target_max=v2, invert=inverted)
+ value = gremlin.util.scale_to_range(
+ value * s, target_min=v1, target_max=v2, invert=inverted
+ )
elif inverted:
value = gremlin.util.scale_to_range(value, invert=True)
return value
@@ -3631,8 +3926,9 @@ def get_ranged_axis_value(self, value : float) -> float:
@property
def merge_mode(self) -> MergeOperationType:
return self._merge_mode
+
@merge_mode.setter
- def merge_mode(self, value : MergeOperationType):
+ def merge_mode(self, value: MergeOperationType):
self._merge_mode = value
self.merged = value != MergeOperationType.NotSet
@@ -3641,7 +3937,7 @@ def merge_device_id(self) -> str:
return self._merge_device_id
@merge_device_id.setter
- def merge_device_id(self, value : str | dinput.GUID):
+ def merge_device_id(self, value: str | dinput.GUID):
if value is None:
self._merge_device_id = None
self._merge_device_guid = None
@@ -3654,8 +3950,9 @@ def merge_device_id(self, value : str | dinput.GUID):
@property
def merge_device_guid(self) -> dinput.GUID:
return self._merge_device_guid
+
@merge_device_guid.setter
- def merge_device_guid(self, value : dinput.GUID):
+ def merge_device_guid(self, value: dinput.GUID):
if value is None:
self._merge_device_id = None
self._merge_device_guid = None
@@ -3663,13 +3960,12 @@ def merge_device_guid(self, value : dinput.GUID):
self._merge_device_guid = value
self._merge_device_id = str(value)
-
@property
def stepped_device_id(self) -> str:
return self._stepped_device_id
@stepped_device_id.setter
- def stepped_device_id(self, value : str | dinput.GUID):
+ def stepped_device_id(self, value: str | dinput.GUID):
if value is None:
self._stepped_device_id = None
self._stepped_device_guid = None
@@ -3682,8 +3978,9 @@ def stepped_device_id(self, value : str | dinput.GUID):
@property
def stepped_device_guid(self) -> dinput.GUID:
return self._stepped_device_guid
+
@stepped_device_guid.setter
- def stepped_device_guid(self, value : dinput.GUID):
+ def stepped_device_guid(self, value: dinput.GUID):
if value is None:
self._stepped_device_id = None
self._stepped_device_guid = None
@@ -3691,11 +3988,8 @@ def stepped_device_guid(self, value : dinput.GUID):
self._stepped_device_guid = value
self._stepped_device_id = str(value)
-
-
-
def display_name(self):
- ''' display name for this action '''
+ """display name for this action"""
if self.action_mode == VjoyAction.VJoyAxis:
return f"VJoy #{self._vjoy_device_id} Axis: {self.vjoy_axis_id}"
elif self.action_mode == VjoyAction.VJoyButton:
@@ -3705,9 +3999,6 @@ def display_name(self):
else:
return f"VJoy #{self._vjoy_device_id} Mode: {self.action_mode}"
-
-
-
@property
def exec_on_release(self):
return self._exec_on_release
@@ -3735,6 +4026,7 @@ def vjoy_device_id(self, value):
@property
def vjoy_input_id(self):
return self._vjoy_input_id
+
@vjoy_input_id.setter
def vjoy_input_id(self, value):
self._vjoy_input_id = value
@@ -3755,11 +4047,10 @@ def action_mode(self) -> VjoyAction:
return self._action_mode
@action_mode.setter
- def action_mode(self, value : VjoyAction):
+ def action_mode(self, value: VjoyAction):
self._action_mode = value
# print (f"action mode set to : {value}")
-
@property
def reverse(self):
# axis reversed state
@@ -3768,7 +4059,7 @@ def reverse(self):
# return usage_data.is_inverted(self.vjoy_device_id, self.vjoy_axis_id) or self._reverse
@reverse.setter
- def reverse(self,value):
+ def reverse(self, value):
usage_data = gremlin.joystick_handling.VJoyUsageState()
usage_data.set_inverted(self.vjoy_device_id, self.vjoy_axis_id, value)
self._reverse = value
@@ -3777,17 +4068,17 @@ def toggle_reverse(self):
# toggles reverse mode for the axis
self.reverse = not self.reverse
-
@property
def reverse_configured(self) -> bool:
- ''' returns the configured reverse value rather than the live mode '''
- return self._reverse
+ """returns the configured reverse value rather than the live mode"""
+ return self._reverse
@property
def grid_visible(self) -> bool:
return self._grid_visible
+
@grid_visible.setter
- def grid_visible(self, value : bool):
+ def grid_visible(self, value: bool):
self._grid_visible = value
config = gremlin.config.Configuration()
config.button_grid_visible = value
@@ -3798,34 +4089,44 @@ def icon(self):
:return icon representing the remap action
"""
import gremlin.shared_state
+
is_dark = gremlin.shared_state.is_dark_theme
prefix = "dark_" if is_dark else ""
fallback = f"{prefix}joystick.png"
- if self.action_mode in (VjoyAction.VJoySetAxis, VjoyAction.VJoyInvertAxis, VjoyAction.VJoyAxis):
+ if self.action_mode in (
+ VjoyAction.VJoySetAxis,
+ VjoyAction.VJoyInvertAxis,
+ VjoyAction.VJoyAxis,
+ ):
input_string = "axis"
elif self.action_mode == VjoyAction.VJoyHat:
input_string = "hat"
fallback = "mdi.axis-arrow"
- elif self.action_mode in (VjoyAction.VJoyButton, VjoyAction.VJoyButtonRelease, VjoyAction.VJoyPulse, VjoyAction.VJoyHatToButton):
+ elif self.action_mode in (
+ VjoyAction.VJoyButton,
+ VjoyAction.VJoyButtonRelease,
+ VjoyAction.VJoyPulse,
+ VjoyAction.VJoyHatToButton,
+ ):
input_string = "button"
fallback = "mdi.gesture-tap-button"
else:
input_string = None
- #log_sys_warn(f"VjoyRemap: don't know how to handle action mode: {self.action_mode}")
+ # log_sys_warn(f"VjoyRemap: don't know how to handle action mode: {self.action_mode}")
-
- icon_path = f"{prefix}icon_{input_string}_{self.vjoy_input_id:03d}.png" if input_string else fallback
+ icon_path = (
+ f"{prefix}icon_{input_string}_{self.vjoy_input_id:03d}.png"
+ if input_string
+ else fallback
+ )
icon_file = get_icon_path(icon_path)
if icon_file and os.path.isfile(icon_file):
return icon_file
return fallback
- #return super().icon()
-
-
-
+ # return super().icon()
def requires_virtual_button(self):
"""Returns whether or not the action requires an activation condition.
@@ -3847,8 +4148,12 @@ def requires_virtual_button(self):
return True
def set_input_id(self, index):
- if self.action_mode in (VjoyAction.VJoyAxis, VjoyAction.VJoyInvertAxis, VjoyAction.VJoySetAxis):
- self.vjoy_axis_id = index
+ if self.action_mode in (
+ VjoyAction.VJoyAxis,
+ VjoyAction.VJoyInvertAxis,
+ VjoyAction.VJoySetAxis,
+ ):
+ self.vjoy_axis_id = index
elif self.action_mode == VjoyAction.VJoyHat:
self.vjoy_hat_id = index
else:
@@ -3856,45 +4161,45 @@ def set_input_id(self, index):
self.vjoy_input_id = index
def get_input_id(self):
- ''' returns input id based on the action mode '''
- if self.action_mode in (VjoyAction.VJoyAxis, VjoyAction.VJoyInvertAxis, VjoyAction.VJoySetAxis):
+ """returns input id based on the action mode"""
+ if self.action_mode in (
+ VjoyAction.VJoyAxis,
+ VjoyAction.VJoyInvertAxis,
+ VjoyAction.VJoySetAxis,
+ ):
return self.vjoy_axis_id
elif self.action_mode == VjoyAction.VJoyHat:
return self.vjoy_hat_id
else:
return self.vjoy_button_id
-
-
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Populates the data storage with data from the XML node.
:param node XML node with which to populate the storage
"""
try:
-
-
vjoy_id = safe_read(node, "vjoy", int)
- if not vjoy_id in self.vjoy_map:
- self.refresh_vjoy() # ensure we have the latest device list
+ if vjoy_id not in self.vjoy_map:
+ self.refresh_vjoy() # ensure we have the latest device list
- if not vjoy_id in self.vjoy_map:
- syslog.error(f"Profile load: vjoy device {vjoy_id} was not found in the list of valid VJOY devices")
+ if vjoy_id not in self.vjoy_map:
+ syslog.error(
+ f"Profile load: vjoy device {vjoy_id} was not found in the list of valid VJOY devices"
+ )
self.vjoy_axis_id = 1
self.vjoy_button_id = 1
self.vjoy_hat_id = 1
return
-
self.vjoy_device_id = vjoy_id
if "input" in node.attrib:
- index = safe_read(node,"input", int, 1)
+ index = safe_read(node, "input", int, 1)
self.set_input_id(index)
-
- #valid = False
+ # valid = False
for input_type in InputType.to_list():
attrib_name = InputType.to_string(input_type)
if attrib_name in node.attrib:
@@ -3902,20 +4207,18 @@ def _parse_xml(self, node, data = None):
self.vjoy_input_id = safe_read(node, attrib_name, int, 1)
self.vjoy_axis_id = self.vjoy_input_id
self.vjoy_button_id = self.vjoy_input_id
- #valid = True
+ # valid = True
break
# if not valid:
# raise gremlin.error.GremlinError(f"VJOYREMAP: Invalid remap type provided: {node.attrib}")
-
-
self.pulse_delay = 250
self.merge_input_id = None
self.merge_device_id = None
if "mode" in node.attrib:
- value = node.attrib['mode']
+ value = node.attrib["mode"]
self.action_mode = VjoyAction.from_string(value)
else:
if self.input_type in VJoyWidget.input_type_buttons:
@@ -3926,20 +4229,30 @@ def _parse_xml(self, node, data = None):
default_action_mode = VjoyAction.VJoyAxis
self.action_mode = default_action_mode
-
# hack to sync all loaded profile setups with the status grid
usage_data = gremlin.joystick_handling.VJoyUsageState()
if self.input_type == InputType.JoystickButton:
- usage_data.set_usage_state(self.vjoy_device_id, self.vjoy_input_id, state = True, action = self, emit = False)
- #usage_data.push_load_list(self.vjoy_device_id,self.input_type,self.vjoy_input_id)
+ usage_data.set_usage_state(
+ self.vjoy_device_id,
+ self.vjoy_input_id,
+ state=True,
+ action=self,
+ emit=False,
+ )
+ # usage_data.push_load_list(self.vjoy_device_id,self.input_type,self.vjoy_input_id)
elif self.input_type == InputType.JoystickAxis:
# check action mode for special case axis to button
if self.action_mode == VjoyAction.VJoyAxisToButton:
- usage_data.set_usage_state(self.vjoy_device_id, self.vjoy_input_id, state = True, action = self, emit = False)
-
+ usage_data.set_usage_state(
+ self.vjoy_device_id,
+ self.vjoy_input_id,
+ state=True,
+ action=self,
+ emit=False,
+ )
if "reverse" in node.attrib:
- self.reverse = safe_read(node,"reverse",bool,False)
+ self.reverse = safe_read(node, "reverse", bool, False)
if "axis-type" in node.attrib:
self.axis_mode = safe_read(node, "axis-type", str, "absolute")
@@ -3947,47 +4260,51 @@ def _parse_xml(self, node, data = None):
self.axis_scaling = safe_read(node, "axis-scaling", float, 1.0)
if "pulse_delay" in node.attrib:
- value = safe_read(node,"pulse_delay", int, 250)
+ value = safe_read(node, "pulse_delay", int, 250)
self.pulse_delay = value
if "start_pressed" in node.attrib:
- self.start_pressed = safe_read(node,"start_pressed", bool, False)
+ self.start_pressed = safe_read(node, "start_pressed", bool, False)
if "target_value" in node.attrib:
- self.target_value = safe_read(node,"target_value", float, 0.0)
+ self.target_value = safe_read(node, "target_value", float, 0.0)
self.target_value_valid = True
if "target_relative" in node.attrib:
- self.target_is_relative = safe_read(node,"target_relative", bool, False)
+ self.target_is_relative = safe_read(
+ node, "target_relative", bool, False
+ )
if "range_low" in node.attrib:
- self.range_low = safe_read(node,"range_low", float, -1.0)
+ self.range_low = safe_read(node, "range_low", float, -1.0)
if "range_high" in node.attrib:
- self.range_high = safe_read(node,"range_high", float, 1.0)
+ self.range_high = safe_read(node, "range_high", float, 1.0)
if "axis_start_value" in node.attrib:
- self.axis_start_value = safe_read(node,"axis_start_value", float, -1.0)
+ self.axis_start_value = safe_read(node, "axis_start_value", float, -1.0)
if "axis_start_value_enabled" in node.attrib:
- self.axis_start_value_enabled = safe_read(node,"axis_start_value_enabled", bool, False)
-
+ self.axis_start_value_enabled = safe_read(
+ node, "axis_start_value_enabled", bool, False
+ )
if "exec_on_release" in node.attrib:
- self.exec_on_release = safe_read(node,"exec_on_release",bool, False)
-
+ self.exec_on_release = safe_read(node, "exec_on_release", bool, False)
if "paired" in node.attrib:
- self.paired = safe_read(node,"paired", bool, False)
+ self.paired = safe_read(node, "paired", bool, False)
if "merge_device_id" in node.attrib:
self.merge_device_id = node.get("merge_device_id")
if "merge_input_id" in node.attrib:
- self.merge_input_id = safe_read(node,"merge_input_id", int, 0)
+ self.merge_input_id = safe_read(node, "merge_input_id", int, 0)
if "merge_input_type" in node.attrib:
- merge_input_type = safe_read(node,"merge_input_type", str, "")
- self.merge_input_type = gremlin.input_types.InputType.to_enum(merge_input_type)
+ merge_input_type = safe_read(node, "merge_input_type", str, "")
+ self.merge_input_type = gremlin.input_types.InputType.to_enum(
+ merge_input_type
+ )
if "merge_mode" in node.attrib:
mode = node.get("merge_mode")
@@ -3997,69 +4314,68 @@ def _parse_xml(self, node, data = None):
except:
pass
if "merge_invert" in node.attrib:
- self.merge_invert = safe_read(node,"merge_invert", bool, False)
+ self.merge_invert = safe_read(node, "merge_invert", bool, False)
if "merge_min" in node.attrib:
- self.output_range_min = safe_read(node,"merge_min", float, -1.0)
+ self.output_range_min = safe_read(node, "merge_min", float, -1.0)
if "merge_max" in node.attrib:
- self.output_range_max = safe_read(node,"merge_max", float, 1.0)
+ self.output_range_max = safe_read(node, "merge_max", float, 1.0)
if "grid_visible" in node.attrib:
- self.grid_visible = safe_read(node,"grid_visible", bool, True)
+ self.grid_visible = safe_read(node, "grid_visible", bool, True)
if "auto_release" in node.attrib:
- self.auto_release = safe_read(node,"auto_release",bool, False)
+ self.auto_release = safe_read(node, "auto_release", bool, False)
if "ignore-release" in node.attrib:
- self.ignore_release = safe_read(node,"ignore-release",bool, False)
+ self.ignore_release = safe_read(node, "ignore-release", bool, False)
if "step-dir" in node.attrib:
- self.target_step_index = safe_read(node,"step-dir", int, 1)
+ self.target_step_index = safe_read(node, "step-dir", int, 1)
if "steps" in node.attrib:
csv = node.get("steps")
self.target_step_list = gremlin.util.csv_to_floatlist(csv)
if "step-start-index" in node.attrib:
- self.target_step_start_index = safe_read(node,"step-start-index", int, 0)
+ self.target_step_start_index = safe_read(
+ node, "step-start-index", int, 0
+ )
if "stepped-device-id" in node.attrib:
self.stepped_device_id = node.get("stepped-device-id")
if "stepped-input-id" in node.attrib:
- self.stepped_input_id = safe_read(node,"stepped-input-id", int, 0)
+ self.stepped_input_id = safe_read(node, "stepped-input-id", int, 0)
# curve data
- curve_node = util.get_xml_child(node,"curve-data")
+ curve_node = util.get_xml_child(node, "curve-data")
if not curve_node:
# older style
- curve_node = util.get_xml_child(node,"response-curve-ex")
+ curve_node = util.get_xml_child(node, "response-curve-ex")
if not curve_node:
- curve_node = util.get_xml_child(node,"response-curve")
+ curve_node = util.get_xml_child(node, "response-curve")
-
if curve_node is not None:
self.curve_data = gremlin.curve_handler.AxisCurveData()
- self.curve_data.calibration = gremlin.ui.axis_calibration.CalibrationManager().getCalibration(self.hardware_device_guid, self.hardware_input_id)
+ self.curve_data.calibration = (
+ gremlin.ui.axis_calibration.CalibrationManager().getCalibration(
+ self.hardware_device_guid, self.hardware_input_id
+ )
+ )
self.curve_data._parse_xml(curve_node)
self.curve_data.curve_update()
# hat buttons
if self.action_mode == VjoyAction.VJoyHatToButton:
- hat_nodes = util.get_xml_child(node,"hat_to_button", multiple = True)
+ hat_nodes = util.get_xml_child(node, "hat_to_button", multiple=True)
for node_hat in hat_nodes:
- name = safe_read(node_hat,"name",str)
+ name = safe_read(node_hat, "name", str)
position = vjoy.vjoy.Hat.name_to_direction[name]
- if position != (0,0):
- button_id = safe_read(node_hat,"input",int,1)
+ if position != (0, 0):
+ button_id = safe_read(node_hat, "input", int, 1)
self.hat_map[position] = button_id
- is_pulse = safe_read(node_hat,"pulse",bool, False)
+ is_pulse = safe_read(node_hat, "pulse", bool, False)
self.hat_pulse_map[position] = is_pulse
if "hat_sticky" in node.attrib:
- self.hat_sticky = safe_read(node,"hat_sticky",bool, False)
-
-
-
-
-
-
+ self.hat_sticky = safe_read(node, "hat_sticky", bool, False)
except ProfileError:
self.vjoy_input_id = None
@@ -4073,28 +4389,30 @@ def _generate_xml(self):
node = ElementTree.Element(VjoyRemap.tag)
node.set("vjoy", str(self.vjoy_device_id))
- save_exec_on_release = VjoyAction.is_command(self.action_mode) or \
- self.action_mode in (VjoyAction.VJoyButton,
- VjoyAction.VJoyInvertAxis,
- VjoyAction.VJoySetAxis,
- VjoyAction.VJoyPulse)
-
- node.set(
- InputType.to_string(self.input_type),
- str(self.vjoy_input_id)
+ save_exec_on_release = VjoyAction.is_command(
+ self.action_mode
+ ) or self.action_mode in (
+ VjoyAction.VJoyButton,
+ VjoyAction.VJoyInvertAxis,
+ VjoyAction.VJoySetAxis,
+ VjoyAction.VJoyPulse,
)
+ node.set(InputType.to_string(self.input_type), str(self.vjoy_input_id))
+
node.set("mode", safe_format(VjoyAction.to_string(self.action_mode), str))
write_node_input = True
-
match self.action_mode:
case VjoyAction.VJoyAxis:
node.set("axis-type", safe_format(self.axis_mode, str))
node.set("axis-scaling", safe_format(self.axis_scaling, float))
node.set("axis_start_value", safe_format(self.axis_start_value, float))
- node.set("axis_start_value_enabled", safe_format(self.axis_start_value_enabled, bool))
+ node.set(
+ "axis_start_value_enabled",
+ safe_format(self.axis_start_value_enabled, bool),
+ )
node.set("range_low", safe_format(self.range_low, float))
node.set("range_high", safe_format(self.range_high, float))
reverse = safe_format(self.reverse_configured, bool)
diff --git a/action_plugins/merged_axis/__init__.py b/action_plugins/merged_axis/__init__.py
index de04b5eb..84d3d0f4 100644
--- a/action_plugins/merged_axis/__init__.py
+++ b/action_plugins/merged_axis/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,7 +16,6 @@
# along with this program. If not, see .
-import os
from PySide6 import QtWidgets, QtGui, QtCore
from lxml import etree as ElementTree
@@ -27,46 +26,42 @@
import gremlin.shared_state
from gremlin.ui import ui_common
import gremlin.types
-import logging
import gremlin.ui.ui_common
-from gremlin.util import safe_format, safe_read, scale_to_range, clamp
+from gremlin.util import safe_format, safe_read, clamp
import gremlin.event_handler
import gremlin.joystick_handling
-from dinput import GUID
-import qtawesome as qta
import gremlin.util
import gremlin.actions
-
class ActionContainerUi(gremlin.ui.ui_common.QRememberDialog):
- """UI to setup the individual action trigger containers and sub actions """
+ """UI to setup the individual action trigger containers and sub actions"""
def __init__(self, action_data, parent=None):
- '''
+ """
:param: data = the gate or range data block
:item_data: the InputItem data block holding the container and input device configuration for this gated input
- :index: the gate number of the gated input - there will at least be two for low and high - index is an integer
- '''
-
- super().__init__(self.__class__.__name__, parent = parent)
+ :index: the gate number of the gated input - there will at least be two for low and high - index is an integer
+ """
+
+ super().__init__(self.__class__.__name__, parent=parent)
# make modal
- self.setWindowModality(QtCore.Qt.ApplicationModal)
+ self.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal)
# Actual configuration object being managed
self.setMinimumWidth(600)
self.setMinimumHeight(800)
self.main_layout = QtWidgets.QVBoxLayout(self)
-
+
from gremlin.ui.joystick_device import InputItemConfiguration
+
self.container_widget = InputItemConfiguration(action_data.item_data)
self.main_layout.addWidget(self.container_widget)
class MergeAxisEntryWidget(QtWidgets.QDockWidget):
-
"""UI dialog which allows configuring how to merge two axes."""
# Signal which is emitted whenever the widget is closed
@@ -87,7 +82,7 @@ def __init__(self, action_data, parent=None):
self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.NoDockWidgetFeatures)
# tracking variables for output computations
-
+
self.action_data = action_data
# Setup the dock widget in which the entire dialog will sit
@@ -101,61 +96,63 @@ def __init__(self, action_data, parent=None):
self.setWidget(self.main_widget)
-
# Selectors for both physical and virtual joystick axis for the
# mapping selection
-
+
self.joy1_selector = ui_common.JoystickSelector(
- lambda x: self._change_cb(),
- [InputType.JoystickAxis]
+ lambda x: self._change_cb(), [InputType.JoystickAxis]
)
self.joy2_selector = ui_common.JoystickSelector(
- lambda x: self._change_cb(),
- [InputType.JoystickAxis]
+ lambda x: self._change_cb(), [InputType.JoystickAxis]
)
-
# Operation selection
self.operation_selector = ui_common.QComboBox()
- self.operation_selector.addItem("Average", gremlin.types.MergeAxisOperation.Average)
- self.operation_selector.addItem("Minimum", gremlin.types.MergeAxisOperation.Minimum)
- self.operation_selector.addItem("Maximum", gremlin.types.MergeAxisOperation.Maximum)
- self.operation_selector.addItem("Sum",gremlin.types.MergeAxisOperation.Sum)
- self.operation_selector.currentIndexChanged.connect(
- lambda x: self._change_cb()
+ self.operation_selector.addItem(
+ "Average", gremlin.types.MergeAxisOperation.Average
+ )
+ self.operation_selector.addItem(
+ "Minimum", gremlin.types.MergeAxisOperation.Minimum
)
+ self.operation_selector.addItem(
+ "Maximum", gremlin.types.MergeAxisOperation.Maximum
+ )
+ self.operation_selector.addItem("Sum", gremlin.types.MergeAxisOperation.Sum)
+ self.operation_selector.currentIndexChanged.connect(lambda x: self._change_cb())
self.operation_container_widget = QtWidgets.QWidget()
- self.operation_container_layout = QtWidgets.QVBoxLayout(self.operation_container_widget)
+ self.operation_container_layout = QtWidgets.QVBoxLayout(
+ self.operation_container_widget
+ )
# output widget
- self.output_widget = ui_common.AxisStateWidget(orientation=QtCore.Qt.Orientation.Horizontal, show_percentage=False)
-
-
+ self.output_widget = ui_common.AxisStateWidget(
+ orientation=QtCore.Qt.Orientation.Horizontal, show_percentage=False
+ )
# configure button
-
+
active_color = gremlin.ui.ui_common.Color.activeContentColor()
- self.configure_icon_active = gremlin.util.load_icon("fa6s.gear",qta_color= active_color)
+ self.configure_icon_active = gremlin.util.load_icon(
+ "fa6s.gear", qta_color=active_color
+ )
self.configure_icon_inactive = gremlin.util.load_icon("fa6s.gear")
- self.configure_button_widget = QtWidgets.QPushButton("Actions")
+ self.configure_button_widget = QtWidgets.QPushButton("Actions")
self.configure_button_widget.setToolTip("Configure Actions")
self.configure_button_widget.clicked.connect(self._configure_cb)
- # reverse checkbox
+ # reverse checkbox
self.invert_widget = QtWidgets.QCheckBox(text="Reverse")
self.invert_widget.setToolTip("Inverts the output of the merge")
self.invert_widget.setChecked(self.action_data.invert_output)
self.invert_widget.clicked.connect(self._invert_cb)
-
self.operation_container_layout.addWidget(self.operation_selector)
self.operation_container_layout.addWidget(self.invert_widget)
-
# Assemble the complete ui
self.grid_layout.addWidget(
QtWidgets.QLabel("Lower Half"), 0, 0
@@ -163,23 +160,23 @@ def __init__(self, action_data, parent=None):
self.grid_layout.addWidget(
QtWidgets.QLabel("Upper Half"), 0, 1
)
-
+
self.grid_layout.addWidget(
QtWidgets.QLabel("Operation"), 0, 2
)
- self.grid_layout.addWidget(
- QtWidgets.QLabel("Mapping"), 0, 3
- )
+ self.grid_layout.addWidget(QtWidgets.QLabel("Mapping"), 0, 3)
- self.grid_layout.addWidget(
- QtWidgets.QLabel("Output"), 0, 4
- )
+ self.grid_layout.addWidget(QtWidgets.QLabel("Output"), 0, 4)
warning_color = gremlin.ui.ui_common.Color.warningColor()
- self.status_widget = ui_common.QIconLabel("ph.shield-warning-fill",use_qta=True,icon_color=QtGui.QColor(warning_color), use_wrap=False)
-
-
+ self.status_widget = ui_common.QIconLabel(
+ "ph.shield-warning-fill",
+ use_qta=True,
+ icon_color=QtGui.QColor(warning_color),
+ use_wrap=False,
+ )
+
self.grid_layout.addWidget(self.joy1_selector, 1, 0)
self.grid_layout.addWidget(self.joy2_selector, 1, 1)
self.grid_layout.addWidget(self.operation_container_widget, 1, 2)
@@ -187,7 +184,7 @@ def __init__(self, action_data, parent=None):
self.grid_layout.addWidget(self.output_widget, 1, 4)
self.main_layout.addWidget(self.status_widget)
-
+
self.grid_layout.addWidget(QtWidgets.QLabel(" "), 1, 5)
self.grid_layout.setColumnStretch(5, 3)
@@ -198,80 +195,83 @@ def __init__(self, action_data, parent=None):
el.profile_start.connect(self._profile_start)
el.profile_stop.connect(self._profile_stop)
-
def _profile_start(self):
- ''' called when the profile starts '''
+ """called when the profile starts"""
el = gremlin.event_handler.EventListener()
el.joystick_event.disconnect(self._joystick_event_handler)
def _profile_stop(self):
- ''' called when the profile stops'''
+ """called when the profile stops"""
self._update_axis_widget()
el = gremlin.event_handler.EventListener()
el.joystick_event.connect(self._joystick_event_handler)
def _joystick_event_handler(self, event):
- ''' handles joystick events in the UI (functor handles the output when profile is running) so we see the output at design time '''
+ """handles joystick events in the UI (functor handles the output when profile is running) so we see the output at design time"""
if gremlin.shared_state.is_running:
- return
+ return
if not event.is_axis:
- return
-
- # merge - check two sets
+ return
+
+ # merge - check two sets
if self.action_data.joy1_guid == self.action_data.joy2_guid:
if self.action_data.joy1_input_id == self.action_data.joy2_input_id:
# no action on same axis input
return
- if event.identifier != self.action_data.joy1_input_id and event.identifier != self.action_data.joy2_input_id:
+ if (
+ event.identifier != self.action_data.joy1_input_id
+ and event.identifier != self.action_data.joy2_input_id
+ ):
# matches neither inputs
return
- elif event.device_guid == self.action_data.joy1_guid and event.identifier != self.action_data.joy1_input_id:
+ elif (
+ event.device_guid == self.action_data.joy1_guid
+ and event.identifier != self.action_data.joy1_input_id
+ ):
# no match device 1
return
- elif event.device_guid == self.action_data.joy2_guid and event.identifier != self.action_data.joy2_input_id:
+ elif (
+ event.device_guid == self.action_data.joy2_guid
+ and event.identifier != self.action_data.joy2_input_id
+ ):
# no match device 2
return
-
value = self.action_data.computeValue()
eh = gremlin.event_handler.EventListener()
custom_event = event.clone()
custom_event.value = value
- custom_event.raw_value = gremlin.util.scale_to_range(value, target_min = -32768, target_max = 32767) # convert back to a raw value
+ custom_event.raw_value = gremlin.util.scale_to_range(
+ value, target_min=-32768, target_max=32767
+ ) # convert back to a raw value
custom_event.device_guid = self.action_data.hardware_device_guid
custom_event.identifier = self.action_data.hardware_input_id
custom_event.is_custom = True
eh.custom_joystick_event.emit(custom_event)
- self._update_axis_widget(value)
+ self._update_axis_widget(value)
- def _update_axis_widget(self, value : float = None):
- ''' updates the repeater '''
+ def _update_axis_widget(self, value: float = None):
+ """updates the repeater"""
if value is None:
value = self.action_data.computeValue()
self.output_widget.setValue(value)
-
-
-
- def setStatus(self, text = ""):
- ''' sets the warning text '''
+ def setStatus(self, text=""):
+ """sets the warning text"""
self.status_widget.setText(text)
visible = True if text else False
self.status_widget.setVisible(visible)
-
@QtCore.Slot(bool)
- def _invert_cb(self, checked):
+ def _invert_cb(self, checked):
self.action_data.invert_output = checked
-
@QtCore.Slot()
def _configure_cb(self):
dialog = ActionContainerUi(self.action_data)
dialog.exec()
self.updateStatus()
-
def closeEvent(self, event):
"""Emits the closed event when this widget is being closed.
@@ -293,33 +293,29 @@ def select(self, data):
joy2_guid = data["upper"]["device_guid"]
joy1_input_id = data["lower"]["axis_id"]
joy2_input_id = data["upper"]["axis_id"]
-
+
self.joy1_selector.set_selection(
InputType.JoystickAxis,
joy1_guid,
joy1_input_id,
)
-
+
self.joy2_selector.set_selection(
InputType.JoystickAxis,
joy2_guid,
joy2_input_id,
)
-
self.operation_selector.setCurrentText(
- gremlin.types.MergeAxisOperation.to_string(
- data["operation"]
- ).capitalize()
+ gremlin.types.MergeAxisOperation.to_string(data["operation"]).capitalize()
)
# sync
self.sync()
-
def _change_cb(self):
- ''' occurs when a joystick device selection occurs '''
-
+ """occurs when a joystick device selection occurs"""
+
joy1_sel = self.joy1_selector.get_selection()
joy2_sel = self.joy2_selector.get_selection()
@@ -331,50 +327,68 @@ def _change_cb(self):
self.action_data.operation = self.operation_selector.currentData()
- self._joy1_value = gremlin.joystick_handling.get_curved_axis(self.action_data.joy1_guid, self.action_data.joy1_input_id)
- self._joy2_value = gremlin.joystick_handling.get_curved_axis(self.action_data.joy2_guid, self.action_data.joy2_input_id)
+ self._joy1_value = gremlin.joystick_handling.get_curved_axis(
+ self.action_data.joy1_guid, self.action_data.joy1_input_id
+ )
+ self._joy2_value = gremlin.joystick_handling.get_curved_axis(
+ self.action_data.joy2_guid, self.action_data.joy2_input_id
+ )
self.updateStatus()
-
@QtCore.Slot()
def profile_start(self):
- ''' stop processing joystick events when profile is running '''
+ """stop processing joystick events when profile is running"""
el = gremlin.event_handler.EventListener()
el.joystick_event.disconnect(self._event_handler)
@QtCore.Slot()
def profile_stop(self):
- ''' process joystick events when profile is not running '''
+ """process joystick events when profile is not running"""
el = gremlin.event_handler.EventListener()
el.joystick_event.connect(self._event_handler)
def sync(self):
- ''' syncs the control to the data '''
-
+ """syncs the control to the data"""
- action_data : MergedAxis = self.action_data
+ action_data: MergedAxis = self.action_data
with QtCore.QSignalBlocker(self.invert_widget):
self.invert_widget.setChecked(action_data.invert_output)
- self.joy1_selector.set_selection(gremlin.input_types.InputType.JoystickAxis, action_data.joy1_guid, action_data.joy1_input_id)
- self.joy2_selector.set_selection(gremlin.input_types.InputType.JoystickAxis, action_data.joy2_guid, action_data.joy2_input_id)
+ self.joy1_selector.set_selection(
+ gremlin.input_types.InputType.JoystickAxis,
+ action_data.joy1_guid,
+ action_data.joy1_input_id,
+ )
+ self.joy2_selector.set_selection(
+ gremlin.input_types.InputType.JoystickAxis,
+ action_data.joy2_guid,
+ action_data.joy2_input_id,
+ )
index = self.operation_selector.findData(action_data.operation)
self.operation_selector.setCurrentIndex(index)
-
- self._joy1_value = gremlin.joystick_handling.get_curved_axis(action_data.joy1_guid, action_data.joy1_input_id)
- self._joy2_value = gremlin.joystick_handling.get_curved_axis(action_data.joy2_guid, action_data.joy2_input_id)
-
- if not (action_data.joy1_guid == action_data.joy2_guid and action_data.joy1_input_id == action_data.joy2_input_id):
+ self._joy1_value = gremlin.joystick_handling.get_curved_axis(
+ action_data.joy1_guid, action_data.joy1_input_id
+ )
+ self._joy2_value = gremlin.joystick_handling.get_curved_axis(
+ action_data.joy2_guid, action_data.joy2_input_id
+ )
+
+ if not (
+ action_data.joy1_guid == action_data.joy2_guid
+ and action_data.joy1_input_id == action_data.joy2_input_id
+ ):
self._update_axis_widget()
self.updateStatus()
-
def updateStatus(self):
- action_data : MergedAxis = self.action_data
- if action_data.joy1_guid == action_data.joy2_guid and action_data.joy1_input_id == action_data.joy2_input_id:
+ action_data: MergedAxis = self.action_data
+ if (
+ action_data.joy1_guid == action_data.joy2_guid
+ and action_data.joy1_input_id == action_data.joy2_input_id
+ ):
self.setStatus("Merge axes must be different")
else:
self.setStatus()
@@ -387,24 +401,21 @@ def updateStatus(self):
class MergedAxisWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""Widget associated with the action of switching to the previous mode."""
def __init__(self, action_data, parent=None):
super().__init__(action_data, parent=parent)
- assert(isinstance(action_data, MergedAxis))
+ assert isinstance(action_data, MergedAxis)
self.action_data = action_data
def _create_ui(self):
-
self.container_widget = QtWidgets.QWidget()
self.container_layout = QtWidgets.QVBoxLayout(self.container_widget)
- self.container_widget.setContentsMargins(0,0,0,0)
+ self.container_widget.setContentsMargins(0, 0, 0, 0)
self.merge_layout = QtWidgets.QVBoxLayout()
self.entry = MergeAxisEntryWidget(self.action_data)
-
if not self.action_data.vjoy_valid:
label = QtWidgets.QLabel(
"No virtual devices available for axis merging. Either no "
@@ -413,7 +424,7 @@ def _create_ui(self):
)
label.setStyleSheet("QLabel { background-color : '#FFF4B0'; }")
label.setWordWrap(True)
- label.setFrameShape(QtWidgets.QFrame.Box)
+ label.setFrameShape(QtWidgets.QFrame.Shape.Box)
label.setMargin(10)
self.main_layout.addWidget(label)
else:
@@ -421,8 +432,6 @@ def _create_ui(self):
container_layout = QtWidgets.QHBoxLayout(container_widget)
container_layout.addStretch()
-
-
# self.add_button = QtWidgets.QPushButton("Add Merged Axis")
# self.add_button.clicked.connect(self.action_data._add_entry)
@@ -432,51 +441,53 @@ def _create_ui(self):
self.merge_layout.addWidget(self.entry)
self.main_layout.addLayout(self.merge_layout)
- self.main_layout.addWidget(container_widget)
-
-
-
+ self.main_layout.addWidget(container_widget)
def _populate_ui(self):
self.entry.sync()
-
class MergedAxisFunctor(gremlin.base_profile.AbstractContainerActionFunctor):
-
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
self.action_data = action
self._joy1_value = 0
self._joy2_value = 0
self._callbacks = {}
-
- def process_event(self, event, value, extra_data = None):
- ''' do nothing because the container will not be called through the normal hierarchy '''
+ def process_event(self, event, value, extra_data=None):
+ """do nothing because the container will not be called through the normal hierarchy"""
return True
-
+
def _event_handler(self, event):
- ''' internal event on axis input - determine if we should fire an update or not '''
+ """internal event on axis input - determine if we should fire an update or not"""
if not event.is_axis:
- return
-
- # merge - check two sets
+ return
+
+ # merge - check two sets
if self.action_data.joy1_guid == self.action_data.joy2_guid:
if self.action_data.joy1_input_id == self.action_data.joy2_input_id:
# no action on same axis input
return
- if event.identifier != self.action_data.joy1_input_id and event.identifier != self.action_data.joy2_input_id:
+ if (
+ event.identifier != self.action_data.joy1_input_id
+ and event.identifier != self.action_data.joy2_input_id
+ ):
# matches neither inputs
return
- elif event.device_guid == self.action_data.joy1_guid and event.identifier != self.action_data.joy1_input_id:
+ elif (
+ event.device_guid == self.action_data.joy1_guid
+ and event.identifier != self.action_data.joy1_input_id
+ ):
# no match device 1
return
- elif event.device_guid == self.action_data.joy2_guid and event.identifier != self.action_data.joy2_input_id:
+ elif (
+ event.device_guid == self.action_data.joy2_guid
+ and event.identifier != self.action_data.joy2_input_id
+ ):
# no match device 2
return
-
value = self.action_data.computeValue()
@@ -495,27 +506,28 @@ def _event_handler(self, event):
@QtCore.Slot()
def profile_start(self):
- ''' profile starts - build execution callbacks by defined container '''
-
+ """profile starts - build execution callbacks by defined container"""
+
# build event callback maps from subcontainers in this gated axis
callbacks_map = {}
for container in self.action_data.item_data.containers:
callbacks_map[container] = container.generate_callbacks()
- self._callbacks = callbacks_map
+ self._callbacks = callbacks_map
- self._joy1_value = gremlin.joystick_handling.get_curved_axis(self.action_data.joy1_guid, self.action_data.joy1_input_id)
- self._joy2_value = gremlin.joystick_handling.get_curved_axis(self.action_data.joy2_guid, self.action_data.joy2_input_id)
+ self._joy1_value = gremlin.joystick_handling.get_curved_axis(
+ self.action_data.joy1_guid, self.action_data.joy1_input_id
+ )
+ self._joy2_value = gremlin.joystick_handling.get_curved_axis(
+ self.action_data.joy2_guid, self.action_data.joy2_input_id
+ )
el = gremlin.event_handler.EventListener()
el.joystick_event.connect(self._event_handler)
-
-
@QtCore.Slot()
def profile_stop(self):
- ''' profile stops - cleanup '''
-
+ """profile stops - cleanup"""
el = gremlin.event_handler.EventListener()
el.joystick_event.disconnect(self._event_handler)
@@ -523,12 +535,9 @@ def profile_stop(self):
# clean up callback map
self._callbacks.clear()
-
-
class MergedAxis(gremlin.base_profile.AbstractAction):
-
- """ action data for the MergedAxis action """
+ """action data for the MergedAxis action"""
name = "Merged Axis"
tag = "merged-axis"
@@ -554,11 +563,12 @@ def __init__(self, parent):
self.invert_output = False
self.action_valid = True
-
# set this to the current input
joy1_input_id = self.hardware_input_id
# get the device info
- info = gremlin.joystick_handling.device_info_from_guid(self.hardware_device_guid)
+ info = gremlin.joystick_handling.device_info_from_guid(
+ self.hardware_device_guid
+ )
# validate bounds
if info.axis_count == 0:
@@ -576,12 +586,10 @@ def __init__(self, parent):
self.joy1_guid = self.hardware_device_guid
self.joy1_input_id = joy1_input_id
- self.joy2_guid = self.hardware_device_guid
+ self.joy2_guid = self.hardware_device_guid
self.joy2_input_id = joy2_input_id
self.operation = gremlin.types.MergeAxisOperation.Average
-
-
# container holder for this action
current_item_data = gremlin.base_profile._get_input_item(self)
item_data = gremlin.base_profile.InputItem()
@@ -591,30 +599,33 @@ def __init__(self, parent):
item_data._is_action = True
item_data._profile_mode = current_item_data._profile_mode
item_data._device_name = current_item_data._device_name
- self.item_data : gremlin.base_profile.InputItem = item_data
-
+ self.item_data: gremlin.base_profile.InputItem = item_data
def computeValue(self) -> float:
- ''' computes the output '''
+ """computes the output"""
- joy1_value = gremlin.joystick_handling.get_axis(self.joy1_guid, self.joy1_input_id)
- joy2_value = gremlin.joystick_handling.get_axis(self.joy2_guid, self.joy2_input_id)
+ joy1_value = gremlin.joystick_handling.get_axis(
+ self.joy1_guid, self.joy1_input_id
+ )
+ joy2_value = gremlin.joystick_handling.get_axis(
+ self.joy2_guid, self.joy2_input_id
+ )
- if self.invert_output:
- r_min = -1.0
- r_max = 1.0
- target = -value
- value = r_min + (target + 1.0)*((r_max - r_min)/2.0)
-
operation = self.operation
if operation == gremlin.types.MergeAxisOperation.Sum:
- value = clamp(joy1_value + joy2_value,-1.0,1.0)
+ value = clamp(joy1_value + joy2_value, -1.0, 1.0)
elif operation == gremlin.types.MergeAxisOperation.Maximum:
value = max(joy1_value, joy2_value)
elif operation == gremlin.types.MergeAxisOperation.Minimum:
value = min(joy1_value, joy2_value)
elif operation == gremlin.types.MergeAxisOperation.Average:
- value = (joy1_value - joy2_value) / 2.0
+ value = (joy1_value - joy2_value) / 2.0
+
+ if self.invert_output:
+ r_min = -1.0
+ r_max = 1.0
+ target = -value
+ value = r_min + (target + 1.0) * ((r_max - r_min) / 2.0)
return value
@@ -624,41 +635,40 @@ def icon(self):
def requires_virtual_button(self):
return False
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
# load gate data
self.entries = []
for entry_node in node:
operation_str = safe_read(entry_node, "operation", str, "")
operation = gremlin.types.MergeAxisOperation.to_enum(operation_str)
- joy1_guid = safe_read(entry_node, "joy1_device_id", str, None )
+ joy1_guid = safe_read(entry_node, "joy1_device_id", str, None)
if joy1_guid:
self.joy1_guid = gremlin.util.parse_guid(joy1_guid)
- self.joy1_input_id = safe_read(entry_node, "joy1_axis_id",int,0)
+ self.joy1_input_id = safe_read(entry_node, "joy1_axis_id", int, 0)
- joy2_guid = safe_read(entry_node, "joy2_device_id", str, None )
+ joy2_guid = safe_read(entry_node, "joy2_device_id", str, None)
if joy2_guid:
self.joy2_guid = gremlin.util.parse_guid(joy2_guid)
- self.joy2_input_id = safe_read(entry_node, "joy2_axis_id",int,0)
-
+ self.joy2_input_id = safe_read(entry_node, "joy2_axis_id", int, 0)
+
invert_output = safe_read(entry_node, "reverse", bool, False)
self.invert_output = invert_output
self.operation = operation
-
+
break
item_node = gremlin.util.get_xml_child(node, "action_containers")
if item_node is not None:
item_node.tag = item_node.get("type")
self.item_data.from_xml(item_node, data)
-
def _generate_xml(self):
- # save gate data
+ # save gate data
node = ElementTree.Element(MergedAxis.tag)
- #entry : MergeAxisEntryWidget = self.entry
- operation = self.operation # entry.operation_selector.currentData()
+ # entry : MergeAxisEntryWidget = self.entry
+ operation = self.operation # entry.operation_selector.currentData()
operation_str = gremlin.types.MergeAxisOperation.to_string(operation)
- entry_node = ElementTree.SubElement(node,"entry")
+ entry_node = ElementTree.SubElement(node, "entry")
entry_node.set("operation", operation_str)
entry_node.set("joy1_device_id", str(self.joy1_guid))
entry_node.set("joy1_axis_id", str(self.joy1_input_id))
diff --git a/action_plugins/noop/__init__.py b/action_plugins/noop/__init__.py
index 8e70c753..f4b92aa3 100644
--- a/action_plugins/noop/__init__.py
+++ b/action_plugins/noop/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -30,11 +30,10 @@ class NoOpActionWidget(AbstractActionWidget):
def __init__(self, action_data, parent=None):
super().__init__(action_data, parent=parent)
- assert(isinstance(action_data, NoOpAction))
-
+ assert isinstance(action_data, NoOpAction)
def display_name(self):
- ''' returns a display string for the current configuration '''
+ """returns a display string for the current configuration"""
return "Noop"
def _create_ui(self):
@@ -46,18 +45,16 @@ def _populate_ui(self):
class NoOpActionFunctor(gremlin.base_profile.AbstractFunctor):
-
"""Functor, executing the NoOp action."""
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
return True
class NoOpAction(gremlin.base_profile.AbstractAction):
-
"""Action which performs no operation."""
name = "NoOp"
@@ -85,7 +82,7 @@ def icon(self):
def requires_virtual_button(self):
return False
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
pass
def _generate_xml(self):
diff --git a/action_plugins/pause/__init__.py b/action_plugins/pause/__init__.py
index 2f2d5f38..2dae6804 100644
--- a/action_plugins/pause/__init__.py
+++ b/action_plugins/pause/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -35,30 +35,30 @@
syslog = logging.getLogger("system")
-class PauseMode (IntEnum):
- Delay = 0 # delay mode
- PauseAction = 1 # pause action mode
+class PauseMode(IntEnum):
+ Delay = 0 # delay mode
+ PauseAction = 1 # pause action mode
-class PauseActionWidget(gremlin.ui.input_item.AbstractActionWidget):
+class PauseActionWidget(gremlin.ui.input_item.AbstractActionWidget):
"""Widget for the pause action."""
def __init__(self, action_data, parent=None):
super().__init__(action_data, parent=parent)
- assert(isinstance(action_data, PauseAction))
-
+ assert isinstance(action_data, PauseAction)
def display_name(self):
- ''' returns a display string for the current configuration '''
+ """returns a display string for the current configuration"""
return "Pause Action"
def _create_ui(self):
-
self.mode_container_widget = QtWidgets.QWidget()
self.mode_container_layout = QtWidgets.QHBoxLayout(self.mode_container_widget)
self.mode_delay_widget = gremlin.ui.ui_common.QDataRadioButton("Delay")
self.mode_delay_widget.data = PauseMode.Delay
- self.mode_pause_widget = gremlin.ui.ui_common.QDataRadioButton("Pause Callback Execution")
+ self.mode_pause_widget = gremlin.ui.ui_common.QDataRadioButton(
+ "Pause Callback Execution"
+ )
self.mode_pause_widget.data = PauseMode.PauseAction
self.mode_container_layout.addWidget(QtWidgets.QLabel("Mode:"))
self.mode_container_layout.addWidget(self.mode_delay_widget)
@@ -71,16 +71,12 @@ def _create_ui(self):
self.mode_pause_widget.setChecked(True)
self.mode_delay_widget.clicked.connect(self._mode_changed)
self.mode_pause_widget.clicked.connect(self._mode_changed)
-
self.delay_widget = gremlin.ui.ui_common.QDelayWidget()
self.delay_widget.setToolTip("Delay in milliseconds")
self.delay_widget.valueChanged.connect(self._value_changed)
self.main_layout.addWidget(self.mode_container_widget)
self.main_layout.addWidget(self.delay_widget)
-
-
-
def _populate_ui(self):
with QtCore.QSignalBlocker(self.delay_widget):
@@ -93,7 +89,6 @@ def _mode_changed(self):
mode = cb.data
self.action_data.mode = mode
-
@QtCore.Slot()
def _value_changed(self):
self.action_data.delay = self.delay_widget.value()
@@ -104,14 +99,16 @@ def _update(self):
class PauseActionFunctor(gremlin.base_profile.AbstractFunctor):
-
- def __init__(self, action_data, parent = None):
+ def __init__(self, action_data, parent=None):
super().__init__(action_data, parent)
self.action_data = action_data
-
- def process_event(self, event : gremlin.event_handler.Event, value : gremlin.actions.Value, extra_data = None):
-
+ def process_event(
+ self,
+ event: gremlin.event_handler.Event,
+ value: gremlin.actions.Value,
+ extra_data=None,
+ ):
syslog = logging.getLogger("system")
if value.is_pressed:
match self.action_data.mode:
@@ -123,15 +120,14 @@ def process_event(self, event : gremlin.event_handler.Event, value : gremlin.act
# delay
syslog.info(f"Pause: start waiting {self.action_data.delay} ms")
- time.sleep(self.action_data.delay/1000)
+ time.sleep(self.action_data.delay / 1000)
syslog.info(f"Pause: end waiting {self.action_data.delay} ms")
self.functor_complete.emit()
-
+
return True
class PauseAction(gremlin.base_profile.AbstractAction):
-
"""Action for pausing the execution of callbacks."""
name = "Pause"
@@ -153,21 +149,17 @@ class PauseAction(gremlin.base_profile.AbstractAction):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
- self.mode = PauseMode.Delay # delay mode is the default
- self.delay = 250 # default delay in ms
-
+ self.mode = PauseMode.Delay # delay mode is the default
+ self.delay = 250 # default delay in ms
def icon(self):
return "fa5.pause-circle"
- #return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
+ # return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
def requires_virtual_button(self):
- return self.get_input_type() in [
- InputType.JoystickAxis,
- InputType.JoystickHat
- ]
+ return self.get_input_type() in [InputType.JoystickAxis, InputType.JoystickHat]
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
if "mode" in node.attrib:
mode = node.get("mode")
match mode:
@@ -176,18 +168,17 @@ def _parse_xml(self, node, data = None):
case 1:
self.mode = PauseMode.PauseAction
if "delay" in node.attrib:
- self.delay = safe_read(node,"delay",int, 250)
+ self.delay = safe_read(node, "delay", int, 250)
pass
else:
# legacy node - use the old mode
self.mode = PauseMode.PauseAction
-
def _generate_xml(self):
node = ElementTree.Element("pause")
- node.set("mode",str(self.mode))
+ node.set("mode", str(self.mode))
if self.mode == PauseMode.Delay:
- node.set("delay",str(self.delay))
+ node.set("delay", str(self.delay))
return node
def _is_valid(self):
diff --git a/action_plugins/play_sound/__init__.py b/action_plugins/play_sound/__init__.py
index 80a693b4..9214daf8 100644
--- a/action_plugins/play_sound/__init__.py
+++ b/action_plugins/play_sound/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -30,7 +30,6 @@
class PlaySoundWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""Widget for the resume action."""
# player has to be a class reference to avoid it being garbage collected and not playing a sound at all
@@ -43,9 +42,9 @@ def __init__(self, action_data, parent=None):
def _create_ui(self):
content_widget = QtWidgets.QWidget()
- content_widget.setContentsMargins(0,0,0,0)
+ content_widget.setContentsMargins(0, 0, 0, 0)
content_layout = QtWidgets.QHBoxLayout(content_widget)
- content_layout.setContentsMargins(0,0,0,0)
+ content_layout.setContentsMargins(0, 0, 0, 0)
self.icon_widget = QtWidgets.QLabel()
self.file_path_widget = QtWidgets.QLineEdit()
self.file_path_widget.installEventFilter(self)
@@ -58,28 +57,27 @@ def _create_ui(self):
self.volume_widget.valueChanged.connect(self._volume_changed)
self.play_widget = QtWidgets.QPushButton("Play")
- self.play_widget.setIcon(load_icon("ei.play",qta_color = gremlin.ui.ui_common.Color.activeColor()))
+ self.play_widget.setIcon(
+ load_icon("ei.play", qta_color=gremlin.ui.ui_common.Color.activeColor())
+ )
self.play_widget.setToolTip("Plays the audio as configured")
self.play_widget.clicked.connect(self._play_cb)
-
-
content_layout.addWidget(self.icon_widget)
content_layout.addWidget(self.file_path_widget)
content_layout.addWidget(self.edit_path_widget)
content_layout.addWidget(QtWidgets.QLabel("Volume"))
content_layout.addWidget(self.volume_widget)
content_layout.addWidget(self.play_widget)
-
+
self.main_layout.addWidget(content_widget)
self.player.setAudioOutput(self.audio)
-
def eventFilter(self, object, event):
t = event.type()
if t == QtCore.QEvent.Type.FocusOut:
- self.action_data.sound_file = self.file_path_widget.text()
+ self.action_data.sound_file = self.file_path_widget.text()
return False
def _populate_ui(self):
@@ -92,22 +90,25 @@ def _volume_changed(self, value):
def _file_changed(self):
fname = self.file_path_widget.text()
- valid = os.path.isfile(fname)
+ valid = os.path.isfile(fname)
if valid:
- self._setIcon("mdi.checkbox-marked-outline", color = gremlin.ui.ui_common.Color.activeColor())
+ self._setIcon(
+ "mdi.checkbox-marked-outline",
+ color=gremlin.ui.ui_common.Color.activeColor(),
+ )
else:
self._setIcon("fa6s.circle-exclamation", color="red")
self.play_widget.setEnabled(valid)
- def _setIcon(self, icon_path = None, use_qta = True, color = None):
- import qtawesome as qta
+ def _setIcon(self, icon_path=None, use_qta=True, color=None):
from gremlin.util import load_pixmap
+
icon_size = QtCore.QSize(16, 16)
- ''' sets the icon of the label, pass a blank or None path to clear the icon'''
+ """ sets the icon of the label, pass a blank or None path to clear the icon"""
if icon_path:
if use_qta:
if color:
- pixmap = qta.icon(icon_path, color=color).pixmap(icon_size)
+ pixmap = qta.icon(icon_path, color=color).pixmap(icon_size)
else:
pixmap = qta.icon(icon_path).pixmap(icon_size)
else:
@@ -123,9 +124,9 @@ def _setIcon(self, icon_path = None, use_qta = True, color = None):
@QtCore.Slot()
def _new_sound_file(self):
- """Prompts the user to select a new sound file to add to the profile. """
+ """Prompts the user to select a new sound file to add to the profile."""
config = gremlin.config.Configuration()
- fname = self.file_path_widget.text() # current entry
+ fname = self.file_path_widget.text() # current entry
if os.path.isfile(fname):
dir = os.path.dirname(fname)
elif os.path.isdir(fname):
@@ -135,14 +136,11 @@ def _new_sound_file(self):
if dir is None or not os.path.isdir(dir):
dir = userprofile_path()
fname, _ = QtWidgets.QFileDialog.getOpenFileName(
- None,
- "Path to sound file",
- dir,
- "All Files (*)"
+ None, "Path to sound file", dir, "All Files (*)"
)
if os.path.isfile(fname):
self.action_data.sound_file = fname
- dirname,_ = os.path.split(fname)
+ dirname, _ = os.path.split(fname)
config.last_sound_folder = dirname
# refresh the UI
self._populate_ui()
@@ -152,34 +150,33 @@ def _play_cb(self):
if os.path.isfile(self.action_data.sound_file):
media = QtCore.QUrl(self.action_data.sound_file)
self.player.setSource(media)
- volume = self.action_data.volume/100.0 # 0.0 to 1.0
- self.audio.setVolume(volume)
+ volume = self.action_data.volume / 100.0 # 0.0 to 1.0
+ self.audio.setVolume(volume)
self.player.play()
+
class PlaySoundFunctor(gremlin.base_profile.AbstractFunctor):
- ''' fixed for QT6 media player changes '''
+ """fixed for QT6 media player changes"""
player = QtMultimedia.QMediaPlayer()
audio = QtMultimedia.QAudioOutput()
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
self.sound_file = action.sound_file
self.volume = action.volume
PlaySoundFunctor.player.setAudioOutput(PlaySoundFunctor.audio)
-
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
if os.path.isfile(self.sound_file):
media = QtCore.QUrl(self.sound_file)
PlaySoundFunctor.player.setSource(media)
- PlaySoundFunctor.audio.setVolume(self.volume/100) # 0 to 1
+ PlaySoundFunctor.audio.setVolume(self.volume / 100) # 0 to 1
PlaySoundFunctor.player.play()
return True
class PlaySound(gremlin.base_profile.AbstractAction):
-
"""Action to resume callback execution."""
name = "Play Sound"
@@ -200,7 +197,7 @@ class PlaySound(gremlin.base_profile.AbstractAction):
def icon(self):
return "ei.speaker"
- #return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
+ # return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
def __init__(self, parent):
super().__init__(parent)
@@ -209,16 +206,13 @@ def __init__(self, parent):
self.volume = 50
def display_name(self):
- ''' returns a display string for the current configuration '''
+ """returns a display string for the current configuration"""
return f"Play: [{self.sound_file}]"
def requires_virtual_button(self):
- return self.get_input_type() in [
- InputType.JoystickAxis,
- InputType.JoystickHat
- ]
+ return self.get_input_type() in [InputType.JoystickAxis, InputType.JoystickHat]
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
self.sound_file = node.get("file")
self.volume = int(node.get("volume", 50))
diff --git a/action_plugins/previous_mode/__init__.py b/action_plugins/previous_mode/__init__.py
index b33a2371..98bfb5dc 100644
--- a/action_plugins/previous_mode/__init__.py
+++ b/action_plugins/previous_mode/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -26,12 +26,11 @@
class PreviousModeWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""Widget associated with the action of switching to the previous mode."""
def __init__(self, action_data, parent=None):
super().__init__(action_data, parent=parent)
- assert(isinstance(action_data, PreviousMode))
+ assert isinstance(action_data, PreviousMode)
def _create_ui(self):
self.label = QtWidgets.QLabel("Switches to the previously active mode")
@@ -42,18 +41,17 @@ def _populate_ui(self):
class PreviousModeFunctor(gremlin.base_profile.AbstractFunctor):
-
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
import gremlin.control_action
+
gremlin.control_action.switch_to_previous_mode()
return True
class PreviousMode(gremlin.base_profile.AbstractAction):
-
"""Action that switches to the previously active mode."""
name = "Switch to previous Mode"
@@ -76,19 +74,16 @@ def __init__(self, parent):
self.parent = parent
def display_name(self):
- ''' returns a display string for the current configuration '''
- return "Previous Mode"
+ """returns a display string for the current configuration"""
+ return "Previous Mode"
def icon(self):
return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
def requires_virtual_button(self):
- return self.get_input_type() in [
- InputType.JoystickAxis,
- InputType.JoystickHat
- ]
+ return self.get_input_type() in [InputType.JoystickAxis, InputType.JoystickHat]
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
pass
def _generate_xml(self):
diff --git a/action_plugins/remap/__init__.py b/action_plugins/remap/__init__.py
index b6043492..50eed881 100644
--- a/action_plugins/remap/__init__.py
+++ b/action_plugins/remap/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -39,8 +39,8 @@
syslog = logging.getLogger("system")
-class RemapWidget(gremlin.ui.input_item.AbstractActionWidget):
+class RemapWidget(gremlin.ui.input_item.AbstractActionWidget):
"""Dialog which allows the selection of a vJoy output to use as
as the remapping for the currently selected input.
"""
@@ -68,54 +68,38 @@ def __init__(self, action_data, parent=None):
:param parent the parent of this widget
"""
super().__init__(action_data, parent=parent)
- assert(isinstance(action_data, Remap))
+ assert isinstance(action_data, Remap)
def _create_ui(self):
"""Creates the UI components."""
import gremlin.shared_state
if not gremlin.shared_state.vjoy_enabled:
- self.main_layout.addWidget(QtWidgets.QLabel("VJOY is not available. Ensure VJOY is installed and configured."))
+ self.main_layout.addWidget(
+ QtWidgets.QLabel(
+ "VJOY is not available. Ensure VJOY is installed and configured."
+ )
+ )
return
-
input_types = {
- InputType.Keyboard: [
- InputType.JoystickButton
- ],
- InputType.KeyboardLatched: [
- InputType.JoystickButton
- ],
- InputType.Midi: [
- InputType.JoystickAxis,
- InputType.JoystickButton
- ],
+ InputType.Keyboard: [InputType.JoystickButton],
+ InputType.KeyboardLatched: [InputType.JoystickButton],
+ InputType.Midi: [InputType.JoystickAxis, InputType.JoystickButton],
InputType.OpenSoundControl: [
InputType.JoystickAxis,
- InputType.JoystickButton
- ],
- InputType.JoystickAxis: [
- InputType.JoystickAxis,
- InputType.JoystickButton
- ],
- InputType.JoystickButton: [
- InputType.JoystickButton
- ],
- InputType.JoystickHat: [
InputType.JoystickButton,
- InputType.JoystickHat
- ]
-
+ ],
+ InputType.JoystickAxis: [InputType.JoystickAxis, InputType.JoystickButton],
+ InputType.JoystickButton: [InputType.JoystickButton],
+ InputType.JoystickHat: [InputType.JoystickButton, InputType.JoystickHat],
}
self.vjoy_selector = ui_common.VJoySelector(
lambda x: self.save_changes(), # handler when selection changes
input_types[self._get_input_type()],
- self.action_data.get_settings().vjoy_as_input
+ self.action_data.get_settings().vjoy_as_input,
)
-
-
-
self.main_layout.addWidget(self.vjoy_selector)
# Create UI widgets for absolute / relative axis modes if the remap
@@ -124,12 +108,11 @@ def _create_ui(self):
self.remap_type_widget = QtWidgets.QWidget()
self.remap_type_layout = QtWidgets.QHBoxLayout(self.remap_type_widget)
-
self.absolute_checkbox = QtWidgets.QRadioButton("Absolute")
self.absolute_checkbox.setChecked(True)
self.relative_checkbox = QtWidgets.QRadioButton("Relative")
self.relative_scaling = ui_common.DynamicDoubleSpinBox()
- self.relative_scaling.setRange(0,1000)
+ self.relative_scaling.setRange(0, 1000)
self.remap_type_layout.addStretch()
self.remap_type_layout.addWidget(self.absolute_checkbox)
@@ -148,13 +131,18 @@ def _create_ui(self):
warning_container = QtWidgets.QWidget()
warning_layout = QtWidgets.QHBoxLayout(warning_container)
warning_color = gremlin.ui.ui_common.Color.warningColor()
- warning_widget = gremlin.ui.ui_common.QIconLabel("ph.shield-warning-fill",use_qta=True,icon_color=QtGui.QColor(warning_color),text="Legacy mapper - consider using VJoy Remap for additional functionality", use_wrap=False)
+ warning_widget = gremlin.ui.ui_common.QIconLabel(
+ "ph.shield-warning-fill",
+ use_qta=True,
+ icon_color=QtGui.QColor(warning_color),
+ text="Legacy mapper - consider using VJoy Remap for additional functionality",
+ use_wrap=False,
+ )
warning_layout.addWidget(warning_widget)
warning_layout.addStretch()
- self.main_layout.addWidget(warning_container)
+ self.main_layout.addWidget(warning_container)
self.main_layout.setContentsMargins(0, 0, 0, 0)
-
def _populate_ui(self):
"""Populates the UI components."""
# Get the appropriate vjoy device identifier
@@ -164,8 +152,7 @@ def _populate_ui(self):
# Get the input type which can change depending on the container used
input_type = self.action_data.get_input_type()
-
-
+
if self.action_data.parent.tag == "hat_buttons":
input_type = InputType.JoystickButton
@@ -194,11 +181,7 @@ def _populate_ui(self):
vjoy_input_id = self.action_data.vjoy_input_id
try:
- self.vjoy_selector.set_selection(
- input_type,
- vjoy_dev_id,
- vjoy_input_id
- )
+ self.vjoy_selector.set_selection(input_type, vjoy_dev_id, vjoy_input_id)
if self.action_data.is_axis:
if self.action_data.axis_mode == "absolute":
@@ -215,8 +198,8 @@ def _populate_ui(self):
self.save_changes()
except gremlin.error.GremlinError as e:
util.display_error(
- f"A needed vJoy device is not accessible: {e}\n\n" +
- "Default values have been set for the input, but they are "
+ f"A needed vJoy device is not accessible: {e}\n\n"
+ + "Default values have been set for the input, but they are "
"not what has been specified."
)
log_sys_error(e)
@@ -245,29 +228,35 @@ def save_changes(self):
self.action_data.axis_scaling = self.relative_scaling.value()
# Signal changes
- #if input_type_changed:
+ # if input_type_changed:
if self.action_data.input_type == InputType.JoystickButton:
-
usage_data = gremlin.joystick_handling.VJoyUsageState()
if current_id is not None and current_id != -1:
# undo prior selection
- usage_data.set_usage_state(vjoy_id, current_id, action = self.action_data, state = False, emit = False)
+ usage_data.set_usage_state(
+ vjoy_id,
+ current_id,
+ action=self.action_data,
+ state=False,
+ emit=False,
+ )
# new selection
- usage_data.set_usage_state(vjoy_id, new_id, action = self.action_data, state = True, emit = False)
-
+ usage_data.set_usage_state(
+ vjoy_id, new_id, action=self.action_data, state=True, emit=False
+ )
+
el = gremlin.event_handler.EventListener()
el.button_usage_changed.emit(vjoy_id)
- #self.action_modified.emit()
+ # self.action_modified.emit()
self.notify_device_changed(emit_profile_changed=False)
except gremlin.error.GremlinError as e:
log_sys_error(e)
-
- def notify_device_changed(self, emit_profile_changed = True, emit_icon = True):
+ def notify_device_changed(self, emit_profile_changed=True, emit_icon=True):
state = gremlin.joystick_handling.VJoyUsageState()
el = gremlin.event_handler.EventListener()
event = gremlin.event_handler.DeviceChangeEvent()
@@ -281,14 +270,13 @@ def notify_device_changed(self, emit_profile_changed = True, emit_icon = True):
if emit_profile_changed:
el.profile_device_changed.emit(event)
if emit_icon:
- el.icon_changed.emit(event)
+ el.icon_changed.emit(event)
class RemapFunctor(gremlin.base_conditions.AbstractFunctor):
-
"""Executes a remap action when called."""
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
self.vjoy_device_id = action.vjoy_device_id
self.vjoy_input_id = action.vjoy_input_id
@@ -306,7 +294,7 @@ def __init__(self, action, parent = None):
self.axis_value = 0.0
self.test = False
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
input_type = event.getInputType()
if event.is_axis:
if event.is_repeater:
@@ -314,35 +302,39 @@ def process_event(self, event, value, extra_data = None):
else:
value = value.current
if self.axis_mode == "absolute":
- joystick_handling.VJoyProxy()[self.vjoy_device_id].axis(self.vjoy_input_id).value = value
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].axis(
+ self.vjoy_input_id
+ ).value = value
else:
self.should_stop_thread = abs(event.value) < 0.05
- self.axis_delta_value = \
- value.current * (self.axis_scaling / 1000.0)
+ self.axis_delta_value = value.current * (self.axis_scaling / 1000.0)
self.thread_last_update = time.time()
if self.thread_running is False:
if isinstance(self.thread, threading.Thread):
self.thread.join()
- self.thread = threading.Thread(target=self.relative_axis_thread, daemon=True)
+ self.thread = threading.Thread(
+ target=self.relative_axis_thread, daemon=True
+ )
self.thread.start()
elif input_type == InputType.JoystickButton:
- if event.event_type in [InputType.JoystickButton, InputType.Keyboard] \
- and event.is_pressed \
- and self.needs_auto_release:
+ if (
+ event.event_type in [InputType.JoystickButton, InputType.Keyboard]
+ and event.is_pressed
+ and self.needs_auto_release
+ ):
input_devices.ButtonReleaseActions().register_button_release(
- (self.vjoy_device_id, self.vjoy_input_id),
- event
+ (self.vjoy_device_id, self.vjoy_input_id), event
)
- joystick_handling.VJoyProxy()[self.vjoy_device_id] \
- .button(self.vjoy_input_id).is_pressed = value.is_pressed
-
-
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].button(
+ self.vjoy_input_id
+ ).is_pressed = value.is_pressed
elif input_type == InputType.JoystickHat:
- joystick_handling.VJoyProxy()[self.vjoy_device_id] \
- .hat(self.vjoy_input_id).direction = value.current
+ joystick_handling.VJoyProxy()[self.vjoy_device_id].hat(
+ self.vjoy_input_id
+ ).direction = value.current
return True
@@ -361,23 +353,21 @@ def relative_axis_thread(self):
return
self.axis_value = max(
- -1.0,
- min(1.0, self.axis_value + self.axis_delta_value)
+ -1.0, min(1.0, self.axis_value + self.axis_delta_value)
)
vjoy_dev.axis(self.vjoy_input_id).value = self.axis_value
- if self.should_stop_thread and \
- self.thread_last_update + 1.0 < time.time():
+ if (
+ self.should_stop_thread
+ and self.thread_last_update + 1.0 < time.time()
+ ):
self.thread_running = False
time.sleep(0.01)
except gremlin.error.VJoyError:
self.thread_running = False
-
-
class Remap(gremlin.base_profile.AbstractAction):
-
"""Action remapping physical joystick inputs to vJoy inputs."""
name = "Remap"
@@ -416,12 +406,12 @@ def __init__(self, parent):
input_type = self.get_input_type()
self.input_type = input_type
self.is_axis = self.input_is_axis()
-
+
self.axis_mode = "absolute"
self.axis_scaling = 1.0
def display_name(self):
- ''' returns a display string for the current configuration '''
+ """returns a display string for the current configuration"""
input_string = "Axis"
if self.input_type == InputType.JoystickButton:
input_string = "Button"
@@ -435,6 +425,7 @@ def icon(self):
:return icon representing the remap action
"""
import gremlin.shared_state
+
is_dark = gremlin.shared_state.is_dark_theme
# Do not return a valid icon if the input id itself is invalid
if self.vjoy_input_id is None:
@@ -448,25 +439,22 @@ def icon(self):
dark_stub = "dark_" if is_dark else ""
if input_string:
-
- #root_path = gremlin.shared_state.root_path
+ # root_path = gremlin.shared_state.root_path
# folder = os.path.join(root_path, "action_plugins", "remap")
# icon_file = os.path.join(folder, "gfx", f"icon_{input_string}_{self.vjoy_input_id:03d}.png")
# if os.path.isfile(icon_file):
# return icon_file
-
-
icon_file = f"{dark_stub}icon_{input_string}_{self.vjoy_input_id:03d}.png"
icon_path = gremlin.util.find_file(icon_file)
if os.path.isfile(icon_path):
return icon_file
-
+
log_sys_warn(f"Icon file: {icon_file}")
- log_sys_warn(f"Warning: unable to determine icon type: {self.input_type} for id {self.vjoy_input_id}")
+ log_sys_warn(
+ f"Warning: unable to determine icon type: {self.input_type} for id {self.vjoy_input_id}"
+ )
return None
-
-
def requires_virtual_button(self):
"""Returns whether or not the action requires an activation condition.
@@ -488,15 +476,14 @@ def requires_virtual_button(self):
else:
return True
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Populates the data storage with data from the XML node.
:param node XML node with which to populate the storage
"""
try:
-
self.vjoy_device_id = safe_read(node, "vjoy", int)
-
+
if "axis" in node.attrib:
self.input_type = InputType.JoystickAxis
self.vjoy_input_id = safe_read(node, "axis", int)
@@ -504,7 +491,13 @@ def _parse_xml(self, node, data = None):
self.input_type = InputType.JoystickButton
self.vjoy_input_id = safe_read(node, "button", int)
usage_data = gremlin.joystick_handling.VJoyUsageState()
- usage_data.set_usage_state(self.vjoy_device_id, self.vjoy_input_id, state = True, action = self, emit = False)
+ usage_data.set_usage_state(
+ self.vjoy_device_id,
+ self.vjoy_input_id,
+ state=True,
+ action=self,
+ emit=False,
+ )
elif "hat" in node.attrib:
self.input_type = InputType.JoystickHat
self.vjoy_input_id = safe_read(node, "hat", int)
@@ -516,9 +509,10 @@ def _parse_xml(self, node, data = None):
f"Invalid remap type provided: {node.attrib}"
)
-
- if self.get_input_type() == InputType.JoystickAxis and \
- self.input_type == InputType.JoystickAxis:
+ if (
+ self.get_input_type() == InputType.JoystickAxis
+ and self.input_type == InputType.JoystickAxis
+ ):
self.axis_mode = safe_read(node, "axis-type", str, "absolute")
self.axis_scaling = safe_read(node, "axis-scaling", float, 1.0)
except ProfileError:
@@ -534,17 +528,15 @@ def _generate_xml(self):
node.set("vjoy", str(self.vjoy_device_id))
if self.input_type == InputType.Keyboard:
node.set(
- InputType.to_string(InputType.JoystickButton),
- str(self.vjoy_input_id)
+ InputType.to_string(InputType.JoystickButton), str(self.vjoy_input_id)
)
else:
- node.set(
- InputType.to_string(self.input_type),
- str(self.vjoy_input_id)
- )
+ node.set(InputType.to_string(self.input_type), str(self.vjoy_input_id))
- if self.get_input_type() == InputType.JoystickAxis and \
- self.input_type == InputType.JoystickAxis:
+ if (
+ self.get_input_type() == InputType.JoystickAxis
+ and self.input_type == InputType.JoystickAxis
+ ):
node.set("axis-type", safe_format(self.axis_mode, str))
node.set("axis-scaling", safe_format(self.axis_scaling, float))
diff --git a/action_plugins/response_curve/__init__.py b/action_plugins/response_curve/__init__.py
index 431d9456..e63317e2 100644
--- a/action_plugins/response_curve/__init__.py
+++ b/action_plugins/response_curve/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -43,7 +43,6 @@
class SymmetryMode(enum.Enum):
-
"""Symmetry modes for response curves."""
NoSymmetry = 1
@@ -52,26 +51,24 @@ class SymmetryMode(enum.Enum):
@staticmethod
def to_string(value):
return _symmetry_mode_to_string[value]
-
+
@staticmethod
def to_enum(value):
return _symmetry_mode_to_enum[value]
+
_symmetry_mode_to_string = {
SymmetryMode.NoSymmetry: "none",
- SymmetryMode.Diagonal: "diagonal"
+ SymmetryMode.Diagonal: "diagonal",
}
_symmetry_mode_to_enum = {
- "none" : SymmetryMode.NoSymmetry,
- "diagonal" : SymmetryMode.Diagonal
+ "none": SymmetryMode.NoSymmetry,
+ "diagonal": SymmetryMode.Diagonal,
}
-
-
class ControlPoint:
-
"""Represents a single control point in a response curve.
Each control point has at least a center point but can possibly have
@@ -154,9 +151,11 @@ def set_handle(self, index, point):
if len(self.handles) > index:
self._last_modified = time.time()
self.handles[index] = point
- if len(self.handles) == 2 and \
- isinstance(self._model, CubicBezierSplineModel) and \
- self._model.handle_symmetry_enabled:
+ if (
+ len(self.handles) == 2
+ and isinstance(self._model, CubicBezierSplineModel)
+ and self._model.handle_symmetry_enabled
+ ):
alt_point = self._center + (self._center - point)
alt_index = 1 if index == 0 else 0
self.handles[alt_index] = alt_point
@@ -177,7 +176,6 @@ def __eq__(self, other):
class AbstractCurveModel(QtCore.QObject):
-
"""Abstract base class for all curve models."""
# Signal emitted when model data changes
@@ -187,7 +185,7 @@ class AbstractCurveModel(QtCore.QObject):
def __init__(self, profile_data, parent=None):
"""Initializes an empty model.
-
+
:param profile_data the data of this response curve
"""
super().__init__(parent)
@@ -213,7 +211,6 @@ def model_updated(self):
self.save_to_profile()
self.content_modified.emit()
-
def get_curve_function(self):
"""Returns the curve function corresponding to the model.
@@ -243,14 +240,13 @@ def add_control_point(self, point, handles=()):
if new_point in self._control_points:
# point already exists, ignore
return
-
+
self._control_points.append(new_point)
if self.symmetry_mode == SymmetryMode.Diagonal:
- self._control_points.append(self._create_control_point(
- Point2D(-point.x, -point.y),
- handles
- ))
+ self._control_points.append(
+ self._create_control_point(Point2D(-point.x, -point.y), handles)
+ )
self.save_to_profile()
self.content_added.emit()
@@ -338,7 +334,6 @@ def set_symmetry_mode(self, mode):
class CubicSplineModel(AbstractCurveModel):
-
"""Represents a simple cubic spline model."""
def __init__(self, profile_data):
@@ -385,9 +380,7 @@ def is_valid_point(self, point, identifier=None):
def _init_from_profile_data(self):
"""Initializes the control points based on profile data."""
for coord in self._profile_data.control_points:
- self._control_points.append(
- ControlPoint(self, Point2D(coord[0], coord[1]))
- )
+ self._control_points.append(ControlPoint(self, Point2D(coord[0], coord[1])))
def save_to_profile(self):
"""Ensures that the control point data is properly recorded in
@@ -399,7 +392,6 @@ def save_to_profile(self):
class CubicBezierSplineModel(AbstractCurveModel):
-
"""Represents a cubic bezier spline model."""
def __init__(self, profile_data):
@@ -413,9 +405,7 @@ def get_curve_function(self):
:return curve function corresponding to the model
"""
points = []
- sorted_control_points = sorted(
- self._control_points, key=lambda e: e.center.x
- )
+ sorted_control_points = sorted(self._control_points, key=lambda e: e.center.x)
for i, pt in enumerate(sorted_control_points):
if i == 0:
points.append((pt.center.x, pt.center.y))
@@ -449,7 +439,7 @@ def _create_control_point(self, point, handles=()):
if len(handles) == 0:
handles = (
Point2D(point.x - 0.05, point.y),
- Point2D(point.x + 0.05, point.y)
+ Point2D(point.x + 0.05, point.y),
)
return ControlPoint(self, point, handles)
@@ -473,53 +463,45 @@ def _init_from_profile_data(self):
# If the data appears to be invalid insert a valid default
if len(self._profile_data.control_points) < 4:
self._profile_data.control_points = []
- self._profile_data.control_points.extend([
- (-1, -1),
- (-0.9, -0.9),
- (0.9, 0.9),
- (1, 1)
- ])
+ self._profile_data.control_points.extend(
+ [(-1, -1), (-0.9, -0.9), (0.9, 0.9), (1, 1)]
+ )
coordinates = self._profile_data.control_points
self._control_points.append(
ControlPoint(
self,
Point2D(coordinates[0][0], coordinates[0][1]),
- [Point2D(coordinates[1][0], coordinates[1][1])]
+ [Point2D(coordinates[1][0], coordinates[1][1])],
)
)
- for i in range(3, len(coordinates)-3, 3):
+ for i in range(3, len(coordinates) - 3, 3):
self._control_points.append(
ControlPoint(
self,
Point2D(coordinates[i][0], coordinates[i][1]),
[
- Point2D(coordinates[i-1][0], coordinates[i-1][1]),
- Point2D(coordinates[i+1][0], coordinates[i+1][1])
- ]
+ Point2D(coordinates[i - 1][0], coordinates[i - 1][1]),
+ Point2D(coordinates[i + 1][0], coordinates[i + 1][1]),
+ ],
)
)
self._control_points.append(
ControlPoint(
self,
Point2D(coordinates[-1][0], coordinates[-1][1]),
- [Point2D(coordinates[-2][0], coordinates[-2][1])]
+ [Point2D(coordinates[-2][0], coordinates[-2][1])],
)
)
def save_to_profile(self):
"""Ensure that UI and profile data are in sync."""
- control_points = sorted(
- self._control_points,
- key=lambda entry: entry.center.x
- )
+ control_points = sorted(self._control_points, key=lambda entry: entry.center.x)
self._profile_data.control_points = []
for cp in control_points:
if cp.center.x == -1:
- self._profile_data.control_points.append(
- [cp.center.x, cp.center.y]
- )
+ self._profile_data.control_points.append([cp.center.x, cp.center.y])
self._profile_data.control_points.append(
[cp.handles[0].x, cp.handles[0].y]
)
@@ -527,25 +509,18 @@ def save_to_profile(self):
self._profile_data.control_points.append(
[cp.handles[0].x, cp.handles[0].y]
)
- self._profile_data.control_points.append(
- [cp.center.x, cp.center.y]
- )
+ self._profile_data.control_points.append([cp.center.x, cp.center.y])
else:
self._profile_data.control_points.append(
[cp.handles[0].x, cp.handles[0].y]
)
- self._profile_data.control_points.append(
- [cp.center.x, cp.center.y]
- )
+ self._profile_data.control_points.append([cp.center.x, cp.center.y])
self._profile_data.control_points.append(
[cp.handles[1].x, cp.handles[1].y]
)
-
-
class DataPointGraphicsItem(QtWidgets.QGraphicsEllipseItem):
-
"""UI Item representing a data point center of a control point."""
def __init__(self, x, y, parent=None):
@@ -560,7 +535,7 @@ def __init__(self, x, y, parent=None):
self.y = y
self.setPos(x, y)
-
+
self.setZValue(3)
self.setBrush(QtGui.QBrush(QtCore.Qt.red))
@@ -569,13 +544,12 @@ def update(self, x, y):
self.y = y
self.redraw()
-
def redraw(self):
"""Forces a position update of the ui element."""
self.setPos(self.x, self.y)
-class ControlPointGraphicsItem(QtWidgets.QGraphicsEllipseItem):
+class ControlPointGraphicsItem(QtWidgets.QGraphicsEllipseItem):
"""UI Item representing the center of a control point."""
def __init__(self, control_point, parent=None):
@@ -585,13 +559,13 @@ def __init__(self, control_point, parent=None):
:param parent the parent of this widget
"""
super().__init__(-4, -4, 8, 8, parent)
- assert(isinstance(control_point, ControlPoint))
+ assert isinstance(control_point, ControlPoint)
self.control_point = control_point
self.setPos(
g_scene_size * self.control_point.center.x,
- -g_scene_size * self.control_point.center.y
+ -g_scene_size * self.control_point.center.y,
)
self.setZValue(2)
self.setBrush(QtGui.QBrush(QtCore.Qt.gray))
@@ -608,7 +582,7 @@ def redraw(self):
"""Forces a position update of the ui element."""
self.setPos(
g_scene_size * self.control_point.center.x,
- -g_scene_size * self.control_point.center.y
+ -g_scene_size * self.control_point.center.y,
)
def set_active(self, is_active):
@@ -642,7 +616,7 @@ def mouseMoveEvent(self, evt):
# Create desired point
new_point = Point2D(
gremlin.util.clamp(evt.scenePos().x() / g_scene_size, -1.0, 1.0),
- gremlin.util.clamp(-evt.scenePos().y() / g_scene_size, -1.0, 1.0)
+ gremlin.util.clamp(-evt.scenePos().y() / g_scene_size, -1.0, 1.0),
)
# Only allow movement along the y axis if the point is on either
@@ -654,7 +628,6 @@ def mouseMoveEvent(self, evt):
class CurveHandleGraphicsItem(QtWidgets.QGraphicsRectItem):
-
"""UI Item representing a handle of a control point."""
def __init__(self, index, point, parent):
@@ -679,8 +652,8 @@ def redraw(self):
point = self.parent.control_point.handles[self.index]
delta = point - center
- self.setPos(delta.x*g_scene_size, -delta.y*g_scene_size)
- self.line.setLine(delta.x*g_scene_size, -delta.y*g_scene_size, 0, 0)
+ self.setPos(delta.x * g_scene_size, -delta.y * g_scene_size)
+ self.line.setLine(delta.x * g_scene_size, -delta.y * g_scene_size, 0, 0)
def set_active(self, is_active):
"""Handles changing the selected state of an item
@@ -715,17 +688,16 @@ def mouseMoveEvent(self, evt):
# Create desired point
new_point = Point2D(
gremlin.util.clamp(evt.scenePos().x() / g_scene_size, -1.0, 1.0),
- gremlin.util.clamp(-evt.scenePos().y() / g_scene_size, -1.0, 1.0)
+ gremlin.util.clamp(-evt.scenePos().y() / g_scene_size, -1.0, 1.0),
)
self.parent.control_point.set_handle(self.index, new_point)
class CurveView(QtWidgets.QGraphicsScene):
-
"""Visualization of the entire curve editor UI element."""
- def __init__(self, curve_model, point_editor, show_input_axis = False, parent=None):
+ def __init__(self, curve_model, point_editor, show_input_axis=False, parent=None):
"""Creates a new instance.
:param curve_model the model to visualize
@@ -754,10 +726,7 @@ def _populate_from_model(self):
"""Populates the UI based on content stored in the model."""
# Remove old curve path and update control points
for item in self.items():
- if type(item) in [
- ControlPointGraphicsItem,
- CurveHandleGraphicsItem
- ]:
+ if type(item) in [ControlPointGraphicsItem, CurveHandleGraphicsItem]:
self.removeItem(item)
for cp in self.model.get_control_points():
@@ -765,9 +734,9 @@ def _populate_from_model(self):
if self.show_input_axis:
if not self.tracker:
- self.tracker = DataPointGraphicsItem(0,0)
+ self.tracker = DataPointGraphicsItem(0, 0)
self.addItem(self.tracker)
-
+
self.redraw_scene()
def add_control_point(self, point, handles=()):
@@ -790,14 +759,12 @@ def _editor_update(self, value):
if self.current_item:
new_point = Point2D(
- self.point_editor.x_input.value(),
- self.point_editor.y_input.value()
+ self.point_editor.x_input.value(), self.point_editor.y_input.value()
)
if abs(self.current_item.control_point.center.x) == 1.0:
new_point.x = self.current_item.control_point.center.x
self.current_item.control_point.set_center(new_point)
self.model.save_to_profile()
-
def _select_item(self, item):
"""Handles drawing of an item being selected.
@@ -805,8 +772,9 @@ def _select_item(self, item):
:param item the item being selected
"""
# Ensure we want / can select the provided item
- if isinstance(item, ControlPointGraphicsItem) or \
- isinstance(item, CurveHandleGraphicsItem):
+ if isinstance(item, ControlPointGraphicsItem) or isinstance(
+ item, CurveHandleGraphicsItem
+ ):
if self.current_item and item != self.current_item:
self.current_item.set_active(False)
self.current_item = item
@@ -833,19 +801,16 @@ def redraw_scene(self):
curve_fn = self.model.get_curve_function()
if curve_fn:
path = QtGui.QPainterPath(
- QtCore.QPointF(int(-g_scene_size),int(-g_scene_size*curve_fn(-1)))
+ QtCore.QPointF(int(-g_scene_size), int(-g_scene_size * curve_fn(-1)))
)
- for x in range(-int(g_scene_size), int(g_scene_size+1), 2):
+ for x in range(-int(g_scene_size), int(g_scene_size + 1), 2):
path.lineTo(x, -g_scene_size * curve_fn(x / g_scene_size))
self.addPath(path, QtGui.QPen(QtGui.QColor(0, 200, 0)))
# Update editor widget fields
if self.current_item:
if isinstance(self.current_item, ControlPointGraphicsItem):
- self.point_editor.set_values(
- self.current_item.control_point.center
- )
-
+ self.point_editor.set_values(self.current_item.control_point.center)
def mousePressEvent(self, evt):
"""Informs the model about point selection if a point is clicked.
@@ -865,10 +830,12 @@ def mouseDoubleClickEvent(self, evt):
if evt.button() == QtCore.Qt.LeftButton:
item = self.itemAt(evt.scenePos(), QtGui.QTransform())
if not isinstance(item, ControlPointGraphicsItem):
- self.add_control_point(Point2D(
- evt.scenePos().x() / g_scene_size,
- evt.scenePos().y() / -g_scene_size
- ))
+ self.add_control_point(
+ Point2D(
+ evt.scenePos().x() / g_scene_size,
+ evt.scenePos().y() / -g_scene_size,
+ )
+ )
def keyPressEvent(self, evt):
"""Removes the currently selected control point if the Del
@@ -876,8 +843,9 @@ def keyPressEvent(self, evt):
:param evt the keyboard event to process.
"""
- if evt.key() == QtCore.Qt.Key_Delete and \
- isinstance(self.current_item, ControlPointGraphicsItem):
+ if evt.key() == QtCore.Qt.Key_Delete and isinstance(
+ self.current_item, ControlPointGraphicsItem
+ ):
# Disallow removing edge points
if abs(self.current_item.control_point.center.x) == 1.0:
return
@@ -893,11 +861,12 @@ def drawBackground(self, painter, rect):
:param painter the painter object
:param rect the drawing rectangle
"""
- painter.drawImage(QtCore.QPoint(int(-g_scene_size), int(-g_scene_size)),self.background_image)
+ painter.drawImage(
+ QtCore.QPoint(int(-g_scene_size), int(-g_scene_size)), self.background_image
+ )
class ControlPointEditorWidget(QtWidgets.QWidget):
-
"""Widgets allowing the control point coordinates to be changed
via text fields."""
@@ -943,7 +912,6 @@ def set_values(self, point):
class DeadzoneWidget(QtWidgets.QWidget):
-
"""Widget visualizing deadzone settings as well as allowing the
modification of these."""
@@ -985,38 +953,29 @@ def __init__(self, profile_data, parent=None):
self.right_upper.setMinimum(0.0)
self.right_upper.setMaximum(1.0)
- self._normalizer =\
- self.left_slider.range()[1] - self.left_slider.range()[0]
+ self._normalizer = self.left_slider.range()[1] - self.left_slider.range()[0]
# Hook up all the required callbacks
self.left_slider.valueChanged.connect(self._update_left)
self.right_slider.valueChanged.connect(self._update_right)
self.left_lower.valueChanged.connect(
lambda value: self._update_from_spinner(
- value,
- DualSlider.LowerHandle,
- self.left_slider
+ value, DualSlider.LowerHandle, self.left_slider
)
)
self.left_upper.valueChanged.connect(
lambda value: self._update_from_spinner(
- value,
- DualSlider.UpperHandle,
- self.left_slider
+ value, DualSlider.UpperHandle, self.left_slider
)
)
self.right_lower.valueChanged.connect(
lambda value: self._update_from_spinner(
- value,
- DualSlider.LowerHandle,
- self.right_slider
+ value, DualSlider.LowerHandle, self.right_slider
)
)
self.right_upper.valueChanged.connect(
lambda value: self._update_from_spinner(
- value,
- DualSlider.UpperHandle,
- self.right_slider
+ value, DualSlider.UpperHandle, self.right_slider
)
)
@@ -1050,7 +1009,7 @@ def get_values(self):
self.left_lower.value(),
self.left_upper.value(),
self.right_lower.value(),
- self.right_upper.value()
+ self.right_upper.value(),
]
def _update_left(self, handle, value):
@@ -1093,7 +1052,6 @@ def _update_from_spinner(self, value, handle, widget):
class ResponseCurveWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""Widget that allows configuring the response of an axis to
user inputs."""
@@ -1108,38 +1066,37 @@ def __init__(self, action_data, parent=None):
self.is_inverted = False
self.action_data = action_data
-
if action_data.show_input_axis:
# hook the hardware input so we can see it on the curve
el = gremlin.event_handler.EventListener()
el.joystick_event.connect(self._joystick_handler)
-
def _update_value(self, value):
- ''' updates dot on the curve based on the value -1 to +1 '''
- curve_value = gremlin.joystick_handling.scale_to_range(value, target_min = -g_scene_size, target_max= +g_scene_size+1) # value on the curve by pixel x
-
- ''' draw the current value on the curve '''
+ """updates dot on the curve based on the value -1 to +1"""
+ curve_value = gremlin.joystick_handling.scale_to_range(
+ value, target_min=-g_scene_size, target_max=+g_scene_size + 1
+ ) # value on the curve by pixel x
+
+ """ draw the current value on the curve """
curve_fn = self.curve_model.get_curve_function()
if curve_fn:
# get the position of the marker
x = curve_value
y = -g_scene_size * curve_fn(x / g_scene_size)
- #print(f"{x} {y}")
- self.curve_scene.tracker.update(x,y)
+ # print(f"{x} {y}")
+ self.curve_scene.tracker.update(x, y)
self.input_raw_widget.setText(f"{value:0.3f}")
- curved = gremlin.util.clamp(curve_fn(value),-1.0, +1.0)
+ curved = gremlin.util.clamp(curve_fn(value), -1.0, +1.0)
self.input_curved_widget.setText(f"{curved:0.3f}")
-
def _joystick_handler(self, event):
- ''' handles joystick input '''
+ """handles joystick input"""
if not event.is_axis:
# ignore if not an axis event
return
-
+
if gremlin.shared_state.is_running:
# ignore if profile is running
return
@@ -1147,18 +1104,19 @@ def _joystick_handler(self, event):
if self.action_data.hardware_device_guid != event.device_guid:
# ignore if a different input device
return
-
+
if self.action_data.hardware_input_id != event.identifier:
# ignore if a different input axis on the input device
return
-
+
if self.curve_scene.tracker is None:
return
- value = gremlin.joystick_handling.scale_to_range(event.raw_value, source_min = -32767, source_max = 32767) # -1 to 1 value
+ value = gremlin.joystick_handling.scale_to_range(
+ event.raw_value, source_min=-32767, source_max=32767
+ ) # -1 to 1 value
self._update_value(value)
-
-
+
@QtCore.Slot()
def profile_start(self):
# listen to hardware events
@@ -1176,31 +1134,32 @@ def profile_stop(self):
def _create_ui(self):
"""Creates the required UI elements."""
-
warning_color = gremlin.ui.ui_common.Color.warningColor()
- warning_widget = gremlin.ui.ui_common.QIconLabel("ph.shield-warning-fill",use_qta=True,icon_color=QtGui.QColor(warning_color),text="Legacy mapper - consider using Response Curve Ex for additional functionality", use_wrap=False)
+ warning_widget = gremlin.ui.ui_common.QIconLabel(
+ "ph.shield-warning-fill",
+ use_qta=True,
+ icon_color=QtGui.QColor(warning_color),
+ text="Legacy mapper - consider using Response Curve Ex for additional functionality",
+ use_wrap=False,
+ )
self.main_layout.addWidget(warning_widget)
-
self.container_options_widget = QtWidgets.QWidget()
- self.container_options_layout = QtWidgets.QHBoxLayout(self.container_options_widget)
+ self.container_options_layout = QtWidgets.QHBoxLayout(
+ self.container_options_widget
+ )
# Dropdown menu for the different curve types
self.curve_type_selection = gremlin.ui.ui_common.QComboBox()
self.curve_type_selection.addItem("Cubic Spline")
self.curve_type_selection.addItem("Cubic Bezier Spline")
- self.curve_type_selection.currentTextChanged.connect(
- self._change_curve_type
- )
+ self.curve_type_selection.currentTextChanged.connect(self._change_curve_type)
# Curve manipulation options
-
-
+
self.container_options_layout.addWidget(QtWidgets.QLabel("Curve Type:"))
self.container_options_layout.addWidget(self.curve_type_selection)
-
-
# Curve inversion
self.curve_inversion = QtWidgets.QPushButton("Invert")
self.curve_inversion.clicked.connect(self._invert_curve)
@@ -1208,14 +1167,12 @@ def _create_ui(self):
# Curve symmetry
self.curve_symmetry = QtWidgets.QCheckBox("Diagonal Symmetry")
- self.curve_symmetry.setChecked(self.action_data.symmetry_mode == SymmetryMode.Diagonal)
+ self.curve_symmetry.setChecked(
+ self.action_data.symmetry_mode == SymmetryMode.Diagonal
+ )
self.curve_symmetry.clicked.connect(self._curve_symmetry_cb)
self.container_options_layout.addWidget(self.curve_symmetry)
-
-
-
-
# Create all objects required for the response curve UI
self.control_point_editor = ControlPointEditorWidget()
# Response curve model used
@@ -1225,14 +1182,13 @@ def _create_ui(self):
self.curve_model = CubicBezierSplineModel(self.action_data)
else:
raise gremlin.error.ProfileError("Invalid curve type")
-
+
# mode
self.curve_model.set_symmetry_mode(self.action_data.symmetry_mode)
-
- # Handle symmetry
+ # Handle symmetry
self.handle_symmetry = QtWidgets.QCheckBox("Force smooth curves")
-
+
if self.action_data.mapping_type == "cubic-bezier-spline":
self.handle_symmetry.setChecked(self.curve_model.handle_symmetry_enabled)
self.handle_symmetry.stateChanged.connect(self._handle_symmetry_cb)
@@ -1253,7 +1209,7 @@ def _create_ui(self):
self.input_curved_widget = QtWidgets.QLineEdit()
self.input_curved_widget.setMaximumWidth(width)
self.input_curved_widget.setReadOnly(True)
-
+
self.container_options_layout.addWidget(QtWidgets.QLabel("Input:"))
self.container_options_layout.addWidget(self.input_raw_widget)
self.container_options_layout.addWidget(QtWidgets.QLabel("Curved:"))
@@ -1263,7 +1219,7 @@ def _create_ui(self):
self.curve_scene = CurveView(
self.curve_model,
self.control_point_editor,
- self.action_data.show_input_axis
+ self.action_data.show_input_axis,
)
# Create view displaying the curve scene
@@ -1282,13 +1238,9 @@ def _create_ui(self):
self.main_layout.addWidget(self.deadzone_label)
self.main_layout.addWidget(self.deadzone_widget)
-
-
def _populate_ui(self):
"""Populates the UI elements."""
- self.curve_type_selection.currentTextChanged.disconnect(
- self._change_curve_type
- )
+ self.curve_type_selection.currentTextChanged.disconnect(self._change_curve_type)
# Create mapping from XML name to ui name
name_map = {}
@@ -1304,13 +1256,14 @@ def _populate_ui(self):
# Set deadzone values
self.deadzone_widget.set_values(self.action_data.deadzone)
- self.curve_type_selection.currentTextChanged.connect(
- self._change_curve_type
- )
+ self.curve_type_selection.currentTextChanged.connect(self._change_curve_type)
# update the joystick input
if self.action_data.show_input_axis:
- value = gremlin.joystick_handling.get_curved_axis(self.action_data.hardware_device_guid, self.action_data.hardware_input_id)
+ value = gremlin.joystick_handling.get_curved_axis(
+ self.action_data.hardware_device_guid,
+ self.action_data.hardware_input_id,
+ )
self._update_value(value)
def _change_curve_type(self, curve_type):
@@ -1320,7 +1273,7 @@ def _change_curve_type(self, curve_type):
"""
model_map = {
"Cubic Spline": CubicSplineModel,
- "Cubic Bezier Spline": CubicBezierSplineModel
+ "Cubic Bezier Spline": CubicBezierSplineModel,
}
# Create new model
@@ -1329,7 +1282,10 @@ def _change_curve_type(self, curve_type):
self.action_data.mapping_type = "cubic-spline"
elif curve_type == "Cubic Bezier Spline":
self.action_data.control_points = [
- (-1.0, -1.0), (-0.8, -0.8), (0.8, 0.8), (1.0, 1.0)
+ (-1.0, -1.0),
+ (-0.8, -0.8),
+ (0.8, 0.8),
+ (1.0, 1.0),
]
self.action_data.mapping_type = "cubic-bezier-spline"
@@ -1343,14 +1299,14 @@ def _change_curve_type(self, curve_type):
elif self.action_data.mapping_type == "cubic-bezier-spline":
self.handle_symmetry.setVisible(True)
self.handle_symmetry.stateChanged.connect(self._handle_symmetry_cb)
-
+
self.curve_symmetry.setChecked(False)
# Recreate the UI components
self.curve_scene = CurveView(
self.curve_model,
self.control_point_editor,
- self.action_data.show_input_axis
+ self.action_data.show_input_axis,
)
self.curve_view = QtWidgets.QGraphicsView(self.curve_scene)
self._configure_response_curve_view()
@@ -1368,9 +1324,7 @@ def _curve_symmetry_cb(self, checked):
def _handle_symmetry_cb(self, state):
if not isinstance(self.curve_model, CubicBezierSplineModel):
- syslog.error(
- "Handle symmetry callback in non bezier curve attempted."
- )
+ syslog.error("Handle symmetry callback in non bezier curve attempted.")
return
self.curve_model.set_handle_symmetry(state == QtCore.Qt.Checked.value)
@@ -1380,12 +1334,11 @@ def _configure_response_curve_view(self):
self.curve_view = QtWidgets.QGraphicsView(self.curve_scene)
self.curve_view.setFixedSize(QtCore.QSize(510, 510))
self.curve_view.setFrameShape(QtWidgets.QFrame.NoFrame)
- self.curve_view.setSceneRect(QtCore.QRectF(
- -g_scene_size,
- -g_scene_size,
- 2*g_scene_size,
- 2*g_scene_size
- ))
+ self.curve_view.setSceneRect(
+ QtCore.QRectF(
+ -g_scene_size, -g_scene_size, 2 * g_scene_size, 2 * g_scene_size
+ )
+ )
gremlin.ui.ui_common.clear_layout(self.curve_view_layout)
self.curve_view_layout.addStretch()
self.curve_view_layout.addWidget(self.curve_view)
@@ -1396,56 +1349,47 @@ def _invert_curve(self):
class ResponseCurveFunctor(gremlin.base_profile.AbstractFunctor):
-
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
- v1,v2,v3,v4 = action.deadzone
+ v1, v2, v3, v4 = action.deadzone
if v2 is None:
v2 = 0.0
if v3 is None:
v3 = 0.0
self.deadzone_fn = lambda value: gremlin.input_devices.deadzone(
- value,
- v1,
- v2,
- v3,
- v4
+ value, v1, v2, v3, v4
)
if action.mapping_type == "cubic-spline":
self.response_fn = gremlin.spline.CubicSpline(action.control_points)
elif action.mapping_type == "cubic-bezier-spline":
- self.response_fn = \
- gremlin.spline.CubicBezierSpline(action.control_points)
+ self.response_fn = gremlin.spline.CubicBezierSpline(action.control_points)
else:
raise gremlin.error.GremlinError("Invalid curve type")
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
value.current = self.response_fn(self.deadzone_fn(value.current))
# print ("response curve")
return True
class ResponseCurve(gremlin.base_profile.AbstractAction):
-
"""Represents axis response curve mapping."""
name = "Response Curve"
tag = "response-curve"
default_button_activation = (True, True)
-
+
# override allowed input if different from default
- input_types = [
- InputType.JoystickAxis
- ]
+ input_types = [InputType.JoystickAxis]
functor = ResponseCurveFunctor
widget = ResponseCurveWidget
curve_name_map = {
"Cubic Spline": "cubic-spline",
- "Cubic Bezier Spline": "cubic-bezier-spline"
+ "Cubic Bezier Spline": "cubic-bezier-spline",
}
def __init__(self, parent):
@@ -1465,7 +1409,7 @@ def __init__(self, parent):
def icon(self):
"""Returns the icon representing the action."""
return "mdi.chart-bell-curve"
- #return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
+ # return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
def requires_virtual_button(self):
"""Returns whether or not an activation condition is needed.
@@ -1474,7 +1418,7 @@ def requires_virtual_button(self):
"""
return False
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Parses the XML corresponding to a response curve.
:param node the XML node to parse
@@ -1484,7 +1428,6 @@ def _parse_xml(self, node, data = None):
mode = node.get("mode")
self.symmetry_mode = SymmetryMode.to_enum(mode)
-
self.control_points = []
for child in node:
if child.tag == "deadzone":
@@ -1492,16 +1435,15 @@ def _parse_xml(self, node, data = None):
float(child.get("low")),
float(child.get("center-low")),
float(child.get("center-high")),
- float(child.get("high"))
+ float(child.get("high")),
]
elif child.tag == "mapping":
self.mapping_type = child.get("type")
self.control_points = []
for point in child.iter("control-point"):
- self.control_points.append((
- float(point.get("x")),
- float(point.get("y"))
- ))
+ self.control_points.append(
+ (float(point.get("x")), float(point.get("y")))
+ )
def _generate_xml(self):
diff --git a/action_plugins/response_curve_ex/__init__.py b/action_plugins/response_curve_ex/__init__.py
index fd4f3f18..c4e3d38b 100644
--- a/action_plugins/response_curve_ex/__init__.py
+++ b/action_plugins/response_curve_ex/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -40,13 +40,11 @@
import gremlin.spline
-
class ResponseCurveExWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""Widget that allows configuring the response of an axis to
user inputs."""
- def __init__(self, action_data : ResponseCurveEx, parent=None):
+ def __init__(self, action_data: ResponseCurveEx, parent=None):
"""Creates a new instance.
:param action_data the data associated with this specific action.
@@ -57,11 +55,11 @@ def __init__(self, action_data : ResponseCurveEx, parent=None):
self.is_inverted = False
self.action_data = action_data
-
-
def _create_ui(self):
"""Creates the required UI elements."""
- self.curve_widget = gremlin.curve_handler.AxisCurveWidget(self.action_data.curve_data, self)
+ self.curve_widget = gremlin.curve_handler.AxisCurveWidget(
+ self.action_data.curve_data, self
+ )
self.main_layout.addWidget(self.curve_widget)
el = gremlin.event_handler.EventListener()
@@ -86,14 +84,13 @@ def _profile_stop(self):
el = gremlin.event_handler.EventListener()
el.joystick_event.connect(self._joystick_event_handler)
-
def _joystick_event_handler(self, event):
- ''' handles joystick input '''
+ """handles joystick input"""
if not event.is_axis:
# ignore if not an axis event
return
-
+
if gremlin.shared_state.is_running:
# ignore if profile is running
return
@@ -101,40 +98,36 @@ def _joystick_event_handler(self, event):
if self.action_data.hardware_device_guid != event.device_guid:
# ignore if a different input device
return
-
+
if self.action_data.hardware_input_id != event.identifier:
# ignore if a different input axis on the input device
return
-
+
self.curve_widget.update_value(event.value)
-
-class ResponseCurveExFunctor(gremlin.base_profile.AbstractFunctor):
- def __init__(self, action_data : ResponseCurveEx, parent = None) :
+class ResponseCurveExFunctor(gremlin.base_profile.AbstractFunctor):
+ def __init__(self, action_data: ResponseCurveEx, parent=None):
super().__init__(action_data, parent)
self.curve_data = action_data.curve_data
self.curve_data.curve_update()
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
if event.is_axis:
value.current = self.curve_data.curve_value(value.current)
return True
class ResponseCurveEx(gremlin.base_profile.AbstractAction):
-
"""Represents axis response curve mapping."""
name = "Response Curve Ex"
tag = "response-curve-ex"
default_button_activation = (True, True)
-
+
# override allowed input if different from default
- input_types = [
- InputType.JoystickAxis
- ]
+ input_types = [InputType.JoystickAxis]
functor = ResponseCurveExFunctor
widget = ResponseCurveExWidget
@@ -147,15 +140,18 @@ def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.curve_data = gremlin.curve_handler.AxisCurveData()
- self.curve_data.calibration = gremlin.ui.axis_calibration.CalibrationManager().getCalibration(self.hardware_device_guid, self.hardware_input_id)
+ self.curve_data.calibration = (
+ gremlin.ui.axis_calibration.CalibrationManager().getCalibration(
+ self.hardware_device_guid, self.hardware_input_id
+ )
+ )
self.curve_data.curve_update()
self.show_input_axis = gremlin.config.Configuration().show_input_axis
-
def icon(self):
"""Returns the icon representing the action."""
return "mdi.chart-bell-curve"
- #return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
+ # return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
def requires_virtual_button(self):
"""Returns whether or not an activation condition is needed.
@@ -164,7 +160,7 @@ def requires_virtual_button(self):
"""
return False
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Parses the XML corresponding to a response curve.
:param node the XML node to parse
@@ -172,15 +168,12 @@ def _parse_xml(self, node, data = None):
self.curve_data._parse_xml(node)
-
-
def _generate_xml(self):
"""Generates a XML node corresponding to this object.
:return XML node representing the object's data
"""
-
node = self.curve_data._generate_xml()
node.tag = "response-curve-ex"
return node
diff --git a/action_plugins/resume/__init__.py b/action_plugins/resume/__init__.py
index 7f2acbe2..3f5789e2 100644
--- a/action_plugins/resume/__init__.py
+++ b/action_plugins/resume/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -26,7 +26,6 @@
class ResumeActionWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""Widget for the resume action."""
def __init__(self, action_data, parent=None):
@@ -42,18 +41,17 @@ def _populate_ui(self):
class ResumeActionFunctor(gremlin.base_profile.AbstractFunctor):
-
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
import gremlin.control_action
+
gremlin.control_action.resume()
return True
class ResumeAction(gremlin.base_profile.AbstractAction):
-
"""Action to resume callback execution."""
name = "Resume"
@@ -73,23 +71,20 @@ class ResumeAction(gremlin.base_profile.AbstractAction):
def icon(self):
return "ei.play-circle-o"
- #return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
+ # return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
def display_name(self):
- ''' returns a display string for the current configuration '''
+ """returns a display string for the current configuration"""
return "Resume"
def requires_virtual_button(self):
- return self.get_input_type() in [
- InputType.JoystickAxis,
- InputType.JoystickHat
- ]
+ return self.get_input_type() in [InputType.JoystickAxis, InputType.JoystickHat]
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
pass
def _generate_xml(self):
diff --git a/action_plugins/run_process/__init__.py b/action_plugins/run_process/__init__.py
index 731f5150..3c97b471 100644
--- a/action_plugins/run_process/__init__.py
+++ b/action_plugins/run_process/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -34,7 +34,6 @@
class RunProcessWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""Widget which allows the configuration of TTS actions."""
def __init__(self, action_data, parent=None):
@@ -42,10 +41,9 @@ def __init__(self, action_data, parent=None):
assert isinstance(action_data, RunProcess)
def _create_ui(self):
-
-
-
- self.process_widget = gremlin.ui.ui_common.QPathLineItem("Process:",self.action_data.process, self.action_data)
+ self.process_widget = gremlin.ui.ui_common.QPathLineItem(
+ "Process:", self.action_data.process, self.action_data
+ )
self.process_widget.pathChanged.connect(self._process_changed_cb)
self.process_widget.open.connect(self._process_open_cb)
self.process_widget.installEventFilter(self)
@@ -55,45 +53,48 @@ def _create_ui(self):
self.args_widget.textChanged.connect(self._args_changed_cb)
self.args_widget.installEventFilter(self)
-
-
self.run_widget = QtWidgets.QPushButton("Test")
- self.run_widget.setIcon(gremlin.util.load_icon("ei.play",qta_color = gremlin.ui.ui_common.Color.activeColor()))
+ self.run_widget.setIcon(
+ gremlin.util.load_icon(
+ "ei.play", qta_color=gremlin.ui.ui_common.Color.activeColor()
+ )
+ )
self.run_widget.setToolTip("Runs the process")
self.run_widget.clicked.connect(self._run_process)
self.chkb_exec_on_release = QtWidgets.QCheckBox("Exec on release")
self.chkb_exec_on_release.setChecked(self.action_data.exec_on_release)
- self.chkb_exec_on_release.setToolTip("Execute the command on input release instead of input press")
+ self.chkb_exec_on_release.setToolTip(
+ "Execute the command on input release instead of input press"
+ )
self.chkb_exec_on_release.clicked.connect(self._exec_on_release_changed)
-
self.options_container_widget = QtWidgets.QWidget()
- self.options_container_widget.setContentsMargins(0,0,0,0)
- self.options_container_layout = QtWidgets.QHBoxLayout(self.options_container_widget)
- self.options_container_layout.setContentsMargins(0,0,0,0)
+ self.options_container_widget.setContentsMargins(0, 0, 0, 0)
+ self.options_container_layout = QtWidgets.QHBoxLayout(
+ self.options_container_widget
+ )
+ self.options_container_layout.setContentsMargins(0, 0, 0, 0)
-
self.args_by_line_widget = QtWidgets.QCheckBox("Argument per line")
self.args_by_line_widget.setChecked(self.action_data.args_per_line)
- self.args_by_line_widget.setToolTip("When enabled, each line in the argument list will be passed as a separate argument to the process")
+ self.args_by_line_widget.setToolTip(
+ "When enabled, each line in the argument list will be passed as a separate argument to the process"
+ )
self.args_by_line_widget.clicked.connect(self._args_per_line_changed)
self.options_container_layout.addWidget(QtWidgets.QLabel("Arguments:"))
self.options_container_layout.addStretch()
self.options_container_layout.addWidget(self.args_by_line_widget)
-
self.container_widget = QtWidgets.QWidget()
self.container_layout = QtWidgets.QHBoxLayout(self.container_widget)
-
self.container_layout.addWidget(self.run_widget)
self.container_layout.addWidget(self.chkb_exec_on_release)
self.container_layout.addStretch()
-
self.main_layout.addWidget(self.process_widget)
self.main_layout.addWidget(self.options_container_widget)
self.main_layout.addWidget(self.args_widget)
@@ -102,7 +103,7 @@ def _create_ui(self):
self._update_ui()
def _process_open_cb(self, widget):
- ''' opens the process executable '''
+ """opens the process executable"""
fname = widget.data.process
self.executable_dialog = gremlin.ui.dialogs.ProcessWindow(fname)
self.executable_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
@@ -123,7 +124,7 @@ def _select_executable(self, fname):
w.setText(fname)
self.action_data.process = fname
self._update_ui()
-
+
@QtCore.Slot(bool)
def _args_per_line_changed(self, checked):
self.action_data.args_per_line = checked
@@ -144,12 +145,12 @@ def eventFilter(self, widget, event):
elif widget == self.process_widget:
self._update_ui()
return False
-
+
def _update_ui(self):
text = self.process_widget.text()
- if "\"" or "\\" in text:
+ if '"' or "\\" in text:
# convert windows format to python format
- text = text.replace("\"","").replace("\\","/")
+ text = text.replace('"', "").replace("\\", "/")
with QtCore.QSignalBlocker(self.process_widget):
self.process_widget.setText(text)
self.action_data.process = text
@@ -160,27 +161,20 @@ def _update_ui(self):
def _args_changed_cb(self):
self.action_data.arguments = self.args_widget.toPlainText()
-
def _populate_ui(self):
pass
-
-
@QtCore.Slot()
def _run_process(self):
self.action_data.execute()
class RunProcessFunctor(gremlin.base_profile.AbstractFunctor):
-
-
-
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
self.action_data = action
-
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
execute = False
if self.action_data.exec_on_release and not event.is_pressed:
execute = True
@@ -189,13 +183,10 @@ def process_event(self, event, value, extra_data = None):
if execute:
self.action_data.execute()
-
return True
-
class RunProcess(gremlin.base_profile.AbstractAction):
-
"""Action representing a single TTS entry."""
name = "Run Process"
@@ -216,33 +207,28 @@ class RunProcess(gremlin.base_profile.AbstractAction):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
- self.process = "" # process to run
- self.arguments = "" # args to send
- self.args_per_line = True # one arg per line
- self.exec_on_release = False # exec on release vs press
-
+ self.process = "" # process to run
+ self.arguments = "" # args to send
+ self.args_per_line = True # one arg per line
+ self.exec_on_release = False # exec on release vs press
def display_name(self):
- ''' returns a display string for the current configuration '''
- return f"Run Process: [{self.process}] Args: [{self.arguments}]"
+ """returns a display string for the current configuration"""
+ return f"Run Process: [{self.process}] Args: [{self.arguments}]"
def icon(self):
return "fa6s.bolt"
def requires_virtual_button(self):
- return self.get_input_type() in [
- InputType.JoystickAxis,
- InputType.JoystickHat
- ]
+ return self.get_input_type() in [InputType.JoystickAxis, InputType.JoystickHat]
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
if "process" in node.attrib:
self.process = node.get("process")
if "args" in node.attrib:
self.arguments = node.get("args")
- self.args_per_line = safe_read(node,"line_per_arg",bool, True)
- self.exec_on_release = safe_read(node,"exec_on_release",bool, False)
-
+ self.args_per_line = safe_read(node, "line_per_arg", bool, True)
+ self.exec_on_release = safe_read(node, "exec_on_release", bool, False)
def _generate_xml(self):
node = ElementTree.Element(self.tag)
@@ -254,9 +240,9 @@ def _generate_xml(self):
def _is_valid(self):
return len(self.process) and os.path.isfile(self.process)
-
+
def execute(self):
- ''' executes the process '''
+ """executes the process"""
try:
if self.args_per_line:
args = self.arguments.splitlines()
@@ -264,7 +250,9 @@ def execute(self):
args = self.arguments
cmd_list = [self.process]
cmd_list.extend(arg for arg in args)
- process = subprocess.Popen(cmd_list,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ process = subprocess.Popen(
+ cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
out, err = process.communicate()
syslog.info(f"PROC: execute process: {self.process} {args}")
if out:
diff --git a/action_plugins/split_axis/__init__.py b/action_plugins/split_axis/__init__.py
index c197e24e..c75e0893 100644
--- a/action_plugins/split_axis/__init__.py
+++ b/action_plugins/split_axis/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -31,8 +31,8 @@
syslog = logging.getLogger("system")
-class SplitAxisWidget(gremlin.ui.input_item.AbstractActionWidget):
+class SplitAxisWidget(gremlin.ui.input_item.AbstractActionWidget):
def __init__(self, action_data, parent=None):
"""Creates a new RemapWidget.
@@ -62,12 +62,10 @@ def _create_ui(self):
# Device selection
self.split_device_layout = QtWidgets.QHBoxLayout()
self.vjoy_selector_1 = gremlin.ui.ui_common.VJoySelector(
- self._create_vjoy_selector_callback(1),
- [InputType.JoystickAxis]
+ self._create_vjoy_selector_callback(1), [InputType.JoystickAxis]
)
self.vjoy_selector_2 = gremlin.ui.ui_common.VJoySelector(
- self._create_vjoy_selector_callback(2),
- [InputType.JoystickAxis]
+ self._create_vjoy_selector_callback(2), [InputType.JoystickAxis]
)
self.split_device_layout.addWidget(self.vjoy_selector_1)
self.split_device_layout.addWidget(self.vjoy_selector_2)
@@ -80,31 +78,27 @@ def _populate_ui(self):
self.split_slider.setValue(self.action_data.center_point * 1e5)
self.split_readout.setValue(self.action_data.center_point)
try:
- if self.action_data.device_low_vjoy_id is None or \
- self.action_data.device_low_axis is None:
- self.vjoy_selector_1.set_selection(
- InputType.JoystickAxis,
- -1,
- -1
- )
+ if (
+ self.action_data.device_low_vjoy_id is None
+ or self.action_data.device_low_axis is None
+ ):
+ self.vjoy_selector_1.set_selection(InputType.JoystickAxis, -1, -1)
else:
self.vjoy_selector_1.set_selection(
InputType.JoystickAxis,
self.action_data.device_low_vjoy_id,
- self.action_data.device_low_axis
- )
- if self.action_data.device_high_vjoy_id is None or \
- self.action_data.device_high_axis is None:
- self.vjoy_selector_2.set_selection(
- InputType.JoystickAxis,
- -1,
- -1
+ self.action_data.device_low_axis,
)
+ if (
+ self.action_data.device_high_vjoy_id is None
+ or self.action_data.device_high_axis is None
+ ):
+ self.vjoy_selector_2.set_selection(InputType.JoystickAxis, -1, -1)
else:
self.vjoy_selector_2.set_selection(
InputType.JoystickAxis,
self.action_data.device_high_vjoy_id,
- self.action_data.device_high_axis
+ self.action_data.device_high_axis,
)
self.save_vjoy_selection(1, self.vjoy_selector_1.get_selection())
@@ -114,8 +108,8 @@ def _populate_ui(self):
# vJoy selector attempting to acquire a vJoy device, this
# should no longer occur, check if this here is still needed
util.display_error(
- f"A needed vJoy device is not accessible: {e}\n\n" +
- "Default values have been set for the input, but they are "
+ f"A needed vJoy device is not accessible: {e}\n\n"
+ + "Default values have been set for the input, but they are "
"not what has been specified."
)
syslog.error(str(e))
@@ -166,20 +160,20 @@ def _create_vjoy_selector_callback(self, axis_id):
class SplitAxisFunctor(gremlin.base_profile.AbstractFunctor):
-
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
self.action = action
self.vjoy = gremlin.joystick_handling.VJoyProxy()
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
if value.current < self.action.center_point:
value_range = -1.0 - self.action.center_point
self.vjoy[self.action.device_low_vjoy_id].axis(
self.action.device_low_axis
- ).value = ((value.current - self.action.center_point) /
- value_range) * 2.0 - 1.0
+ ).value = (
+ (value.current - self.action.center_point) / value_range
+ ) * 2.0 - 1.0
self.vjoy[self.action.device_high_vjoy_id].axis(
self.action.device_high_axis
).value = -1.0
@@ -189,8 +183,9 @@ def process_event(self, event, value, extra_data = None):
self.vjoy[self.action.device_high_vjoy_id].axis(
self.action.device_high_axis
- ).value = ((value.current - self.action.center_point) /
- value_range) * 2.0 - 1.0
+ ).value = (
+ (value.current - self.action.center_point) / value_range
+ ) * 2.0 - 1.0
self.vjoy[self.action.device_low_vjoy_id].axis(
self.action.device_low_axis
).value = -1.0
@@ -199,15 +194,12 @@ def process_event(self, event, value, extra_data = None):
class SplitAxis(gremlin.base_profile.AbstractAction):
-
name = "Split Axis"
tag = "split-axis"
default_button_activation = (True, True)
# override default allowed inputs here
- input_types = [
- InputType.JoystickAxis
- ]
+ input_types = [InputType.JoystickAxis]
functor = SplitAxisFunctor
widget = SplitAxisWidget
@@ -227,7 +219,7 @@ def icon(self):
def requires_virtual_button(self):
return False
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
self.center_point = float(node.get("center-point"))
self.device_low_vjoy_id = safe_read(node, "device-low-vjoy-id", int)
self.device_high_vjoy_id = safe_read(node, "device-high-vjoy-id", int)
diff --git a/action_plugins/switch_mode/__init__.py b/action_plugins/switch_mode/__init__.py
index 0a07bf66..a7646c2b 100644
--- a/action_plugins/switch_mode/__init__.py
+++ b/action_plugins/switch_mode/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,8 +16,7 @@
# along with this program. If not, see .
-import os
-from PySide6 import QtWidgets, QtCore
+from PySide6 import QtCore
from lxml import etree as ElementTree
import gremlin.base_profile
@@ -38,7 +37,6 @@
class SwitchModeWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""Widget which allows the configuration of a mode to switch to."""
def __init__(self, action_data, parent=None):
@@ -47,7 +45,9 @@ def __init__(self, action_data, parent=None):
def _create_ui(self):
self.mode_selector_widget = gremlin.ui.ui_common.QComboBox()
- self.mode_selector_widget.currentIndexChanged.connect(self._mode_selected_changed)
+ self.mode_selector_widget.currentIndexChanged.connect(
+ self._mode_selected_changed
+ )
self.main_layout.addWidget(self.mode_selector_widget)
self.ec = gremlin.execution_graph.ExecutionContext()
el = gremlin.event_handler.EventListener()
@@ -55,35 +55,35 @@ def _create_ui(self):
el.execution_context_changed.connect(self._update_modes)
self._update_modes()
-
-
@QtCore.Slot()
def _update_modes(self):
- ''' called when mode list needs to be updated '''
- # update the list of available modes
+ """called when mode list needs to be updated"""
+ # update the list of available modes
with QtCore.QSignalBlocker(self.mode_selector_widget):
- current_mode = self.action_data.mode # current mode
+ current_mode = self.action_data.mode # current mode
self.mode_selector_widget.clear()
-
# remove the current mode so we cannot switch to ourselves
-
- modes = self.ec.getModeNames(as_tuple=True, include_current = False)
+
+ modes = self.ec.getModeNames(as_tuple=True, include_current=False)
if not modes:
# allow to select self if that's the only option
modes = self.ec.getModeNames(as_tuple=True)
-
+
index = 0
select_index = None
for mode, display in modes:
- print (f"Mode: {display} -> {mode}")
+ print(f"Mode: {display} -> {mode}")
self.mode_selector_widget.addItem(display, mode)
- if select_index is None and mode == current_mode and current_mode is not None:
+ if (
+ select_index is None
+ and mode == current_mode
+ and current_mode is not None
+ ):
select_index = index
index += 1
if select_index is not None:
self.mode_selector_widget.setCurrentIndex(select_index)
-
def _mode_selected_changed(self):
mode = self.mode_selector_widget.currentData()
@@ -100,34 +100,31 @@ def _populate_ui(self):
index = 0
self.mode_selector_widget.setCurrentIndex(index)
-
-
class SwitchModeFunctor(gremlin.base_profile.AbstractFunctor):
-
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
self.action_data = action
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
import gremlin.control_action
- import gremlin.config
- import logging
-
+ import gremlin.config
+
if event.is_pressed or value.current:
- verbose = gremlin.config.Configuration().verbose
+ verbose = gremlin.config.Configuration().verbose
mode = self.action_data.mode
- current_mode = gremlin.shared_state.runtime_mode
+ current_mode = gremlin.shared_state.runtime_mode
if mode and current_mode and mode != current_mode:
- if verbose: syslog.info(f"ACTION SWITCH: mode switch from [{current_mode}] to [{mode}] requested")
+ if verbose:
+ syslog.info(
+ f"ACTION SWITCH: mode switch from [{current_mode}] to [{mode}] requested"
+ )
gremlin.control_action.switch_mode(mode)
return True
-
class SwitchMode(gremlin.base_profile.AbstractAction):
-
"""Action representing the change of mode."""
name = "Switch Mode"
@@ -156,7 +153,7 @@ def __init__(self, parent):
@property
def mode(self) -> str:
return self._mode
-
+
@mode.setter
def mode(self, value: str):
if value != self._mode:
@@ -165,31 +162,33 @@ def mode(self, value: str):
verbose = gremlin.config.Configuration().verbose
if verbose:
input_item = self.get_input_item()
- syslog.info(f"SWITCHMODE: mode set to: {value} input: {str(input_item)}")
+ syslog.info(
+ f"SWITCHMODE: mode set to: {value} input: {str(input_item)}"
+ )
def display_name(self):
- ''' returns a display string for the current configuration '''
+ """returns a display string for the current configuration"""
return f"Switch to: {self.mode}"
def icon(self):
return "ei.fork"
- #return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
-
+ # return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
+
@property
def priority(self):
# priority relative to other actions in this sequence - 0 is the default for all actions unless specified - higher numbers run last
return 999
def requires_virtual_button(self):
- return self.get_input_type() in [
- InputType.JoystickAxis,
- InputType.JoystickHat
- ]
+ return self.get_input_type() in [InputType.JoystickAxis, InputType.JoystickHat]
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
self._mode = node.get("name")
verbose = gremlin.config.Configuration().verbose_mode_outputs
- if verbose: syslog.info(f"Read mode: {self._mode} from XML - edit mode: {gremlin.shared_state.edit_mode}")
+ if verbose:
+ syslog.info(
+ f"Read mode: {self._mode} from XML - edit mode: {gremlin.shared_state.edit_mode}"
+ )
def _generate_xml(self):
node = ElementTree.Element("switch-mode")
@@ -198,7 +197,6 @@ def _generate_xml(self):
def _is_valid(self):
return True
-
version = 1
diff --git a/action_plugins/temporary_mode_switch/__init__.py b/action_plugins/temporary_mode_switch/__init__.py
index e384d0de..099747fb 100644
--- a/action_plugins/temporary_mode_switch/__init__.py
+++ b/action_plugins/temporary_mode_switch/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,13 +16,11 @@
# along with this program. If not, see .
-import os
-from PySide6 import QtWidgets, QtCore
+from PySide6 import QtCore
from lxml import etree as ElementTree
import gremlin.base_profile
import gremlin.config
-import gremlin.config
import gremlin.event_handler
from gremlin.input_types import InputType
import gremlin.profile
@@ -38,7 +36,6 @@
class TemporaryModeSwitchWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""Widget which allows the configuration of a mode to switch to."""
def __init__(self, action_data, parent=None):
@@ -52,23 +49,21 @@ def _create_ui(self):
self._update_modes()
el = gremlin.event_handler.EventListener()
- el.edit_mode_changed.connect(self._update_modes)
+ el.edit_mode_changed.connect(self._update_modes)
el.profile_modes_changed.connect(self._update_modes)
def _update_modes(self):
-
mode = self.action_data.mode_name
index = 0
select_index = None
# remove the current mode so we cannot switch to ourselves
ec = gremlin.execution_graph.ExecutionContext()
- modes = ec.getModeNames(as_tuple=True, include_current = False)
+ modes = ec.getModeNames(as_tuple=True, include_current=False)
if not modes:
# allow to select self if that's the only option
modes = ec.getModeNames(as_tuple=True)
-
with QtCore.QSignalBlocker(self.mode_selector_widget):
self.mode_selector_widget.clear()
for entry, display in modes:
@@ -82,7 +77,6 @@ def _update_modes(self):
elif self.mode_selector_widget.count():
self.mode_selector_widget.setCurrentIndex(0)
-
def _mode_list_changed_cb(self):
self.action_data.mode_name = self.mode_selector_widget.currentData()
self.action_modified.emit()
@@ -100,45 +94,63 @@ def _populate_ui(self):
class TemporaryModeSwitchFunctor(gremlin.base_profile.AbstractFunctor):
-
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
- self.action_data : TemporaryModeSwitch = action
-
- def process_event(self, event, value, extra_data = None):
+ self.action_data: TemporaryModeSwitch = action
+
+ def process_event(self, event, value, extra_data=None):
import gremlin.control_action
import gremlin.shared_state
+
verbose = gremlin.config.Configuration().verbose
if verbose:
syslog = logging.getLogger("system")
- if verbose:
+ if verbose:
# get attached mode
mode = self.action_data.get_mode()
device_name = self.action_data.get_device_name()
input_id = self.action_data.get_input_id()
input_type = self.action_data.get_input_type()
- syslog.info(f"Temporary mode change event:")
- syslog.info(f"\tAttached device: {device_name} input type: {InputType.to_display_name(input_type)} input: {input_id} mode: {mode}")
- syslog.info(f"\tevent pressed: [{event.is_pressed}] saved restore mode: [{self.action_data.restore_mode}]")
- syslog.info(f"\tcurrent profile mode: {gremlin.shared_state.runtime_mode} mode to set: {self.action_data.mode_name}")
+ syslog.info("Temporary mode change event:")
+ syslog.info(
+ f"\tAttached device: {device_name} input type: {InputType.to_display_name(input_type)} input: {input_id} mode: {mode}"
+ )
+ syslog.info(
+ f"\tevent pressed: [{event.is_pressed}] saved restore mode: [{self.action_data.restore_mode}]"
+ )
+ syslog.info(
+ f"\tcurrent profile mode: {gremlin.shared_state.runtime_mode} mode to set: {self.action_data.mode_name}"
+ )
if event.is_pressed:
next_mode = self.action_data.mode_name
current_mode = gremlin.shared_state.runtime_mode
if next_mode != current_mode:
- if verbose: syslog.info(f"Temporary mode change: saved current mode [{current_mode}] as the restore mode")
+ if verbose:
+ syslog.info(
+ f"Temporary mode change: saved current mode [{current_mode}] as the restore mode"
+ )
self.action_data.restore_mode = current_mode
- if verbose: syslog.info(f"Temporary mode change: change mode to [{next_mode}] (the restore mode is [{current_mode}])")
+ if verbose:
+ syslog.info(
+ f"Temporary mode change: change mode to [{next_mode}] (the restore mode is [{current_mode}])"
+ )
gremlin.event_handler.EventHandler().change_mode(next_mode)
- if verbose: syslog.info(f"Temporary mode change: register callback")
- gremlin.input_devices.ButtonReleaseActions().register_callback(lambda : self._restore_callback(current_mode), event)
+ if verbose:
+ syslog.info("Temporary mode change: register callback")
+ gremlin.input_devices.ButtonReleaseActions().register_callback(
+ lambda: self._restore_callback(current_mode), event
+ )
else:
# nothing to come back to
- if verbose: syslog.info(f"Temporary mode change: [{current_mode}] (no change because current mode is the same as the requested temporary mode)")
+ if verbose:
+ syslog.info(
+ f"Temporary mode change: [{current_mode}] (no change because current mode is the same as the requested temporary mode)"
+ )
self.action_data.restore_mode = None
-
+
return True
-
+
def _restore_callback(self, mode):
verbose = gremlin.config.Configuration().verbose
if verbose:
@@ -146,8 +158,8 @@ def _restore_callback(self, mode):
syslog.info(f"Temporary mode change: callback: restoring mode {mode}")
gremlin.event_handler.EventHandler().change_mode(mode)
-class TemporaryModeSwitch(gremlin.base_profile.AbstractAction):
+class TemporaryModeSwitch(gremlin.base_profile.AbstractAction):
"""Action representing the change of mode."""
name = "Temporary Mode Switch"
@@ -180,7 +192,7 @@ def __init__(self, parent):
mode = node.parent.name
else:
mode = current_mode
-
+
self.mode_name = mode
self.parent = parent
self.restore_mode = None
@@ -188,23 +200,20 @@ def __init__(self, parent):
@property
def priority(self):
# priority relative to other actions in this sequence - 0 is the default for all actions unless specified - the highest number runs last
- return 999
+ return 999
def display_name(self):
- ''' returns a display string for the current configuration '''
+ """returns a display string for the current configuration"""
return f"Switch to: {self.mode_name}"
def icon(self):
return "ei.fork"
- #return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
+ # return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
def requires_virtual_button(self):
- return self.get_input_type() in [
- InputType.JoystickAxis,
- InputType.JoystickHat
- ]
+ return self.get_input_type() in [InputType.JoystickAxis, InputType.JoystickHat]
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
self.mode_name = node.get("name")
def _generate_xml(self):
diff --git a/action_plugins/toggle_pause/__init__.py b/action_plugins/toggle_pause/__init__.py
index d0b9e45e..831289cf 100644
--- a/action_plugins/toggle_pause/__init__.py
+++ b/action_plugins/toggle_pause/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
-import os
from PySide6 import QtWidgets
from lxml import etree as ElementTree
@@ -26,7 +24,6 @@
class TogglePauseActionWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""Widget for the resume action."""
def __init__(self, action_data, parent=None):
@@ -42,18 +39,17 @@ def _populate_ui(self):
class TogglePauseActionFunctor(gremlin.base_profile.AbstractFunctor):
-
- def __init__(self, action, parent = None):
+ def __init__(self, action, parent=None):
super().__init__(action, parent)
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
import gremlin.control_action
+
gremlin.control_action.toggle_pause_resume()
return True
class TogglePauseAction(gremlin.base_profile.AbstractAction):
-
"""Action to resume callback execution."""
name = "Toggle Pause & Resume"
@@ -76,20 +72,17 @@ def __init__(self, parent):
self.parent = parent
def display_name(self):
- ''' returns a display string for the current configuration '''
+ """returns a display string for the current configuration"""
return "Toggle Pause"
-
+
def icon(self):
return "fa5.pause-circle"
- #return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
+ # return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
def requires_virtual_button(self):
- return self.get_input_type() in [
- InputType.JoystickAxis,
- InputType.JoystickHat
- ]
+ return self.get_input_type() in [InputType.JoystickAxis, InputType.JoystickHat]
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
pass
def _generate_xml(self):
diff --git a/container_plugins/__init__.py b/container_plugins/__init__.py
index f5cb3e71..d0c3a1ff 100644
--- a/container_plugins/__init__.py
+++ b/container_plugins/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/container_plugins/basic/__init__.py b/container_plugins/basic/__init__.py
index 85e26331..db34123b 100644
--- a/container_plugins/basic/__init__.py
+++ b/container_plugins/basic/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -29,8 +29,8 @@
from gremlin.ui.input_item import AbstractContainerWidget
from gremlin.base_conditions import AbstractFunctor
-class BasicContainerWidget(AbstractContainerWidget):
+class BasicContainerWidget(AbstractContainerWidget):
"""Basic container which holds a single action."""
def __init__(self, profile_data, parent=None):
@@ -50,7 +50,7 @@ def _create_action_ui(self):
widget = self._create_action_set_widget(
self.profile_data.action_sets[0],
"Basic",
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
self.action_layout.addWidget(widget)
widget.redraw()
@@ -76,7 +76,7 @@ def _create_condition_ui(self):
widget = self._create_action_set_widget(
self.profile_data.action_sets[0],
"Basic",
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
self.activation_condition_layout.addWidget(widget)
widget.redraw()
@@ -90,6 +90,7 @@ def _add_action(self, action_data):
:param action_name the name of the action to add
"""
from gremlin.clipboard import Clipboard
+
if action_data is None:
return
if isinstance(action_data, str):
@@ -101,13 +102,15 @@ def _add_action(self, action_data):
if action_data.is_action:
# verify the action in the clipboard is appropriate for this input
- action_item = plugin_manager.duplicate(action_data.data, self.profile_data)
+ action_item = plugin_manager.duplicate(
+ action_data.data, self.profile_data
+ )
self.profile_data.add_action(action_item)
self.container_modified.emit()
def _paste_action(self, action, container):
- ''' paste action'''
+ """paste action"""
plugin_manager = gremlin.plugin_manager.ActionPlugins()
action_item = plugin_manager.duplicate(action, self.profile_data)
self.profile_data.add_action(action_item)
@@ -133,17 +136,15 @@ def _get_window_title(self):
class BasicContainerFunctor(AbstractFunctor):
-
"""Executes the contents of the associated basic container."""
- def __init__(self, container, parent = None):
+ def __init__(self, container, parent=None):
super().__init__(container, parent)
self.action_set = gremlin.execution_graph.ActionSetExecutionGraph(
- container.action_sets[0],
- parent
+ container.action_sets[0], parent
)
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
"""Executes the content with the provided data.
:param event the event to process
@@ -154,7 +155,6 @@ def process_event(self, event, value, extra_data = None):
class BasicContainer(AbstractContainer):
-
"""Represents a container which holds exactly one action."""
name = "Basic"
@@ -166,19 +166,19 @@ class BasicContainer(AbstractContainer):
# InputType.JoystickHat,
# InputType.Keyboard
# ]
-
+
interaction_types = []
functor = BasicContainerFunctor
widget = BasicContainerWidget
- def __init__(self, parent=None, node = None):
+ def __init__(self, parent=None, node=None):
"""Creates a new instance.
:param parent the InputItem this container is linked to
"""
super().__init__(parent, node)
-
+
def add_action(self, action, index=-1):
assert isinstance(action, gremlin.base_profile.AbstractAction)
@@ -190,16 +190,18 @@ def add_action(self, action, index=-1):
for container in self.parent.containers:
for action_set in container.action_sets:
for t_action in action_set:
- if gremlin.base_profile._is_curve_tag(t_action.tag):
+ if gremlin.base_profile._is_curve_tag(t_action.tag):
curve_sets.append(action_set)
elif t_action.tag == "remap":
remap_sets.append(action_set)
- if action.tag == "remap" and len(curve_sets) == 1 and \
- len(remap_sets) == 0:
+ if action.tag == "remap" and len(curve_sets) == 1 and len(remap_sets) == 0:
curve_sets[0].append(action)
- elif gremlin.base_profile._is_curve_tag(action.tag) and len(remap_sets) == 1 and \
- len(curve_sets) == 0:
+ elif (
+ gremlin.base_profile._is_curve_tag(action.tag)
+ and len(remap_sets) == 1
+ and len(curve_sets) == 0
+ ):
remap_sets[0].append(action)
else:
if index == -1:
@@ -212,11 +214,11 @@ def add_action(self, action, index=-1):
index = len(self.action_sets) - 1
self.action_sets[index].append(action)
- #self.refresh_conditions()
+ # self.refresh_conditions()
self.create_or_delete_virtual_button()
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Populates the container with the XML node's contents.
:param node the XML node with which to populate the container
diff --git a/container_plugins/button/__init__.py b/container_plugins/button/__init__.py
index 06336a87..42463148 100644
--- a/container_plugins/button/__init__.py
+++ b/container_plugins/button/__init__.py
@@ -17,12 +17,8 @@
#
# this code is build on Gremlin work by Lionel Ott
-import copy
-import logging
-import threading
-import time
from lxml import etree as ElementTree
-
+import logging
from PySide6 import QtWidgets
import gremlin
@@ -32,14 +28,15 @@
from gremlin.base_profile import AbstractContainer
from gremlin.input_types import InputType
-class ButtonContainerWidget(AbstractContainerWidget):
+syslog = logging.getLogger("system")
+class ButtonContainerWidget(AbstractContainerWidget):
"""Container with two actions, one for input button is pressed, the other for when the input button is released
-
- While this can be duplicated with conditions - this is a helper container to simplify the profile setup.
- Works with buttons or hats
-
+ While this can be duplicated with conditions - this is a helper container to simplify the profile setup.
+
+ Works with buttons or hats
+
"""
def __init__(self, profile_data, parent=None):
@@ -69,7 +66,7 @@ def _create_action_ui(self):
0,
"Button Press",
self.action_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
if self.profile_data.action_sets[1] is None:
@@ -83,7 +80,7 @@ def _create_action_ui(self):
1,
"Button Release",
self.action_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
def _create_condition_ui(self):
@@ -93,7 +90,7 @@ def _create_condition_ui(self):
0,
"Button Press",
self.activation_condition_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
if self.profile_data.action_sets[1] is not None:
@@ -101,7 +98,7 @@ def _create_condition_ui(self):
1,
"Button Release",
self.activation_condition_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
def _add_action_selector(self, add_action_cb, label, paste_action_cb):
@@ -132,9 +129,7 @@ def _create_action_widget(self, index, label, layout, view_type):
:param label the name of the action to create
"""
widget = self._create_action_set_widget(
- self.profile_data.action_sets[index],
- label,
- view_type
+ self.profile_data.action_sets[index], label, view_type
)
layout.addWidget(widget)
widget.redraw()
@@ -154,7 +149,7 @@ def _add_action(self, index, action_name):
self.container_modified.emit()
def _paste_action(self, index, action):
- ''' paste action'''
+ """paste action"""
plugin_manager = gremlin.plugin_manager.ActionPlugins()
action_item = plugin_manager.duplicate(action, self.profile_data)
if self.profile_data.action_sets[index] is None:
@@ -162,8 +157,6 @@ def _paste_action(self, index, action):
self.profile_data.action_sets[index].append(action_item)
self.profile_data.create_or_delete_virtual_button()
-
-
def _handle_interaction(self, widget, action):
"""Handles interaction icons being pressed on the individual actions.
@@ -189,8 +182,7 @@ def _get_window_title(self):
class ButtonContainerFunctor(gremlin.base_conditions.AbstractFunctor):
-
- def __init__(self, container, parent = None):
+ def __init__(self, container, parent=None):
super().__init__(container, parent)
self.press_set = gremlin.execution_graph.ActionSetExecutionGraph(
container.action_sets[0], parent
@@ -199,11 +191,10 @@ def __init__(self, container, parent = None):
container.action_sets[1], parent
)
- def process_event(self, event, value, extra_data = None):
-
+ def process_event(self, event, value, extra_data=None):
if event.event_type == InputType.JoystickHat:
is_hat = True
- is_pressed = value.current != (0,0)
+ is_pressed = value.current != (0, 0)
elif not isinstance(value.current, bool):
syslog.warning(
f"Invalid data type received in Button container: {type(event.value)}"
@@ -218,14 +209,13 @@ def process_event(self, event, value, extra_data = None):
self.press_set.process_event(event, value)
else:
# button release
- value.current = (0,0) if is_hat else True
+ value.current = (0, 0) if is_hat else True
self.release_set.process_event(event, value)
return True
class ButtonContainer(AbstractContainer):
-
"""A container with two actions which are triggered based on the duration
of the activation.
@@ -247,7 +237,7 @@ class ButtonContainer(AbstractContainer):
gremlin.ui.input_item.ActionSetView.Interactions.Edit,
]
- def __init__(self, parent=None, node = None):
+ def __init__(self, parent=None, node=None):
"""Creates a new instance.
:param parent the InputItem this container is linked to
@@ -257,7 +247,7 @@ def __init__(self, parent=None, node = None):
self.delay = 0.5
self.activate_on = "release"
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Populates the container with the XML node's contents.
:param node the XML node with which to populate the container
diff --git a/container_plugins/chain/__init__.py b/container_plugins/chain/__init__.py
index 22d79ccd..492ccdbb 100644
--- a/container_plugins/chain/__init__.py
+++ b/container_plugins/chain/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -26,7 +26,7 @@
import gremlin.config
import gremlin.ui.ui_common
import gremlin.ui.input_item
-from gremlin.ui.input_item import AbstractContainerWidget, AbstractActionWidget
+from gremlin.ui.input_item import AbstractContainerWidget
from gremlin.base_profile import AbstractContainer
from gremlin.input_types import InputType
@@ -34,7 +34,6 @@
class ChainContainerWidget(AbstractContainerWidget):
-
"""Container which holds a sequence of actions."""
def __init__(self, profile_data, parent=None):
@@ -57,7 +56,6 @@ def _create_action_ui(self):
self.action_selector.action_added.connect(self._add_action)
self.action_selector.add_button.setText("Add Step")
self.action_selector.action_paste.connect(self._paste_action)
-
self.widget_layout.addWidget(self.action_selector)
@@ -79,7 +77,7 @@ def _create_action_ui(self):
widget = self._create_action_set_widget(
self.profile_data.action_sets[i],
f"Action {i:d}",
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
self.action_layout.addWidget(widget)
widget.redraw()
@@ -91,7 +89,7 @@ def _create_condition_ui(self):
widget = self._create_action_set_widget(
self.profile_data.action_sets[i],
f"Action {i:d}",
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
self.activation_condition_layout.addWidget(widget)
widget.redraw()
@@ -108,7 +106,7 @@ def _add_action(self, action_name):
self.container_modified.emit()
def _paste_action(self, action, container):
- ''' pastes an action '''
+ """pastes an action"""
plugin_manager = gremlin.plugin_manager.ActionPlugins()
action_item = plugin_manager.duplicate(action, self.profile_data)
self.profile_data.add_action(action_item)
@@ -140,16 +138,22 @@ def _handle_interaction(self, widget, action):
# Perform action
if action == gremlin.ui.input_item.ActionSetView.Interactions.Up:
if index > 0:
- self.profile_data.action_sets[index],\
- self.profile_data.action_sets[index-1] = \
- self.profile_data.action_sets[index-1],\
- self.profile_data.action_sets[index]
+ (
+ self.profile_data.action_sets[index],
+ self.profile_data.action_sets[index - 1],
+ ) = (
+ self.profile_data.action_sets[index - 1],
+ self.profile_data.action_sets[index],
+ )
if action == gremlin.ui.input_item.ActionSetView.Interactions.Down:
if index < len(self.profile_data.action_sets) - 1:
- self.profile_data.action_sets[index], \
- self.profile_data.action_sets[index + 1] = \
- self.profile_data.action_sets[index + 1], \
- self.profile_data.action_sets[index]
+ (
+ self.profile_data.action_sets[index],
+ self.profile_data.action_sets[index + 1],
+ ) = (
+ self.profile_data.action_sets[index + 1],
+ self.profile_data.action_sets[index],
+ )
if action == gremlin.ui.input_item.ActionSetView.Interactions.Delete:
del self.profile_data.action_sets[index]
@@ -164,8 +168,7 @@ def _get_window_title(self):
class ChainContainerFunctor(gremlin.base_conditions.AbstractFunctor):
-
- def __init__(self, container, parent = None):
+ def __init__(self, container, parent=None):
super().__init__(container, parent)
self.action_sets = []
for action_set in container.action_sets:
@@ -187,9 +190,9 @@ def __init__(self, container, parent = None):
if cond.comparison == "press":
self.switch_on_press = True
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
if event.event_type == InputType.JoystickHat:
- is_pressed = value.current != (0,0)
+ is_pressed = value.current != (0, 0)
elif not isinstance(value.current, bool):
syslog.warning(
f"Invalid data type received in Chain container: {type(event.value)}"
@@ -198,7 +201,6 @@ def process_event(self, event, value, extra_data = None):
else:
is_pressed = value.current
-
if self.timeout > 0.0:
if self.last_execution + self.timeout < time.time():
self.index = 0
@@ -215,7 +217,6 @@ def process_event(self, event, value, extra_data = None):
class ChainContainer(AbstractContainer):
-
"""Represents a container which holds multiplier actions.
The actions will trigger one after the other with subsequent activations.
@@ -241,7 +242,7 @@ class ChainContainer(AbstractContainer):
functor = ChainContainerFunctor
widget = ChainContainerWidget
- def __init__(self, parent=None, node = None):
+ def __init__(self, parent=None, node=None):
"""Creates a new instance.
:param parent the InputItem this container is linked to
@@ -249,7 +250,7 @@ def __init__(self, parent=None, node = None):
super().__init__(parent, node)
self.timeout = 0.0
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Populates the container with the XML node's contents.
:param node the XML node with which to populate the container
@@ -276,7 +277,7 @@ def _is_container_valid(self):
:return True if the container is configured properly, False otherwise
"""
- #return len(self.action_sets) > 0
+ # return len(self.action_sets) > 0
return True
diff --git a/container_plugins/double_tap/__init__.py b/container_plugins/double_tap/__init__.py
index 0928fd62..de06dcea 100644
--- a/container_plugins/double_tap/__init__.py
+++ b/container_plugins/double_tap/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -31,11 +31,10 @@
from gremlin.input_types import InputType
-
syslog = logging.getLogger("system")
-class DoubleTapContainerWidget(AbstractContainerWidget):
+class DoubleTapContainerWidget(AbstractContainerWidget):
"""DoubleTap container for actions for double or single taps."""
def __init__(self, profile_data, parent=None):
@@ -53,9 +52,7 @@ def _create_action_ui(self):
self.options_layout = QtWidgets.QHBoxLayout()
# Activation delay
- self.options_layout.addWidget(
- QtWidgets.QLabel("Double-tap delay: ")
- )
+ self.options_layout.addWidget(QtWidgets.QLabel("Double-tap delay: "))
self.delay_input = gremlin.ui.ui_common.DynamicDoubleSpinBox()
self.delay_input.setRange(0.1, 2.0)
self.delay_input.setSingleStep(0.1)
@@ -91,7 +88,7 @@ def _create_action_ui(self):
0,
"Single Tap",
self.action_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
if self.profile_data.action_sets[1] is None:
@@ -105,7 +102,7 @@ def _create_action_ui(self):
1,
"Double Tap",
self.action_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
def _create_condition_ui(self):
@@ -115,7 +112,7 @@ def _create_condition_ui(self):
0,
"Single Tap",
self.activation_condition_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
if self.profile_data.action_sets[1] is not None:
@@ -123,7 +120,7 @@ def _create_condition_ui(self):
1,
"Double Tap",
self.activation_condition_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
def _add_action_selector(self, add_action_cb, label, paste_action_cb):
@@ -154,9 +151,7 @@ def _create_action_widget(self, index, label, layout, view_type):
:param label the name of the action to create
"""
widget = self._create_action_set_widget(
- self.profile_data.action_sets[index],
- label,
- view_type
+ self.profile_data.action_sets[index], label, view_type
)
layout.addWidget(widget)
widget.redraw()
@@ -175,9 +170,8 @@ def _add_action(self, index, action_name):
self.profile_data.create_or_delete_virtual_button()
self.container_modified.emit()
-
def _paste_action(self, index, action):
- ''' pastes an action '''
+ """pastes an action"""
plugin_manager = gremlin.plugin_manager.ActionPlugins()
action_item = plugin_manager.duplicate(action, self.profile_data)
if self.profile_data.action_sets[index] is None:
@@ -186,7 +180,6 @@ def _paste_action(self, index, action):
self.profile_data.create_or_delete_virtual_button()
self.container_modified.emit()
-
def _delay_changed_cb(self, value):
"""Updates the activation delay value.
@@ -229,10 +222,9 @@ def _get_window_title(self):
class DoubleTapContainerFunctor(gremlin.base_conditions.AbstractFunctor):
-
"""Executes the contents of the associated DoubleTap container."""
- def __init__(self, container, parent = None):
+ def __init__(self, container, parent=None):
super().__init__(container, parent)
self.single_tap = gremlin.execution_graph.ActionSetExecutionGraph(
container.action_sets[0], parent
@@ -250,9 +242,9 @@ def __init__(self, container, parent = None):
self.event_press = None
self.processed_single_tap = True
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
if event.event_type == InputType.JoystickHat:
- is_pressed = value.current != (0,0)
+ is_pressed = value.current != (0, 0)
elif not isinstance(value.current, bool):
syslog.warning(
f"Invalid data type received in DoubleTap container: {type(event.value)}"
@@ -262,59 +254,56 @@ def process_event(self, event, value, extra_data = None):
is_pressed = value.current
if self.processed_single_tap:
-
- # Copy state when input is pressed
- if is_pressed:
- self.value_press = copy.deepcopy(value)
- self.event_press = event.clone()
-
- # Execute double tap logic
- if is_pressed:
-
- # Second activation within the delay, i.e. second tap
- if (self.start_time + self.delay) > time.time():
- # Prevent repeated double taps from repeated button presses
- self.start_time = 0
- self.tap_type = "double"
- if self.activate_on == "exclusive":
- self.double_action_timer.cancel()
- # First activation within the delay, i.e. first tap
- else:
- self.start_time = time.time()
- #print ("first activation")
- self.tap_type = "single"
- if self.activate_on == "exclusive":
- self.double_action_timer = \
- threading.Timer(self.delay, self._single_tap)
- self.double_action_timer.start()
-
- # Input is being released at this point
- elif self.double_action_timer and self.double_action_timer.is_alive():
- # if releasing single tap before delay
- # we will want to send a short press and release
- self.double_action_timer.cancel()
- self.double_action_timer = threading.Timer(
- (self.start_time + self.delay) - time.time(),
- lambda: self._single_tap(event, value)
- )
- self.double_action_timer.start()
-
- if self.tap_type == "double":
- #print ("double tap")
- self.double_tap.process_event(event, value)
- if self.activate_on == "combined":
- self.single_tap.process_event(event, value)
- elif self.activate_on != "exclusive":
- #print ("single tap exclusive")
+ # Copy state when input is pressed
+ if is_pressed:
+ self.value_press = copy.deepcopy(value)
+ self.event_press = event.clone()
+
+ # Execute double tap logic
+ if is_pressed:
+ # Second activation within the delay, i.e. second tap
+ if (self.start_time + self.delay) > time.time():
+ # Prevent repeated double taps from repeated button presses
+ self.start_time = 0
+ self.tap_type = "double"
+ if self.activate_on == "exclusive":
+ self.double_action_timer.cancel()
+ # First activation within the delay, i.e. first tap
+ else:
+ self.start_time = time.time()
+ # print ("first activation")
+ self.tap_type = "single"
+ if self.activate_on == "exclusive":
+ self.double_action_timer = threading.Timer(
+ self.delay, self._single_tap
+ )
+ self.double_action_timer.start()
+
+ # Input is being released at this point
+ elif self.double_action_timer and self.double_action_timer.is_alive():
+ # if releasing single tap before delay
+ # we will want to send a short press and release
+ self.double_action_timer.cancel()
+ self.double_action_timer = threading.Timer(
+ (self.start_time + self.delay) - time.time(),
+ lambda: self._single_tap(event, value),
+ )
+ self.double_action_timer.start()
+
+ if self.tap_type == "double":
+ # print ("double tap")
+ self.double_tap.process_event(event, value)
+ if self.activate_on == "combined":
self.single_tap.process_event(event, value)
-
+ elif self.activate_on != "exclusive":
+ # print ("single tap exclusive")
+ self.single_tap.process_event(event, value)
+
else:
- #print ("first tap")
+ # print ("first tap")
self.start_time = time.time()
self.single_tap.process_event(event, value)
self.processed_single_tap = True
-
-
return True
@@ -327,8 +316,8 @@ def _single_tap(self, event_release=None, value_release=None):
self.single_tap.process_event(event_release, value_release)
self.processed_single_tap = True
-class DoubleTapContainer(AbstractContainer):
+class DoubleTapContainer(AbstractContainer):
"""A container with two actions which are triggered based on the delay
between the taps.
@@ -352,7 +341,7 @@ class DoubleTapContainer(AbstractContainer):
gremlin.ui.input_item.ActionSetView.Interactions.Edit,
]
- def __init__(self, parent=None, node = None):
+ def __init__(self, parent=None, node=None):
"""Creates a new instance.
:param parent the InputItem this container is linked to
@@ -362,15 +351,16 @@ def __init__(self, parent=None, node = None):
self.delay = 0.5
self.activate_on = "exclusive"
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Populates the container with the XML node's contents.
:param node the XML node with which to populate the container
"""
super()._parse_xml(node, data)
self.delay = gremlin.profile.safe_read(node, "delay", float, 0.5)
- self.activate_on = \
- gremlin.profile.safe_read(node, "activate-on", str, "combined")
+ self.activate_on = gremlin.profile.safe_read(
+ node, "activate-on", str, "combined"
+ )
def _generate_xml(self):
"""Returns an XML node representing this container's data.
diff --git a/container_plugins/hat_buttons/__init__.py b/container_plugins/hat_buttons/__init__.py
index 4b328cf1..5b9090ed 100644
--- a/container_plugins/hat_buttons/__init__.py
+++ b/container_plugins/hat_buttons/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -31,14 +31,8 @@
from gremlin.base_profile import AbstractContainer
-
# Lookup for direction to index with 4 way hat usage
-_four_lookup = {
- (0, 1): 0,
- (1, 0): 1,
- (0, -1): 2,
- (-1, 0): 3
-}
+_four_lookup = {(0, 1): 0, (1, 0): 1, (0, -1): 2, (-1, 0): 3}
# Lookup for direction to indices with 8 way hat usage
_eight_lookup = {
@@ -49,7 +43,7 @@
(0, -1): 4,
(-1, -1): 5,
(-1, 0): 6,
- (-1, 1): 7
+ (-1, 1): 7,
}
# Names for the indices in a 4 way hat case
@@ -57,13 +51,18 @@
# Names for the indices in a 8 way hat case
_eight_names = [
- "North", "North East", "East", "South East", "South",
- "South West", "West", "North West"
+ "North",
+ "North East",
+ "East",
+ "South East",
+ "South",
+ "South West",
+ "West",
+ "North West",
]
class HatButtonsContainerWidget(AbstractContainerWidget):
-
"""Basic container which holds a single action."""
def __init__(self, profile_data, parent=None):
@@ -101,29 +100,27 @@ def _create_action_ui(self):
for i, direction in enumerate(_four_names):
if self.profile_data.action_sets[i] is None:
self._add_action_selector(
- lambda x: self._add_action(i, x),
- direction
+ lambda x: self._add_action(i, x), direction
)
else:
self._create_action_widget(
i,
direction,
self.action_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
elif self.profile_data.button_count == 8:
for i, direction in enumerate(_eight_names):
if self.profile_data.action_sets[i] is None:
self._add_action_selector(
- lambda x: self._add_action(i, x),
- direction
+ lambda x: self._add_action(i, x), direction
)
else:
self._create_action_widget(
i,
direction,
self.action_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
else:
pass
@@ -146,9 +143,7 @@ def _create_condition_ui(self):
names = _eight_names
for i, action_set in enumerate(self.profile_data.action_sets):
widget = self._create_action_set_widget(
- action_set,
- names[i],
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ action_set, names[i], gremlin.ui.ui_common.ContainerViewTypes.Conditions
)
self.activation_condition_layout.addWidget(widget)
widget.redraw()
@@ -163,9 +158,7 @@ def _create_action_widget(self, index, label, layout, view_type):
:param view_type the visualization type being used
"""
widget = self._create_action_set_widget(
- self.profile_data.action_sets[index],
- label,
- view_type
+ self.profile_data.action_sets[index], label, view_type
)
layout.addWidget(widget)
widget.redraw()
@@ -221,17 +214,16 @@ def _change_button_type(self, state):
class HatButtonsContainerFunctor(gremlin.base_conditions.AbstractFunctor):
-
"""Executes the contents of the associated basic container.
This functor does nothing when called (should never happen) as the
callbacks generated by this container are several basic containers.
"""
- def __init__(self, container, parent = None):
+ def __init__(self, container, parent=None):
super().__init__(container, parent)
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
"""Executes the content with the provided data.
:param event the event to process
@@ -242,7 +234,6 @@ def process_event(self, event, value, extra_data = None):
class HatButtonsContainer(AbstractContainer):
-
"""Represents a container which holds exactly one action."""
name = "Hat Buttons"
@@ -253,7 +244,7 @@ class HatButtonsContainer(AbstractContainer):
input_types = [InputType.JoystickHat]
interaction_types = []
- def __init__(self, parent=None, node = None):
+ def __init__(self, parent=None, node=None):
"""Creates a new instance.
:param parent the InputItem this container is linked to
@@ -285,37 +276,46 @@ def generate_callbacks(self, parent):
continue
# Callback generating virtual button events
- callbacks.append(gremlin.base_profile.CallbackData(
- gremlin.execution_graph.VirtualButtonProcess(VirtualHatButton(self, [gremlin.util.hat_tuple_to_direction(id_to_direction[i])])),None))
+ callbacks.append(
+ gremlin.base_profile.CallbackData(
+ gremlin.execution_graph.VirtualButtonProcess(
+ VirtualHatButton(
+ self,
+ [gremlin.util.hat_tuple_to_direction(id_to_direction[i])],
+ )
+ ),
+ None,
+ )
+ )
# Create fake BasicContainer for each action set
basic_container = BasicContainer(self, parent)
basic_container.action_sets = [action_set]
basic_container.activation_condition = self.activation_condition
-
-
# Callback reacting to virtual button events
- callbacks.append(gremlin.base_profile.CallbackData(
- gremlin.execution_graph.VirtualButtonCallback(basic_container, parent),
- gremlin.event_handler.Event(
- InputType.VirtualButton,
- callbacks[-1].callback.virtual_button.identifier,
- device_guid=dinput.GUID_Virtual,
- is_pressed=True,
- raw_value=True
+ callbacks.append(
+ gremlin.base_profile.CallbackData(
+ gremlin.execution_graph.VirtualButtonCallback(
+ basic_container, parent
+ ),
+ gremlin.event_handler.Event(
+ InputType.VirtualButton,
+ callbacks[-1].callback.virtual_button.identifier,
+ device_guid=dinput.GUID_Virtual,
+ is_pressed=True,
+ raw_value=True,
+ ),
)
- ))
+ )
return callbacks
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Populates the container with the XML node's contents.
:param node the XML node with which to populate the container
"""
- self.button_count = gremlin.profile.safe_read(
- node, "button-count", int, 4
- )
+ self.button_count = gremlin.profile.safe_read(node, "button-count", int, 4)
def _generate_xml(self):
"""Returns an XML node representing this container's data.
diff --git a/container_plugins/range/__init__.py b/container_plugins/range/__init__.py
index 7d7cec69..38373620 100644
--- a/container_plugins/range/__init__.py
+++ b/container_plugins/range/__init__.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8; -*-
-# Based on original concept / code by Lionel Ott - Copyright (C) 2015 - 2019 Lionel Ott
+
+# Based on original concept / code by Lionel Ott - Copyright (C) 2015 - 2019 Lionel Ott
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -15,24 +16,20 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
import logging
+import os
from lxml import etree as ElementTree
+from PySide6 import QtCore, QtWidgets
import gremlin.actions
import gremlin.config
import gremlin.event_handler
-from gremlin.input_types import InputType
import gremlin.joystick_handling
-from gremlin.util import rad2deg, get_guid
-from gremlin.profile import safe_format, safe_read
+from gremlin.input_types import InputType
+from gremlin.util import get_guid, safe_read, safe_format
import gremlin.ui.ui_common
import gremlin.ui.input_item
-import os
from gremlin.ui.input_item import AbstractContainerWidget
from gremlin.base_profile import AbstractContainer
-
-from action_plugins.map_to_keyboard import *
-from action_plugins.map_to_mouse import *
import gremlin.config
@@ -40,7 +37,7 @@
class RangeContainerWidget(AbstractContainerWidget):
- ''' Range container for a ranged action '''
+ """Range container for a ranged action"""
def __init__(self, profile_data, parent=None):
"""Creates a new instance.
@@ -49,12 +46,9 @@ def __init__(self, profile_data, parent=None):
:param parent the parent of this widget
"""
super().__init__(profile_data, parent)
-
-
def _create_action_ui(self):
- ''' creates the UI for the container '''
-
+ """creates the UI for the container"""
# get container parent widget
@@ -63,18 +57,15 @@ def _create_action_ui(self):
toolbar_widget.setLayout(toolbar_container)
toolbar1_widget = QtWidgets.QWidget()
- toolbar1_widget.setContentsMargins(0,0,0,0)
+ toolbar1_widget.setContentsMargins(0, 0, 0, 0)
toolbar1_layout = QtWidgets.QHBoxLayout()
toolbar1_widget.setLayout(toolbar1_layout)
toolbar2_widget = QtWidgets.QWidget()
- toolbar2_widget.setContentsMargins(0,0,0,0)
+ toolbar2_widget.setContentsMargins(0, 0, 0, 0)
toolbar2_layout = QtWidgets.QHBoxLayout()
toolbar2_widget.setLayout(toolbar2_layout)
-
-
-
self.widget_layout = QtWidgets.QVBoxLayout()
# self.profile_data.create_or_delete_virtual_button()
@@ -99,14 +90,14 @@ def _create_action_ui(self):
# holds the mode change data when in trigger by value change mode
mode_widget = QtWidgets.QWidget()
- mode_container = QtWidgets.QHBoxLayout()
+ mode_container = QtWidgets.QHBoxLayout()
mode_widget.setLayout(mode_container)
mode_widget.setToolTip("Sets the mode of the container.")
self.ui_mode_widget = mode_widget
# holds the range data when triggered by range
range_widget = QtWidgets.QWidget()
- range_container = QtWidgets.QHBoxLayout(range_widget) # holds the range data
+ range_container = QtWidgets.QHBoxLayout(range_widget) # holds the range data
self.ui_range_widget = range_widget
# options_widget = QtWidgets.QWidget()
@@ -114,23 +105,31 @@ def _create_action_ui(self):
# options_layout = QtWidgets.QHBoxLayout(options_widget)
# self.ui_options_widget = options_widget
- any_change_mode = QtWidgets.QCheckBox("Any Change") # trigger on any change mode
+ any_change_mode = QtWidgets.QCheckBox(
+ "Any Change"
+ ) # trigger on any change mode
self.ui_any_change_mode = any_change_mode
any_change_mode.setChecked(action_data.any_change_mode)
any_change_mode.clicked.connect(self._any_change_mode_changed)
- any_change_mode.setToolTip("When set, the action will be triggered on any axis value change.")
+ any_change_mode.setToolTip(
+ "When set, the action will be triggered on any axis value change."
+ )
any_change_label = QtWidgets.QLabel("Delta %")
- any_change_delta = gremlin.ui.ui_common.QIntLineEdit(min_range=0,max_range=100) # QtWidgets.QSpinBox()
+ any_change_delta = gremlin.ui.ui_common.QIntLineEdit(
+ min_range=0, max_range=100
+ ) # QtWidgets.QSpinBox()
self.ui_any_change_delta = any_change_delta
- any_change_delta.setRange(0,100)
+ any_change_delta.setRange(0, 100)
any_change_delta.setValue(action_data.any_change_delta)
- any_change_delta.setToolTip("In any change mode, determines how much the axis should deviate from the old value before triggering the action")
-
+ any_change_delta.setToolTip(
+ "In any change mode, determines how much the axis should deviate from the old value before triggering the action"
+ )
+
container_mode_widget = QtWidgets.QWidget()
- container_mode_widget.setContentsMargins(0,0,0,0)
+ container_mode_widget.setContentsMargins(0, 0, 0, 0)
container_mode_layout = QtWidgets.QHBoxLayout(container_mode_widget)
- container_mode_layout.setContentsMargins(0,0,0,0)
+ container_mode_layout.setContentsMargins(0, 0, 0, 0)
rb_change_both = gremlin.ui.ui_common.QDataRadioButton("Both", 0)
rb_change_up = gremlin.ui.ui_common.QDataRadioButton("Up", 1)
rb_change_down = gremlin.ui.ui_common.QDataRadioButton("Down", -1)
@@ -150,18 +149,23 @@ def _create_action_ui(self):
container_mode_layout.addWidget(rb_change_down)
container_mode_layout.addStretch()
-
min_box_included = QtWidgets.QCheckBox("[")
min_box_included.setChecked(action_data.range_min_included)
- min_box_included.setToolTip("Include/Exclude flag: When set, the range includes the specified min value.
When not set, the value is excluded from the max range")
+ min_box_included.setToolTip(
+ "Include/Exclude flag: When set, the range includes the specified min value.
When not set, the value is excluded from the max range"
+ )
max_box_included = QtWidgets.QCheckBox("]")
max_box_included.setChecked(action_data.range_max_included)
- max_box_included.setToolTip("Include/Exclude flag: When set, the range includes the specified max value
When not set, the value is excluded from the max range")
+ max_box_included.setToolTip(
+ "Include/Exclude flag: When set, the range includes the specified max value
When not set, the value is excluded from the max range"
+ )
add_button_top_90 = QtWidgets.QPushButton("Top 90%")
add_button_top_90.clicked.connect(self._add_top_90)
- add_button_top_90.setToolTip("Configures the container for the top 90 percent range. When used with the symmetry option, sets a trigger for bottom 10 percent or top 10 percent of the input range")
+ add_button_top_90.setToolTip(
+ "Configures the container for the top 90 percent range. When used with the symmetry option, sets a trigger for bottom 10 percent or top 10 percent of the input range"
+ )
action_label = QtWidgets.QLabel("Actions")
self.ui_action_dropdown = gremlin.ui.ui_common.QComboBox()
@@ -171,7 +175,9 @@ def _create_action_ui(self):
cfg = gremlin.config.Configuration()
self.ui_action_dropdown.setCurrentText(cfg.last_action)
- self.ui_action_dropdown.setToolTip("Determines the default action added to a new container")
+ self.ui_action_dropdown.setToolTip(
+ "Determines the default action added to a new container"
+ )
self.add_button = QtWidgets.QPushButton("Add")
self.add_button.clicked.connect(self._add_action)
@@ -182,19 +188,20 @@ def _create_action_ui(self):
self.ui_range_count.minimum = 1
self.ui_range_count.maximum = 20
self.ui_range_count.setValue(5)
- self.ui_range_count.setToolTip("Determines how many ranges (brackets) will be added. The range values for each container will be computed based on the number of 'slots' entered here.
A value of 5 means 5 containers will be created with a range of 20 percent each.")
-
+ self.ui_range_count.setToolTip(
+ "Determines how many ranges (brackets) will be added. The range values for each container will be computed based on the number of 'slots' entered here.
A value of 5 means 5 containers will be created with a range of 20 percent each."
+ )
add_range = QtWidgets.QPushButton("Add Ranges")
add_range.clicked.connect(self._add_range)
add_range.setToolTip("Adds the number of requested ranges (these are added)")
-
replace_range = QtWidgets.QPushButton("Replace Ranges")
replace_range.clicked.connect(self._replace_range)
- replace_range.setToolTip("Replaces all containers with a new range. Warning: this will delete any existing actions.")
+ replace_range.setToolTip(
+ "Replaces all containers with a new range. Warning: this will delete any existing actions."
+ )
-
# max range box
max_box = gremlin.ui.ui_common.DynamicDoubleSpinBox()
max_box.setMinimum(-1.0)
@@ -203,24 +210,22 @@ def _create_action_ui(self):
max_box.setValue(action_data.range_max)
max_box.setToolTip("Upper range of the bracket")
-
-
symmetrical_box = QtWidgets.QCheckBox("Symmetrical")
symmetrical_box.setChecked(action_data.symmetrical)
- symmetrical_box.setToolTip("When enabled, the range given will be automatically mirrored about the center of the range, causing an action trigger when the range on either side of the center value is entered.")
+ symmetrical_box.setToolTip(
+ "When enabled, the range given will be automatically mirrored about the center of the range, causing an action trigger when the range on either side of the center value is entered."
+ )
# release_box = QtWidgets.QCheckBox("Autorelease")
# release_box.setChecked(action_data.autorelease)
# release_box.setToolTip("When enabled, a release action will be triggered when the input exits the range")
# options_layout.addWidget(release_box)
-
mode_container.addWidget(any_change_label)
mode_container.addWidget(any_change_delta)
mode_container.addWidget(container_mode_widget)
mode_container.addStretch()
-
range_container.addWidget(QtWidgets.QLabel("Start:"))
range_container.addWidget(min_box_included)
@@ -231,8 +236,6 @@ def _create_action_ui(self):
range_container.addWidget(symmetrical_box)
range_container.addStretch()
-
-
toolbar1_layout.addWidget(any_change_mode)
toolbar1_layout.addWidget(mode_widget)
toolbar1_layout.addWidget(range_widget)
@@ -247,20 +250,16 @@ def _create_action_ui(self):
toolbar2_layout.addWidget(replace_range)
toolbar2_layout.addStretch()
-
-
- self.widget_layout.addWidget(toolbar_widget)
+ self.widget_layout.addWidget(toolbar_widget)
self.widget_layout.addWidget(self.action_selector)
-
+
self.ui_min_box = min_box
self.ui_min_box_included = min_box_included
self.ui_max_box = max_box
self.ui_max_box_included = min_box_included
self.ui_symmetrical = symmetrical_box
self.ui_range_options = toolbar2_widget
- #self.ui_autorelease = release_box
-
-
+ # self.ui_autorelease = release_box
self.action_selector.action_added.connect(self._add_action)
self.action_selector.action_paste.connect(self._paste_action)
@@ -270,7 +269,7 @@ def _create_action_ui(self):
max_box.valueChanged.connect(self._range_max_changed)
max_box_included.clicked.connect(self._range_max_included_changed)
symmetrical_box.clicked.connect(self._symmetrical_changed)
- #release_box.clicked.connect(self._autorelease_changed)
+ # release_box.clicked.connect(self._autorelease_changed)
self.action_layout.addLayout(self.widget_layout)
@@ -279,24 +278,21 @@ def _create_action_ui(self):
range_widget.setEnabled(not mode)
toolbar2_widget.setEnabled(not mode)
-
toolbar_container.addWidget(toolbar1_widget)
toolbar_container.addWidget(toolbar2_widget)
- #toolbar_container.addWidget(options_widget)
-
+ # toolbar_container.addWidget(options_widget)
# Insert action widgets
for i, action in enumerate(self.profile_data.action_sets):
widget = self._create_action_set_widget(
self.profile_data.action_sets[i],
f"Action {i:d}",
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
self.action_layout.addWidget(widget)
widget.redraw()
widget.model.data_changed.connect(self.container_modified.emit)
-
@QtCore.Slot(bool)
def _change_direction(self, checked):
if checked:
@@ -310,7 +306,7 @@ def _create_condition_ui(self):
widget = self._create_action_set_widget(
self.profile_data.action_sets[i],
f"Action {i:d}",
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
self.activation_condition_layout.addWidget(widget)
widget.redraw()
@@ -324,14 +320,14 @@ def _add_action(self, action_name):
plugin_manager = gremlin.plugin_manager.ActionPlugins()
action_item = plugin_manager.get_class(action_name)(self.profile_data)
self.profile_data.add_action(action_item)
- self.container_modified.emit()
+ self.container_modified.emit()
def _paste_action(self, action, container):
- """ pastes an action into the container """
+ """pastes an action into the container"""
plugin_manager = gremlin.plugin_manager.ActionPlugins()
action_item = plugin_manager.duplicate(action, self.profile_data)
self.profile_data.add_action(action_item)
- self.container_modified.emit()
+ self.container_modified.emit()
def _handle_interaction(self, widget, action):
"""Handles interaction icons being pressed on the individual actions.
@@ -352,16 +348,22 @@ def _handle_interaction(self, widget, action):
# Perform action
if action == gremlin.ui.input_item.ActionSetView.Interactions.Up:
if index > 0:
- self.profile_data.action_sets[index],\
- self.profile_data.action_sets[index-1] = \
- self.profile_data.action_sets[index-1],\
- self.profile_data.action_sets[index]
+ (
+ self.profile_data.action_sets[index],
+ self.profile_data.action_sets[index - 1],
+ ) = (
+ self.profile_data.action_sets[index - 1],
+ self.profile_data.action_sets[index],
+ )
if action == gremlin.ui.input_item.ActionSetView.Interactions.Down:
if index < len(self.profile_data.action_sets) - 1:
- self.profile_data.action_sets[index], \
- self.profile_data.action_sets[index + 1] = \
- self.profile_data.action_sets[index + 1], \
- self.profile_data.action_sets[index]
+ (
+ self.profile_data.action_sets[index],
+ self.profile_data.action_sets[index + 1],
+ ) = (
+ self.profile_data.action_sets[index + 1],
+ self.profile_data.action_sets[index],
+ )
if action == gremlin.ui.input_item.ActionSetView.Interactions.Delete:
del self.profile_data.action_sets[index]
@@ -373,7 +375,7 @@ def _get_window_title(self):
:return title to use for the container
"""
return f"Range: {" -> ".join([", ".join([a.name for a in actions]) for actions in self.profile_data.action_sets])}"
-
+
def _any_change_mode_changed(self):
mode = self.ui_any_change_mode.isChecked()
self.ui_range_widget.setEnabled(not mode)
@@ -381,14 +383,14 @@ def _any_change_mode_changed(self):
self.ui_mode_widget.setEnabled(mode)
self.profile_data.any_change_mode = mode
-
- ''' event handlers for the UI elements in this action '''
+ """ event handlers for the UI elements in this action """
+
def _range_min_changed(self):
self.profile_data.range_min = self.ui_min_box.value()
def _range_max_changed(self):
self.profile_data.range_max = self.ui_max_box.value()
-
+
def _range_min_included_changed(self):
self.profile_data.range_min_included = self.ui_min_box_included.isChecked()
@@ -397,7 +399,7 @@ def _range_max_included_changed(self):
@QtCore.Slot(bool)
def _symmetrical_changed(self):
- self.profile_data.symmetrical = self.ui_symmetrical.isChecked()
+ self.profile_data.symmetrical = self.ui_symmetrical.isChecked()
# @QtCore.Slot(bool)
# def _autorelease_changed(self):
@@ -406,25 +408,27 @@ def _symmetrical_changed(self):
def _add_top_90(self):
self.ui_min_box.setValue(0.90)
self.ui_max_box.setValue(1.00)
-
+
def _add_range(self):
- ''' adds containers '''
+ """adds containers"""
count = self.ui_range_count.value()
action = self.ui_action_dropdown.currentText()
self._add_containers(count, action)
def _replace_range(self):
- ''' replaces current containers with new containers '''
+ """replaces current containers with new containers"""
import gremlin.util
# do a confirmation box just in case
message_box = QtWidgets.QMessageBox()
message_box.setIcon(QtWidgets.QMessageBox.Icon.Warning)
- message_box.setText("This will remove the current container set and any actions.")
+ message_box.setText(
+ "This will remove the current container set and any actions."
+ )
message_box.setInformativeText("Are you sure?")
message_box.setStandardButtons(
- QtWidgets.QMessageBox.StandardButton.Cancel |
- QtWidgets.QMessageBox.StandardButton.Ok
+ QtWidgets.QMessageBox.StandardButton.Cancel
+ | QtWidgets.QMessageBox.StandardButton.Ok
)
gremlin.util.centerDialog(message_box)
result = message_box.exec()
@@ -439,8 +443,7 @@ def _replace_range(self):
widget._remove_container(container)
self._add_range()
-
- def _add_containers(self, count, action_name = None):
+ def _add_containers(self, count, action_name=None):
container_plugins = gremlin.plugin_manager.ContainerPlugins()
# the profile_data member is a RangeContainer object
widget = container_plugins.get_parent_widget(self.profile_data)
@@ -449,10 +452,10 @@ def _add_containers(self, count, action_name = None):
value = -1.0
offset = 2.0 / count
for _ in range(count):
- container : RangeContainer
+ container: RangeContainer
container = widget._add_container(RangeContainer.name)
container.range_min = value
- container.range_min_included = value == -1.0
+ container.range_min_included = value == -1.0
value += offset
container.range_max = value
@@ -460,9 +463,10 @@ def _add_containers(self, count, action_name = None):
if action_name:
container._widget._add_action(action_name)
-
-class RangeReleaseTrigger():
- ''' holds a release trigger when a value is out of range '''
+
+class RangeReleaseTrigger:
+ """holds a release trigger when a value is out of range"""
+
def __init__(self, ranges, event, action):
self.id = get_guid()
self.ranges = ranges
@@ -472,15 +476,14 @@ def __init__(self, ranges, event, action):
def __hash__(self):
return hash(self.id)
-class RangeContainerFunctor(gremlin.base_conditions.AbstractFunctor):
+class RangeContainerFunctor(gremlin.base_conditions.AbstractFunctor):
"""Executes the contents of the associated range container."""
-
- def __init__(self, action_data, parent = None):
+ def __init__(self, action_data, parent=None):
action_data: RangeContainer
super().__init__(action_data, parent)
-
+
self.action_sets = []
for action_set in action_data.action_sets:
self.action_sets.append(
@@ -492,7 +495,9 @@ def __init__(self, action_data, parent = None):
self.any_change_direction = action_data.any_change_direction
self.reset_range()
- self.any_change_delta = action_data.any_change_delta / 200 # 2 * 100 because the range is -1 to +1, so 2 total, to actual range value
+ self.any_change_delta = (
+ action_data.any_change_delta / 200
+ ) # 2 * 100 because the range is -1 to +1, so 2 total, to actual range value
self.range_min = action_data.range_min
self.range_max = action_data.range_max
if self.range_min > self.range_max:
@@ -507,16 +512,17 @@ def __init__(self, action_data, parent = None):
self.last_target = -2.0
def profile_start(self):
- ''' called on profile start - get the start position of the axis'''
- self.last_target = gremlin.joystick_handling.get_axis(self.action_data.hardware_device_guid, self.action_data.hardware_input_id)
-
+ """called on profile start - get the start position of the axis"""
+ self.last_target = gremlin.joystick_handling.get_axis(
+ self.action_data.hardware_device_guid, self.action_data.hardware_input_id
+ )
def reset_range(self):
- ''' resets the range trigger '''
+ """resets the range trigger"""
self.last_range_min = -2.0
self.last_range_max = -2.0
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
"""Executes the content with the provided data.
:param event the event to process
@@ -524,12 +530,12 @@ def process_event(self, event, value, extra_data = None):
:return True if execution was successful, False otherwise
"""
- if not event.is_axis:
+ if not event.is_axis:
return
-
+
verbose = gremlin.config.Configuration().verbose
syslog = logging.getLogger("system")
-
+
# process release triggers
releases = set()
for exit_trigger in self.action_data.exit_range_triggers:
@@ -543,21 +549,16 @@ def process_event(self, event, value, extra_data = None):
for exit_trigger in releases:
self.action_data.exit_range_triggers.remove(exit_trigger)
- #print (f"process release event: {exit_trigger.id}")
+ # print (f"process release event: {exit_trigger.id}")
# trigger the release
release_event = exit_trigger.event
- release_event.is_pressed = False # trigger button based values OFF
+ release_event.is_pressed = False # trigger button based values OFF
action = exit_trigger.action
release_event.value = value.current
-
- release_value = gremlin.actions.Value(False,False)
- action.process_event(release_event, release_value)
-
-
-
-
+ release_value = gremlin.actions.Value(False, False)
+ action.process_event(release_event, release_value)
trigger = False
@@ -570,24 +571,17 @@ def process_event(self, event, value, extra_data = None):
self.latched_loaded = True
-
target = value.current
in_range = False
-
-
-
is_change_mode = False
if self.any_change_mode:
# trigger if change meets the deflection delta
trigger = abs(target - self.last_target) >= self.any_change_delta
- #syslog.info(f"Target {target:0.3f} last: {self.last_target:0.3f} dir flag: {target > self.last_target} delta: {abs(target - self.last_target):0.3f} threshold: {self.any_change_delta:0.3f}")
+ # syslog.info(f"Target {target:0.3f} last: {self.last_target:0.3f} dir flag: {target > self.last_target} delta: {abs(target - self.last_target):0.3f} threshold: {self.any_change_delta:0.3f}")
if trigger:
-
-
-
# apply direction filter if any
if self.any_change_direction == 1:
trigger = target > self.last_target
@@ -601,13 +595,12 @@ def process_event(self, event, value, extra_data = None):
if verbose:
syslog.info("Trigger change")
- is_change_mode = trigger
+ is_change_mode = trigger
self.last_target = target
else:
-
# verify the event is in the correct range
- container : RangeContainer
+ container: RangeContainer
container = self.action_data
range_min = self.range_min
@@ -621,13 +614,11 @@ def process_event(self, event, value, extra_data = None):
if sym_max < sym_min:
sym_max, sym_min = sym_min, sym_max
- #print (f"Add range: {sym_min:0.4f} {sym_max:0.4f}")
+ # print (f"Add range: {sym_min:0.4f} {sym_max:0.4f}")
ranges.append((sym_min, sym_max))
-
-
- for (range_min, range_max) in ranges:
- if target < range_min or target > range_max:
+ for range_min, range_max in ranges:
+ if target < range_min or target > range_max:
continue
if not container.range_min_included:
if target == range_min:
@@ -636,18 +627,18 @@ def process_event(self, event, value, extra_data = None):
if target == range_max:
continue
- #syslog.info(f"{target:0.3f} range {range_min:0.3f} {range_max:0.3f} bracket {self.last_range_min:0.3f} {self.last_range_max:0.3f}")
- #print (f"{target:0.4f} is in range")
+ # syslog.info(f"{target:0.3f} range {range_min:0.3f} {range_max:0.3f} bracket {self.last_range_min:0.3f} {self.last_range_max:0.3f}")
+ # print (f"{target:0.4f} is in range")
in_range = True
break
if in_range:
# axis value is in a bracket range - make sure it hasn't been processed already
- trigger = True # self.last_range_min != range_min and self.last_range_max != range_max
+ trigger = True # self.last_range_min != range_min and self.last_range_max != range_max
for exit_trigger in self.action_data.exit_range_triggers:
for range_min, range_max in exit_trigger.ranges:
- if target < range_min or target > range_max:
+ if target < range_min or target > range_max:
continue
if not container.range_min_included:
if target == range_min:
@@ -657,18 +648,20 @@ def process_event(self, event, value, extra_data = None):
continue
trigger = False
break
-
+
if trigger:
self.last_target = target
if trigger:
- #syslog.info("trigger!")
+ # syslog.info("trigger!")
event_clone = event.clone()
event_clone.event_type = InputType.JoystickButton
- event_clone.identifier = 1
- event_clone.is_axis = False # make this a button event
- event_clone.is_pressed = True # button press is ON
- event_clone.is_virtual_button = True # indicate this is a virtual button press
+ event_clone.identifier = 1
+ event_clone.is_axis = False # make this a button event
+ event_clone.is_pressed = True # button press is ON
+ event_clone.is_virtual_button = (
+ True # indicate this is a virtual button press
+ )
value_clone = gremlin.actions.Value(True, True)
for action in self.action_sets:
# execute the action
@@ -677,7 +670,7 @@ def process_event(self, event, value, extra_data = None):
# register a range exit trigger on non change triggers
exit_trigger = RangeReleaseTrigger(ranges, event_clone, action)
self.action_data.exit_range_triggers.append(exit_trigger)
- #print (f"process press event: {exit_trigger.id}")
+ # print (f"process press event: {exit_trigger.id}")
if in_range:
self.last_range_min = range_min
self.last_range_max = range_max
@@ -687,18 +680,12 @@ def process_event(self, event, value, extra_data = None):
for functor in self.latched_functors:
functor.reset_range()
-
-
-
-
-
-
return True
+
class RangeContainer(AbstractContainer):
- ''' action data for the map to Range action '''
+ """action data for the map to Range action"""
-
name = "Range"
tag = "range"
@@ -717,48 +704,54 @@ class RangeContainer(AbstractContainer):
functor = RangeContainerFunctor
widget = RangeContainerWidget
- def __init__(self, parent, node = None):
- '''' creates a new instance
+ def __init__(self, parent, node=None):
+ """' creates a new instance
:parent the InputItem which is the parent to this action
- '''
+ """
super().__init__(parent, node)
- self.id = get_guid() # unique id of this item
- self._index = 0 # index # of this item
- self.range_min = -1.0 # lower bound of the range
- self.range_min_included = False # true if the lower range is excluded from the range
- self.range_max = 1.0 # upper bound of the range
- self.range_max_included = False # true if the higher range is excluded from the range
- self.symmetrical = False # true if the range is symmetrical about the center of the axis
- self.range_min_included = True # true if the boundary is included in the range
- self.range_max_included = True # true if the boundery is included in the range
- self._widget = None # will be populated by the widget attached to this container
- self._functor = None # will be populated when the functor is created for this container
- self.any_change_mode = False # trigger on any change mode
- self.any_change_delta = 10 # percentage move that must be detected before the action is triggered 0 to 100
- self.any_change_direction = 0 # direction of change 0 = both, 1 = up, -1 = down
+ self.id = get_guid() # unique id of this item
+ self._index = 0 # index # of this item
+ self.range_min = -1.0 # lower bound of the range
+ self.range_min_included = (
+ False # true if the lower range is excluded from the range
+ )
+ self.range_max = 1.0 # upper bound of the range
+ self.range_max_included = (
+ False # true if the higher range is excluded from the range
+ )
+ self.symmetrical = (
+ False # true if the range is symmetrical about the center of the axis
+ )
+ self.range_min_included = True # true if the boundary is included in the range
+ self.range_max_included = True # true if the boundery is included in the range
+ self._widget = (
+ None # will be populated by the widget attached to this container
+ )
+ self._functor = (
+ None # will be populated when the functor is created for this container
+ )
+ self.any_change_mode = False # trigger on any change mode
+ self.any_change_delta = 10 # percentage move that must be detected before the action is triggered 0 to 100
+ self.any_change_direction = 0 # direction of change 0 = both, 1 = up, -1 = down
self.condition_enabled = False
self.virtual_button_enabled = False
- self.exit_range_triggers = [] # triggers to execute when the range is exited
- self.autorelease = False # if set, autoreleases when the value is outside the range after being in the range
-
-
+ self.exit_range_triggers = [] # triggers to execute when the range is exited
+ self.autorelease = False # if set, autoreleases when the value is outside the range after being in the range
+
# make actions think we're attached to a button
self.override_input_id = 1
self.override_input_type = InputType.JoystickButton
-
-
def icon(self):
"""Returns the icon to use for this action.
:return icon representing this action
"""
- return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
-
+ return f"{os.path.dirname(os.path.realpath(__file__))}/icon.png"
- def _parse_xml(self, node, data = None):
- ''' reads configuration '''
+ def _parse_xml(self, node, data=None):
+ """reads configuration"""
try:
if "any" in node.attrib:
self.any_change_mode = safe_read(node, "any", bool)
@@ -773,19 +766,18 @@ def _parse_xml(self, node, data = None):
if "max_inc" in node.attrib:
self.range_min_included = safe_read(node, "max_inc", bool)
if "sym" in node.attrib:
- self.symmetrical = safe_read(node, "sym",bool)
+ self.symmetrical = safe_read(node, "sym", bool)
if "direction" in node.attrib:
- self.any_change_direction = safe_read(node,"direction",int)
+ self.any_change_direction = safe_read(node, "direction", int)
if "autorelease" in node.attrib:
- self.autorelease = safe_read(node,"autorelease", bool, False)
-
- except:
+ self.autorelease = safe_read(node, "autorelease", bool, False)
+
+ except Exception:
pass
pass
def _generate_xml(self):
-
- ''' returns an xml node encoding this action's data '''
+ """returns an xml node encoding this action's data"""
node = ElementTree.Element("container")
node.set("type", RangeContainer.tag)
node.set("any", safe_format(self.any_change_mode, bool))
@@ -806,18 +798,14 @@ def _generate_xml(self):
return node
-
def _is_container_valid(self):
"""Returns whether or not this container is configured properly.
:return True if the container is configured properly, False otherwise
"""
- return True # len(self.action_sets) > 0
-
+ return True # len(self.action_sets) > 0
version = 1
name = "Range"
create = RangeContainer
-
-
diff --git a/container_plugins/sequence/__init__.py b/container_plugins/sequence/__init__.py
index 4dc1ab36..65345a68 100644
--- a/container_plugins/sequence/__init__.py
+++ b/container_plugins/sequence/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -42,7 +42,6 @@
class SequenceContainerWidget(AbstractContainerWidget):
-
"""Container which holds a sequence of actions."""
def __init__(self, profile_data, parent=None):
@@ -59,8 +58,7 @@ def _create_action_ui(self):
self.profile_data.create_or_delete_virtual_button()
self.action_selector = gremlin.ui.ui_common.ActionSelector(
- self.profile_data.get_input_type(),
- self.profile_data
+ self.profile_data.get_input_type(), self.profile_data
)
self.action_selector.action_added.connect(self._add_action)
self.action_selector.add_button.setText("Add Step")
@@ -69,7 +67,9 @@ def _create_action_ui(self):
self.widget_layout.addWidget(self.action_selector)
self._trigger_on_release_widget = QtWidgets.QCheckBox("Trigger on release")
- self._trigger_on_release_widget.setToolTip("Triggers the sequence on input release instead of input press")
+ self._trigger_on_release_widget.setToolTip(
+ "Triggers the sequence on input release instead of input press"
+ )
self._trigger_on_release_widget.setChecked(self.profile_data.trigger_on_release)
self._trigger_on_release_widget.clicked.connect(self._trigger_mode_changed)
@@ -83,7 +83,7 @@ def _create_action_ui(self):
widget = self._create_action_set_widget(
self.profile_data.action_sets[i],
f"Step {i + 1}",
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
self.action_layout.addWidget(widget)
widget.redraw()
@@ -99,7 +99,7 @@ def _create_condition_ui(self):
widget = self._create_action_set_widget(
self.profile_data.action_sets[i],
f"Step {i:d}",
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
self.activation_condition_layout.addWidget(widget)
widget.redraw()
@@ -116,14 +116,12 @@ def _add_action(self, action_name):
self.container_modified.emit()
def _paste_action(self, action):
- ''' pastes an action '''
+ """pastes an action"""
plugin_manager = gremlin.plugin_manager.ActionPlugins()
action_item = plugin_manager.duplicate(action, self.profile_data)
self.profile_data.add_action(action_item)
self.container_modified.emit()
-
-
def _handle_interaction(self, widget, action):
"""Handles interaction icons being pressed on the individual actions.
@@ -143,16 +141,22 @@ def _handle_interaction(self, widget, action):
# Perform action
if action == gremlin.ui.input_item.ActionSetView.Interactions.Up:
if index > 0:
- self.profile_data.action_sets[index],\
- self.profile_data.action_sets[index-1] = \
- self.profile_data.action_sets[index-1],\
- self.profile_data.action_sets[index]
+ (
+ self.profile_data.action_sets[index],
+ self.profile_data.action_sets[index - 1],
+ ) = (
+ self.profile_data.action_sets[index - 1],
+ self.profile_data.action_sets[index],
+ )
if action == gremlin.ui.input_item.ActionSetView.Interactions.Down:
if index < len(self.profile_data.action_sets) - 1:
- self.profile_data.action_sets[index], \
- self.profile_data.action_sets[index + 1] = \
- self.profile_data.action_sets[index + 1], \
- self.profile_data.action_sets[index]
+ (
+ self.profile_data.action_sets[index],
+ self.profile_data.action_sets[index + 1],
+ ) = (
+ self.profile_data.action_sets[index + 1],
+ self.profile_data.action_sets[index],
+ )
if action == gremlin.ui.input_item.ActionSetView.Interactions.Delete:
del self.profile_data.action_sets[index]
@@ -167,18 +171,17 @@ def _get_window_title(self):
class SequenceContainerFunctor(gremlin.base_conditions.AbstractFunctor):
-
- def __init__(self, container : SequenceContainer, parent = None):
+ def __init__(self, container: SequenceContainer, parent=None):
super().__init__(container, parent)
self.action_sets = []
self.container = container
- self.graph_map = {} # holds index to graph
- self.index_map = {} # holds graph to index
+ self.graph_map = {} # holds index to graph
+ self.index_map = {} # holds graph to index
self._macro_id = None
index = 0
for action_set in container.action_sets:
graph = gremlin.execution_graph.ActionSetExecutionGraph(action_set, parent)
- self.action_sets.append(graph)
+ self.action_sets.append(graph)
self.graph_map[index] = graph
self.index_map[graph] = index
@@ -195,54 +198,53 @@ def __init__(self, container : SequenceContainer, parent = None):
if cond.comparison == "press":
self.switch_on_press = True
-
eh = gremlin.event_handler.EventListener()
eh.macro_step_completed.connect(self._macro_completed)
def profile_start(self):
- ''' occurs at profile start '''
+ """occurs at profile start"""
self.index = 0
self._event = None
self._value = None
self._macro_id = None
-
- def process_event(self, event : gremlin.event_handler.Event, value : gremlin.actions.Value, extra_data = None):
+ def process_event(
+ self,
+ event: gremlin.event_handler.Event,
+ value: gremlin.actions.Value,
+ extra_data=None,
+ ):
syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose
if self._macro_id is not None:
# ignore events while the sequence is still running
return True
-
- auto_release = False
if event.event_type == InputType.JoystickHat:
- is_pressed = value.current != (0,0)
+ is_pressed = value.current != (0, 0)
elif not isinstance(value.current, bool):
- syslog.warning(f"Invalid data type received in Sequence container: {type(event.value)}")
+ syslog.warning(
+ f"Invalid data type received in Sequence container: {type(event.value)}"
+ )
return False
else:
is_pressed = value.current
-
if self.container.trigger_on_release:
if is_pressed:
- # ignore pressed event if we're triggering on input release
- if verbose: syslog.info(f"SEQUENCE: execute - ignore pressed event")
+ # ignore pressed event if we're triggering on input release
+ if verbose:
+ syslog.info("SEQUENCE: execute - ignore pressed event")
return True
- is_pressed = True # flip it for containers
- auto_release = True
+ is_pressed = True # flip it for containers
value.is_pressed = is_pressed
value.current = is_pressed
event.is_pressed = is_pressed
event.raw_value = is_pressed
-
- self._macro_id = 0
-
-
+ self._macro_id = 0
count = len(self.action_sets)
@@ -255,36 +257,36 @@ def process_event(self, event : gremlin.event_handler.Event, value : gremlin.act
action.data = f"Step {index + 1}"
macro.add_action(action)
-
# queue the work up
mgr.queue_macro(macro)
- if verbose: syslog.info(f"SEQUENCE: execute graph sequence - id {self._macro_id}")
-
+ if verbose:
+ syslog.info(f"SEQUENCE: execute graph sequence - id {self._macro_id}")
+
return True
-
+
@QtCore.Slot(int)
- def _macro_completed(self, id : int):
- ''' occurs when a macro completes - the id is the id of the macro completed '''
-
+ def _macro_completed(self, id: int):
+ """occurs when a macro completes - the id is the id of the macro completed"""
+
if self._macro_id is not None and id == self._macro_id:
syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose
- if verbose: syslog.info(f"SEQUENCE: completed graph sequence - id {self._macro_id}")
+ if verbose:
+ syslog.info(f"SEQUENCE: completed graph sequence - id {self._macro_id}")
self._macro_id = None
class SequenceContainer(AbstractContainer):
-
"""Represents a container which holds sequential actions.
The actions will trigger one after the other with subsequent activations.
-
+
"""
name = "Sequence"
tag = "sequence"
- #override default allowed inputs here
+ # override default allowed inputs here
input_types = [
InputType.JoystickButton,
InputType.JoystickHat,
@@ -292,7 +294,7 @@ class SequenceContainer(AbstractContainer):
InputType.KeyboardLatched,
InputType.OpenSoundControl,
InputType.Midi,
- InputType.Mouse
+ InputType.Mouse,
]
interaction_types = [
gremlin.ui.input_item.ActionSetView.Interactions.Up,
@@ -303,22 +305,21 @@ class SequenceContainer(AbstractContainer):
functor = SequenceContainerFunctor
widget = SequenceContainerWidget
- def __init__(self, parent=None, node = None):
+ def __init__(self, parent=None, node=None):
"""Creates a new instance.
:param parent the InputItem this container is linked to
"""
super().__init__(parent, node)
- self.trigger_on_release = False # true if the sequence triggers on input release instead of input press
-
+ self.trigger_on_release = False # true if the sequence triggers on input release instead of input press
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Populates the container with the XML node's contents.
:param node the XML node with which to populate the container
"""
if "trigger_on_release" in node.attrib:
- self.trigger_on_release = safe_read(node,"trigger_on_release",bool,False)
+ self.trigger_on_release = safe_read(node, "trigger_on_release", bool, False)
# action sets are read by the parent
def _generate_xml(self):
@@ -328,7 +329,7 @@ def _generate_xml(self):
"""
node = ElementTree.Element("container")
node.set("type", SequenceContainer.tag)
- node.set("trigger_on_release",safe_format(self.trigger_on_release,bool))
+ node.set("trigger_on_release", safe_format(self.trigger_on_release, bool))
for actions in self.action_sets:
as_node = ElementTree.Element("action-set")
for action in actions:
@@ -342,7 +343,7 @@ def _is_container_valid(self):
:return True if the container is configured properly, False otherwise
"""
return True
- #return len(self.action_sets) > 0
+ # return len(self.action_sets) > 0
# Plugin definitions
diff --git a/container_plugins/smart_toggle/__init__.py b/container_plugins/smart_toggle/__init__.py
index aad6a208..4689c880 100644
--- a/container_plugins/smart_toggle/__init__.py
+++ b/container_plugins/smart_toggle/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -33,8 +33,8 @@
syslog = logging.getLogger("system")
-class SmartToggleContainerWidget(AbstractContainerWidget):
+class SmartToggleContainerWidget(AbstractContainerWidget):
"""SmartToggle container which holds or toggles a single action."""
def __init__(self, profile_data, parent=None):
@@ -52,9 +52,7 @@ def _create_action_ui(self):
self.options_layout = QtWidgets.QHBoxLayout()
# Activation delay
- self.options_layout.addWidget(
- QtWidgets.QLabel("Toggle time: ")
- )
+ self.options_layout.addWidget(QtWidgets.QLabel("Toggle time: "))
self.delay_input = gremlin.ui.ui_common.DynamicDoubleSpinBox()
self.delay_input.setRange(0.1, 2.0)
self.delay_input.setSingleStep(0.1)
@@ -72,7 +70,7 @@ def _create_action_ui(self):
widget = self._create_action_set_widget(
self.profile_data.action_sets[0],
"Smart Toggle",
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
self.action_layout.addWidget(widget)
widget.redraw()
@@ -88,11 +86,10 @@ def _create_action_ui(self):
def _create_condition_ui(self):
if self.profile_data.action_sets:
-
widget = self._create_action_set_widget(
self.profile_data.action_sets[0],
"Smart Toggle",
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
self.activation_condition_layout.addWidget(widget)
widget.redraw()
@@ -122,7 +119,7 @@ def _paste_action(self, action, container):
self.profile_data.action_sets[0] = []
self.profile_data.action_sets[0].append(action_item)
self.profile_data.create_or_delete_virtual_button()
- self.container_modified.emit()
+ self.container_modified.emit()
def _delay_changed_cb(self, value):
"""Updates the activation delay value.
@@ -161,10 +158,9 @@ def _get_window_title(self):
class SmartToggleContainerFunctor(gremlin.base_conditions.AbstractFunctor):
-
"""Executes the contents of the associated SmartToggle container."""
- def __init__(self, container, parent = None):
+ def __init__(self, container, parent=None):
"""Creates a new functor instance.
Parameters
@@ -187,7 +183,7 @@ def __init__(self, container, parent = None):
if "needs_auto_release" in functor.__dict__:
functor.needs_auto_release = False
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
# TODO: Currently this does not handle hat or axis events, however
# virtual buttons created on those inputs is supported
if not isinstance(value.current, bool):
@@ -204,10 +200,7 @@ def process_event(self, event, value, extra_data = None):
# Run release logic when the second press happens in toggle mode
elif self.mode == "toggle":
- self.action_set.process_event(
- self.release_event,
- self.release_value
- )
+ self.action_set.process_event(self.release_event, self.release_value)
self.activation_time = 0.0
self.mode = None
else:
@@ -229,7 +222,6 @@ def process_event(self, event, value, extra_data = None):
class SmartToggleContainer(AbstractContainer):
-
"""Represents a container which holds exactly one action."""
name = "Smart Toggle"
@@ -247,7 +239,7 @@ class SmartToggleContainer(AbstractContainer):
functor = SmartToggleContainerFunctor
widget = SmartToggleContainerWidget
- def __init__(self, parent=None, node = None):
+ def __init__(self, parent=None, node=None):
"""Creates a new instance.
:param parent the InputItem this container is linked to
@@ -256,7 +248,7 @@ def __init__(self, parent=None, node = None):
self.action_sets = [[]]
self.delay = 0.5
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Populates the container with the XML node's contents.
:param node the XML node with which to populate the container
diff --git a/container_plugins/switch/__init__.py b/container_plugins/switch/__init__.py
index e614d7c9..42aec72f 100644
--- a/container_plugins/switch/__init__.py
+++ b/container_plugins/switch/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -35,32 +35,34 @@
import gremlin.joystick_handling
from gremlin.input_types import InputType
import enum
-import gremlin.util
+import gremlin.util
from gremlin.util import safe_format, safe_read
syslog = logging.getLogger("system")
+
class SwitchModeType(enum.IntEnum):
- ''' possible switch modes '''
+ """possible switch modes"""
+
NotSet = 0
OnChange = 1
OnPress = 2
OnRelease = 3
@staticmethod
- def to_display_name(value : SwitchModeType):
+ def to_display_name(value: SwitchModeType):
return _switch_mode_to_display_lookup[value]
-
+
@staticmethod
- def to_enum(value : str):
+ def to_enum(value: str):
return _switch_mode_to_enum_lookup[value]
-
+
@staticmethod
- def to_string(value : SwitchModeType):
+ def to_string(value: SwitchModeType):
return _switch_mode_to_string_lookup[value]
-
+
@staticmethod
- def to_description(value : SwitchModeType):
+ def to_description(value: SwitchModeType):
return _switch_mode_to_description_lookup[value]
@@ -68,37 +70,43 @@ def to_description(value : SwitchModeType):
SwitchModeType.NotSet: "Not set",
SwitchModeType.OnChange: "On Change",
SwitchModeType.OnPress: "On Press",
- SwitchModeType.OnRelease: "On Release"
+ SwitchModeType.OnRelease: "On Release",
}
_switch_mode_to_description_lookup = {
SwitchModeType.NotSet: "",
SwitchModeType.OnChange: "Action will execute when the input state changes",
SwitchModeType.OnPress: "Actions will execute when the button is pressed",
- SwitchModeType.OnRelease: "Actions will execute when the button is released"
+ SwitchModeType.OnRelease: "Actions will execute when the button is released",
}
_switch_mode_to_string_lookup = {
SwitchModeType.NotSet: "none",
SwitchModeType.OnChange: "on_change",
SwitchModeType.OnPress: "on_press",
- SwitchModeType.OnRelease: "on_release"
+ SwitchModeType.OnRelease: "on_release",
}
_switch_mode_to_enum_lookup = {
- "none" : SwitchModeType.NotSet ,
- "on_change" : SwitchModeType.OnChange,
- "on_press" : SwitchModeType.OnPress,
- "on_release" : SwitchModeType.OnRelease
+ "none": SwitchModeType.NotSet,
+ "on_change": SwitchModeType.OnChange,
+ "on_press": SwitchModeType.OnPress,
+ "on_release": SwitchModeType.OnRelease,
}
class SwitchWidget(QtWidgets.QWidget):
- ''' widget that holds the UI for a single switch position '''
+ """widget that holds the UI for a single switch position"""
delete_item = QtCore.Signal(object)
- def __init__(self, container : SwitchContainerWidget, profile_data : SwitchContainer, data : SwitchData, parent = None):
+ def __init__(
+ self,
+ container: SwitchContainerWidget,
+ profile_data: SwitchContainer,
+ data: SwitchData,
+ parent=None,
+ ):
super().__init__(parent)
self.main_layout = QtWidgets.QVBoxLayout(self)
@@ -109,7 +117,9 @@ def __init__(self, container : SwitchContainerWidget, profile_data : SwitchConta
device_widget = QtWidgets.QWidget()
device_layout = QtWidgets.QHBoxLayout(device_widget)
- device_layout.addWidget(QtWidgets.QLabel(f"Switch position [{data.index+1}]"))
+ device_layout.addWidget(
+ QtWidgets.QLabel(f"Switch position [{data.index+1}]")
+ )
self.selector_device_widget = gremlin.ui.ui_common.NoWheelComboBox()
self.selector_input_widget = gremlin.ui.ui_common.NoWheelComboBox()
@@ -124,11 +134,8 @@ def __init__(self, container : SwitchContainerWidget, profile_data : SwitchConta
self.listen_widget.clicked.connect(self._listen_cb)
device_layout.addWidget(self.listen_widget)
-
-
self.main_layout.addWidget(device_widget)
-
# populate the selector with hardware inputs
self._selector_enabled = True
@@ -137,7 +144,9 @@ def __init__(self, container : SwitchContainerWidget, profile_data : SwitchConta
default_device = None
selected_input_id = 1
if data.device_id is not None:
- default_device = next((dev for dev in devices if dev.device_id == data.device_id), None)
+ default_device = next(
+ (dev for dev in devices if dev.device_id == data.device_id), None
+ )
if default_device:
if default_device.device_guid == data.device_guid:
# the merge device to pick is the same as the current device
@@ -145,11 +154,21 @@ def __init__(self, container : SwitchContainerWidget, profile_data : SwitchConta
# there is only one input which is already used
self._selector_enabled = False
- if data.input_id is not None and data.input_id < default_device.button_count :
+ if (
+ data.input_id is not None
+ and data.input_id < default_device.button_count
+ ):
selected_input_id = data.input_id
if not default_device:
- default_device = next((dev for dev in devices if dev.device_guid == self.profile_data.hardware_device_guid), None)
+ default_device = next(
+ (
+ dev
+ for dev in devices
+ if dev.device_guid == self.profile_data.hardware_device_guid
+ ),
+ None,
+ )
if default_device:
button_count = default_device.button_count
if button_count == 1:
@@ -165,9 +184,16 @@ def __init__(self, container : SwitchContainerWidget, profile_data : SwitchConta
elif input_id > 1:
# pick one below if next not available
selected_input_id = input_id - 1
-
- # Insert the action widgets for this switch
- action_set = next((action_set for i,action_set in enumerate(self.profile_data.action_sets) if i == data.index), None)
+
+ # Insert the action widgets for this switch
+ action_set = next(
+ (
+ action_set
+ for i, action_set in enumerate(self.profile_data.action_sets)
+ if i == data.index
+ ),
+ None,
+ )
if action_set is None:
# add the action set
self.profile_data.action_sets.append([])
@@ -176,15 +202,13 @@ def __init__(self, container : SwitchContainerWidget, profile_data : SwitchConta
widget = self.container._create_action_set_widget(
action_set,
f"Action {data.index:d}",
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
self.main_layout.addWidget(widget)
widget.redraw()
widget.model.data_changed.connect(self._action_changed)
self.action_widget = widget
-
-
-
+
if not self._selector_enabled:
return
@@ -199,13 +223,14 @@ def __init__(self, container : SwitchContainerWidget, profile_data : SwitchConta
self.selector_device_widget.currentIndexChanged.connect(self._device_changed_cb)
self.selector_input_widget.currentIndexChanged.connect(self._input_changed_cb)
-
# populate the buttons
self.selector_device_widget.setCurrentIndex(selected_device_index)
for switch_type in SwitchModeType:
if switch_type != SwitchModeType.NotSet:
- rb = gremlin.ui.ui_common.QDataRadioButton(text = SwitchModeType.to_display_name(switch_type), data = switch_type)
+ rb = gremlin.ui.ui_common.QDataRadioButton(
+ text=SwitchModeType.to_display_name(switch_type), data=switch_type
+ )
rb.data = switch_type
device_layout.addWidget(rb)
if data.mode == switch_type:
@@ -213,7 +238,6 @@ def __init__(self, container : SwitchContainerWidget, profile_data : SwitchConta
rb.clicked.connect(self._switch_mode_changed)
-
# select the default device
self.selector_device_widget.setCurrentIndex(selected_device_index)
@@ -222,16 +246,14 @@ def __init__(self, container : SwitchContainerWidget, profile_data : SwitchConta
selected_input_index = 0
self.selector_input_widget.setCurrentIndex(selected_input_index)
-
-
self.delete_button = QtWidgets.QPushButton(
- gremlin.util.load_icon("gfx/{prefix}button_delete.png"), "")
+ gremlin.util.load_icon("gfx/{prefix}button_delete.png"), ""
+ )
self.delete_button.setToolTip("Delete this entry")
self.delete_button.clicked.connect(self._delete_cb)
device_layout.addStretch()
device_layout.addWidget(self.delete_button)
-
@QtCore.Slot()
def _delete_cb(self):
msgbox = gremlin.ui.ui_common.ConfirmBox(f"Delete switch {self.data.index}?")
@@ -240,11 +262,11 @@ def _delete_cb(self):
self.delete_item.emit(self.data)
QtCore.Slot()
+
def _listen_cb(self):
- ''' listen to an input for a button '''
+ """listen to an input for a button"""
button_press_dialog = gremlin.ui.ui_common.InputListenerWidget(
- [InputType.JoystickButton],
- return_kb_event=False
+ [InputType.JoystickButton], return_kb_event=False
)
button_press_dialog.item_selected.connect(self._update_button)
@@ -259,58 +281,56 @@ def _listen_cb(self):
int(geom.x() + geom.width() / 2 - 150),
int(geom.y() + geom.height() / 2 - 75),
300,
- 150
+ 150,
)
button_press_dialog.show()
@QtCore.Slot()
- def _update_button(self, event : gremlin.event_handler.Event):
- ''' called when a button input is selected '''
+ def _update_button(self, event: gremlin.event_handler.Event):
+ """called when a button input is selected"""
hardware_index = self.selector_device_widget.findData(event.device_id)
self.selector_device_widget.setCurrentIndex(hardware_index)
input_index = self.selector_input_widget.findData(event.identifier)
self.selector_input_widget.setCurrentIndex(input_index)
-
@QtCore.Slot()
def _action_changed(self):
- ''' occurs when the action list changes '''
+ """occurs when the action list changes"""
self.action_widget.redraw()
self.container.container_modified.emit()
@QtCore.Slot()
def _device_changed_cb(self):
- ''' merge device changed '''
+ """merge device changed"""
index = self.selector_device_widget.currentIndex()
device_id = self.selector_device_widget.itemData(index)
dev = self.profile_data.device_map[device_id]
with QtCore.QSignalBlocker(self.selector_input_widget):
self.selector_input_widget.clear()
first_input_id = None
- for input_id in range(1, dev.button_count+1):
+ for input_id in range(1, dev.button_count + 1):
self.selector_input_widget.addItem(f"Button {input_id}", input_id)
if first_input_id is None:
first_input_id = input_id
self.data.device_id = device_id
self.data.input_id = first_input_id
-
-
+
@QtCore.Slot()
def _input_changed_cb(self):
- ''' merge input changed '''
+ """merge input changed"""
index = self.selector_input_widget.currentIndex()
input_id = self.selector_input_widget.itemData(index)
self.data.input_id = input_id
@QtCore.Slot()
def _switch_mode_changed(self):
- ''' mode changed '''
+ """mode changed"""
widget = self.sender()
mode = widget.data
self.data.mode = mode
-class SwitchContainerWidget(AbstractContainerWidget):
+class SwitchContainerWidget(AbstractContainerWidget):
"""Container which holds a sequence of actions."""
def __init__(self, profile_data, parent=None):
@@ -321,11 +341,8 @@ def __init__(self, profile_data, parent=None):
"""
super().__init__(profile_data, parent)
-
-
-
def _update_ui(self):
- ''' redraws the entire switch content '''
+ """redraws the entire switch content"""
self._widget_map.clear()
self.action_widgets.clear()
gremlin.util.clear_layout(self.action_layout)
@@ -333,7 +350,7 @@ def _update_ui(self):
def _create_action_ui(self):
"""Creates the UI components."""
- self._widget_map = {} # map of widgets by position index
+ self._widget_map = {} # map of widgets by position index
self.profile_data.create_or_delete_virtual_button()
self.action_selector = gremlin.ui.ui_common.ActionSelector(
@@ -341,18 +358,21 @@ def _create_action_ui(self):
self.profile_data,
)
-
self.action_selector.action_added.connect(self._add_action)
self.action_selector.action_paste.connect(self._paste_action)
self.header_widget = QtWidgets.QWidget()
- self.header_widget.setContentsMargins(0,0,0,0)
+ self.header_widget.setContentsMargins(0, 0, 0, 0)
self.header_layout = QtWidgets.QHBoxLayout(self.header_widget)
- self.header_layout.setContentsMargins(0,0,0,0)
+ self.header_layout.setContentsMargins(0, 0, 0, 0)
# positions
- self.header_layout.addWidget(QtWidgets.QLabel(f"Switch positions: {self.profile_data.position_count}"))
-
+ self.header_layout.addWidget(
+ QtWidgets.QLabel(
+ f"Switch positions: {self.profile_data.position_count}"
+ )
+ )
+
# switch positions
self.add_position = QtWidgets.QPushButton("Add Switch Position")
self.add_position.clicked.connect(self._add_position)
@@ -361,14 +381,13 @@ def _create_action_ui(self):
self.action_layout.addWidget(self.header_widget)
-
- ''' creates the switch entries '''
- data : SwitchData
+ """ creates the switch entries """
+ data: SwitchData
for data in self.profile_data.position_data.values():
self._create_selector_ui(data)
- def _create_selector_ui(self, data : SwitchData):
- ''' creates the input selector '''
+ def _create_selector_ui(self, data: SwitchData):
+ """creates the input selector"""
# merge operations
switch_widget = SwitchWidget(self, self.profile_data, data)
@@ -378,15 +397,13 @@ def _create_selector_ui(self, data : SwitchData):
self.action_widget = switch_widget.action_widget
self.action_widgets.append(switch_widget.action_widget)
-
-
def _create_condition_ui(self):
if self.profile_data.action_sets:
for i, action in enumerate(self.profile_data.action_sets):
widget = self._create_action_set_widget(
self.profile_data.action_sets[i],
f"Switch {i+1} Action(s):",
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
self.activation_condition_layout.addWidget(widget)
widget.redraw()
@@ -405,17 +422,24 @@ def _add_action(self, action_name):
def _add_position(self):
index = len(self.profile_data.position_data)
- used_inputs = [data.input_id for data in self.profile_data.position_data.values()]
+ used_inputs = [
+ data.input_id for data in self.profile_data.position_data.values()
+ ]
device_id = self.profile_data.hardware_device_id
device = self.profile_data.device_map[device_id]
input_id = 0
for id in range(device.button_count):
- if not id in used_inputs:
+ if id not in used_inputs:
input_id = id
break
- self.profile_data.position_data[index] = SwitchData(index,self.profile_data.hardware_device_guid, input_id, SwitchModeType.OnChange)
+ self.profile_data.position_data[index] = SwitchData(
+ index,
+ self.profile_data.hardware_device_guid,
+ input_id,
+ SwitchModeType.OnChange,
+ )
self._update_ui()
@@ -423,9 +447,8 @@ def _delete_cb(self, data):
del self.profile_data.position_data[data.index]
self._update_ui()
-
def _paste_action(self, action, container):
- ''' pastes an action '''
+ """pastes an action"""
plugin_manager = gremlin.plugin_manager.ActionPlugins()
action_item = plugin_manager.duplicate(action, self.profile_data)
self.profile_data.add_action(action_item)
@@ -450,16 +473,22 @@ def _handle_interaction(self, widget, action):
# Perform action
if action == gremlin.ui.input_item.ActionSetView.Interactions.Up:
if index > 0:
- self.profile_data.action_sets[index],\
- self.profile_data.action_sets[index-1] = \
- self.profile_data.action_sets[index-1],\
- self.profile_data.action_sets[index]
+ (
+ self.profile_data.action_sets[index],
+ self.profile_data.action_sets[index - 1],
+ ) = (
+ self.profile_data.action_sets[index - 1],
+ self.profile_data.action_sets[index],
+ )
if action == gremlin.ui.input_item.ActionSetView.Interactions.Down:
if index < len(self.profile_data.action_sets) - 1:
- self.profile_data.action_sets[index], \
- self.profile_data.action_sets[index + 1] = \
- self.profile_data.action_sets[index + 1], \
- self.profile_data.action_sets[index]
+ (
+ self.profile_data.action_sets[index],
+ self.profile_data.action_sets[index + 1],
+ ) = (
+ self.profile_data.action_sets[index + 1],
+ self.profile_data.action_sets[index],
+ )
if action == gremlin.ui.input_item.ActionSetView.Interactions.Delete:
del self.profile_data.action_sets[index]
@@ -474,10 +503,9 @@ def _get_window_title(self):
class SwitchContainerFunctor(gremlin.base_conditions.AbstractFunctor):
-
- def __init__(self, container, parent = None):
+ def __init__(self, container, parent=None):
super().__init__(container, parent)
- self.profile_data : SwitchContainer = container
+ self.profile_data: SwitchContainer = container
self.action_sets = []
for action_set in container.action_sets:
self.action_sets.append(
@@ -499,21 +527,27 @@ def __init__(self, container, parent = None):
if cond.comparison == "press":
self.switch_on_press = True
-
def latch_extra_inputs(self):
- ''' returns the list of extra devices to latch to this functor (device_guid, input_type, input_id) '''
+ """returns the list of extra devices to latch to this functor (device_guid, input_type, input_id)"""
latch_list = []
- data : SwitchData
+ data: SwitchData
for data in self.profile_data.position_data.values():
- latch_list.append((data.device_guid, InputType.JoystickButton, data.input_id))
+ latch_list.append(
+ (data.device_guid, InputType.JoystickButton, data.input_id)
+ )
return latch_list
- def process_event(self, event : gremlin.event_handler.Event, value : gremlin.actions.Value, extra_data = None):
+ def process_event(
+ self,
+ event: gremlin.event_handler.Event,
+ value: gremlin.actions.Value,
+ extra_data=None,
+ ):
if event.is_axis:
return True
if event.event_type == InputType.JoystickHat:
is_hat = True
- is_pressed = value.current != (0,0)
+ is_pressed = value.current != (0, 0)
elif not isinstance(value.current, bool):
syslog.warning(
f"Invalid data type received in Switch container: {type(event.value)}"
@@ -522,9 +556,9 @@ def process_event(self, event : gremlin.event_handler.Event, value : gremlin.act
else:
is_hat = False
is_pressed = value.current
-
- data : SwitchData
-
+
+ data: SwitchData
+
for data in self.profile_data.position_data.values():
if data.device_guid != event.device_guid:
continue
@@ -541,28 +575,32 @@ def process_event(self, event : gremlin.event_handler.Event, value : gremlin.act
continue
if value.current is None:
- value.current = (0,0) if is_hat else is_pressed
-
-
+ value.current = (0, 0) if is_hat else is_pressed
self.action_sets[data.index].process_event(event, value)
-
return True
-class SwitchData():
- ''' data block for each switch position '''
- def __init__(self, index = -1, device_guid = None, input_id = None, mode : SwitchModeType = SwitchModeType.NotSet):
- self.index = index # sequence
+class SwitchData:
+ """data block for each switch position"""
+
+ def __init__(
+ self,
+ index=-1,
+ device_guid=None,
+ input_id=None,
+ mode: SwitchModeType = SwitchModeType.NotSet,
+ ):
+ self.index = index # sequence
self.device_guid = device_guid
self.input_id = input_id
self.mode = mode
self.device_id = str(device_guid)
- self.action_set = None # data associated with this set
+ self.action_set = None # data associated with this set
def _generate_xml(self):
- ''' create xml data '''
+ """create xml data"""
node = ElementTree.Element("switch")
node.set("index", str(self.index))
node.set("mode", SwitchModeType.to_string(self.mode))
@@ -570,9 +608,9 @@ def _generate_xml(self):
node.set("device_id", self.device_id)
return node
-
- def _parse_xml(self, node, data = None):
- ''' read xml data '''
+
+ def _parse_xml(self, node, data=None):
+ """read xml data"""
if node.tag == "switch":
if "index" in node.attrib:
self.index = safe_read(node, "index", int, -1)
@@ -583,13 +621,9 @@ def _parse_xml(self, node, data = None):
if "device_id" in node.attrib:
self.device_id = node.get("device_id")
self.device_guid = gremlin.util.parse_guid(self.device_id)
-
-
-
class SwitchContainer(AbstractContainer):
-
"""Represents a container which holds multiplier actions.
The actions will trigger one after the other with subsequent activations.
@@ -613,7 +647,7 @@ class SwitchContainer(AbstractContainer):
functor = SwitchContainerFunctor
widget = SwitchContainerWidget
- def __init__(self, parent=None, node = None):
+ def __init__(self, parent=None, node=None):
"""Creates a new instance.
:param parent the InputItem this container is linked to
@@ -622,13 +656,22 @@ def __init__(self, parent=None, node = None):
self.timeout = 0.0
self.position_data = {} # data block indexed by position index
- self.position_data[0] = SwitchData(0, self.hardware_device_guid, self.hardware_input_id, SwitchModeType.OnPress)
- self.position_data[1] = SwitchData(1, self.hardware_device_guid, self.hardware_input_id, SwitchModeType.OnRelease)
+ self.position_data[0] = SwitchData(
+ 0, self.hardware_device_guid, self.hardware_input_id, SwitchModeType.OnPress
+ )
+ self.position_data[1] = SwitchData(
+ 1,
+ self.hardware_device_guid,
+ self.hardware_input_id,
+ SwitchModeType.OnRelease,
+ )
self.device_map = {} # device list and buttons keyed by device_id(str)
- self.device_button_map = {}
- devices = sorted(gremlin.joystick_handling.button_input_devices(), key=lambda x: x.name)
-
+ self.device_button_map = {}
+ devices = sorted(
+ gremlin.joystick_handling.button_input_devices(), key=lambda x: x.name
+ )
+
for dev in devices:
self.device_map[dev.device_id] = dev
@@ -636,21 +679,20 @@ def __init__(self, parent=None, node = None):
def position_count(self) -> int:
return len(self.position_data)
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Populates the container with the XML node's contents.
:param node the XML node with which to populate the container
"""
-
+
# get the switch nodes
- switch_nodes = gremlin.util.get_xml_child(node, "switch",True)
+ switch_nodes = gremlin.util.get_xml_child(node, "switch", True)
for child in switch_nodes:
data = SwitchData()
data._parse_xml(child)
self.position_data[data.index] = data
self.action_sets.append([])
-
def _generate_xml(self):
"""Returns an XML node representing this container's data.
@@ -659,9 +701,8 @@ def _generate_xml(self):
"""
node = ElementTree.Element("container")
node.set("type", SwitchContainer.tag)
-
- data : SwitchData
+ data: SwitchData
for data in self.position_data.values():
child = data._generate_xml()
node.append(child)
diff --git a/container_plugins/tempo/__init__.py b/container_plugins/tempo/__init__.py
index 9eb0fed0..99e5a508 100644
--- a/container_plugins/tempo/__init__.py
+++ b/container_plugins/tempo/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -30,8 +30,9 @@
from gremlin.input_types import InputType
syslog = logging.getLogger("system")
-class TempoContainerWidget(AbstractContainerWidget):
+
+class TempoContainerWidget(AbstractContainerWidget):
"""Container with two actions, triggered based on activation duration."""
def __init__(self, profile_data, parent=None):
@@ -49,9 +50,7 @@ def _create_action_ui(self):
self.options_layout = QtWidgets.QHBoxLayout()
# Activation delay
- self.options_layout.addWidget(
- QtWidgets.QLabel("Long press delay: ")
- )
+ self.options_layout.addWidget(QtWidgets.QLabel("Long press delay: "))
self.delay_input = gremlin.ui.ui_common.DynamicDoubleSpinBox()
self.delay_input.setRange(0.1, 2.0)
self.delay_input.setSingleStep(0.1)
@@ -87,7 +86,7 @@ def _create_action_ui(self):
0,
"Short Press",
self.action_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
if self.profile_data.action_sets[1] is None:
@@ -101,7 +100,7 @@ def _create_action_ui(self):
1,
"Long Press",
self.action_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
def _create_condition_ui(self):
@@ -111,7 +110,7 @@ def _create_condition_ui(self):
0,
"Short Press",
self.activation_condition_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
if self.profile_data.action_sets[1] is not None:
@@ -119,7 +118,7 @@ def _create_condition_ui(self):
1,
"Long Press",
self.activation_condition_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
def _add_action_selector(self, add_action_cb, label, paste_action_cb):
@@ -150,9 +149,7 @@ def _create_action_widget(self, index, label, layout, view_type):
:param label the name of the action to create
"""
widget = self._create_action_set_widget(
- self.profile_data.action_sets[index],
- label,
- view_type
+ self.profile_data.action_sets[index], label, view_type
)
layout.addWidget(widget)
widget.redraw()
@@ -172,7 +169,7 @@ def _add_action(self, index, action_name):
self.container_modified.emit()
def _paste_action(self, index, action):
- """paste action into the container """
+ """paste action into the container"""
plugin_manager = gremlin.plugin_manager.ActionPlugins()
action_item = plugin_manager.duplicate(action, self.profile_data)
if self.profile_data.action_sets[index] is None:
@@ -180,7 +177,6 @@ def _paste_action(self, index, action):
self.profile_data.action_sets[index].append(action_item)
self.profile_data.create_or_delete_virtual_button()
self.container_modified.emit()
-
def _delay_changed_cb(self, value):
"""Updates the activation delay value.
@@ -199,7 +195,9 @@ def _activation_changed_cb(self, value):
else:
self.profile_data.activate_on = "release"
- def _handle_interaction(self, widget, action : gremlin.ui.input_item.ActionSetView.Interactions):
+ def _handle_interaction(
+ self, widget, action: gremlin.ui.input_item.ActionSetView.Interactions
+ ):
"""Handles interaction icons being pressed on the individual actions.
:param widget the action widget on which an action was invoked
@@ -218,17 +216,18 @@ def _get_window_title(self):
:return title to use for the container
"""
- if self.profile_data.is_valid() \
- and len(self.profile_data.action_sets) == 2 \
- and None not in self.profile_data.action_sets:
+ if (
+ self.profile_data.is_valid()
+ and len(self.profile_data.action_sets) == 2
+ and None not in self.profile_data.action_sets
+ ):
return f"Tempo: ({", ".join([a.name for a in self.profile_data.action_sets[0]])}) / ({", ".join([a.name for a in self.profile_data.action_sets[1]])})"
else:
return "Tempo"
class TempoContainerFunctor(gremlin.base_conditions.AbstractFunctor):
-
- def __init__(self, container, parent = None):
+ def __init__(self, container, parent=None):
super().__init__(container, parent)
self.short_set = gremlin.execution_graph.ActionSetExecutionGraph(
container.action_sets[0], parent
@@ -254,10 +253,9 @@ def profile_start(self):
self.value_press = None
self.event_press = None
-
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
if event.event_type == InputType.JoystickHat:
- is_pressed = value.current != (0,0)
+ is_pressed = value.current != (0, 0)
elif not isinstance(value.current, bool):
syslog.warning(
f"Invalid data type received in TempoEx container: {type(event.value)}"
@@ -278,29 +276,28 @@ def process_event(self, event, value, extra_data = None):
self.timer.start()
if self.activate_on == "press":
- #print ("tempo short (activate on press)")
+ # print ("tempo short (activate on press)")
self.short_set.process_event(self.event_press, self.value_press)
else:
# Short press
-
+
if (self.start_time + self.delay) > time.time():
if self.timer:
self.timer.cancel()
-
if self.activate_on == "release":
- #print ("tempo short (activate on release)")
- threading.Thread(target=lambda: self._short_press(
- self.event_press,
- self.value_press,
- event,
- value
- ), daemon=True).start()
+ # print ("tempo short (activate on release)")
+ threading.Thread(
+ target=lambda: self._short_press(
+ self.event_press, self.value_press, event, value
+ ),
+ daemon=True,
+ ).start()
else:
self.short_set.process_event(event, value)
# Long press
else:
- #print ("tempo long")
+ # print ("tempo long")
self.long_set.process_event(event, value)
if self.activate_on == "press":
self.short_set.process_event(event, value)
@@ -327,7 +324,6 @@ def _long_press(self):
class TempoContainer(AbstractContainer):
-
"""A container with two actions which are triggered based on the duration
of the activation.
@@ -351,7 +347,7 @@ class TempoContainer(AbstractContainer):
gremlin.ui.input_item.ActionSetView.Interactions.Delete,
]
- def __init__(self, parent=None, node = None):
+ def __init__(self, parent=None, node=None):
"""Creates a new instance.
:param parent the InputItem this container is linked to
@@ -361,7 +357,7 @@ def __init__(self, parent=None, node = None):
self.delay = 0.5
self.activate_on = "release"
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Populates the container with the XML node's contents.
:param node the XML node with which to populate the container
@@ -392,9 +388,7 @@ def _is_container_valid(self):
:return True if the container is configured properly, False otherwise
"""
- return True # len(self.action_sets) == 2 and None not in self.action_sets
-
-
+ return True # len(self.action_sets) == 2 and None not in self.action_sets
# Plugin definitions
diff --git a/container_plugins/tempoEx/__init__.py b/container_plugins/tempoEx/__init__.py
index d7a9bd86..dee8e8de 100644
--- a/container_plugins/tempoEx/__init__.py
+++ b/container_plugins/tempoEx/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -25,7 +25,7 @@
from PySide6.QtCore import Slot
import gremlin.base_conditions
-from gremlin.clipboard import Clipboard
+from gremlin.clipboard import Clipboard
import gremlin
import gremlin.base_classes
import gremlin.plugin_manager
@@ -38,8 +38,9 @@
from gremlin.input_types import InputType
syslog = logging.getLogger("system")
-class TempoExContainerWidget(AbstractContainerWidget):
+
+class TempoExContainerWidget(AbstractContainerWidget):
"""Container with two actions, triggered based on activation duration."""
def __init__(self, profile_data, parent=None):
@@ -49,8 +50,6 @@ def __init__(self, profile_data, parent=None):
:param parent the parent of this widget
"""
super().__init__(profile_data, parent)
-
-
def _create_action_ui(self):
"""Creates the UI components."""
@@ -65,11 +64,8 @@ def _create_action_ui(self):
self.long_group_widget.setLayout(self.long_layout)
self.options_layout = QtWidgets.QHBoxLayout()
-
# Activation delay
- self.options_layout.addWidget(
- QtWidgets.QLabel("Long press delay: ")
- )
+ self.options_layout.addWidget(QtWidgets.QLabel("Long press delay: "))
self.delay_input = gremlin.ui.ui_common.DynamicDoubleSpinBox()
self.delay_input.setRange(0.1, 2.0)
self.delay_input.setSingleStep(0.1)
@@ -79,19 +75,15 @@ def _create_action_ui(self):
self.options_layout.addWidget(self.delay_input)
self.options_layout.addStretch()
-
-
# Activation moment
self.options_layout.addWidget(QtWidgets.QLabel("Activate on: "))
self.activate_press = QtWidgets.QRadioButton("on press")
self.activate_release = QtWidgets.QRadioButton("on release")
-
if self.profile_data.activate_on == "press":
self.activate_press.setChecked(True)
else:
- self.activate_release.setChecked(True)
-
+ self.activate_release.setChecked(True)
self.activate_press.toggled.connect(self._activation_changed_cb)
self.activate_release.toggled.connect(self._activation_changed_cb)
@@ -104,7 +96,6 @@ def _create_action_ui(self):
self.chain_short_widget = QtWidgets.QCheckBox("short actions")
self.chain_long_widget = QtWidgets.QCheckBox("long actions")
-
if self.profile_data.chain_short:
self.chain_short_widget.setChecked(True)
@@ -117,7 +108,6 @@ def _create_action_ui(self):
self.options_layout.addWidget(self.chain_short_widget)
self.options_layout.addWidget(self.chain_long_widget)
-
# chain timeout
self.options_layout.addWidget(QtWidgets.QLabel("Chain Timeout: "))
self.timeout_input = gremlin.ui.ui_common.DynamicDoubleSpinBox()
@@ -128,32 +118,24 @@ def _create_action_ui(self):
self.timeout_input.valueChanged.connect(self._timeout_changed_cb)
self.options_layout.addWidget(self.timeout_input)
-
self.action_layout.addLayout(self.options_layout)
self.action_layout.addWidget(self.short_group_widget)
self.action_layout.addWidget(self.long_group_widget)
# self.action_layout.addLayout(self.short_layout)
# self.action_layout.addLayout(self.long_layout)
-
-
self.short_action_selector = gremlin.ui.ui_common.ActionSelector(
- self.profile_data.get_input_type(),
- self.profile_data
+ self.profile_data.get_input_type(), self.profile_data
)
self.short_action_selector.action_label.setText("Short Action")
-
self.long_action_selector = gremlin.ui.ui_common.ActionSelector(
- self.profile_data.get_input_type(),
- self.profile_data
+ self.profile_data.get_input_type(), self.profile_data
)
self.long_action_selector.action_label.setText("Long Action")
-
- self.short_layout.addWidget(self.short_action_selector)
- self.long_layout.addWidget(self.long_action_selector)
-
+ self.short_layout.addWidget(self.short_action_selector)
+ self.long_layout.addWidget(self.long_action_selector)
self.short_action_selector.action_added.connect(self._add_short_action)
self.short_action_selector.action_paste.connect(self._paste_short_action)
@@ -164,13 +146,12 @@ def _create_action_ui(self):
self.short_layout_widget_list = []
self.long_layout_widget_list = []
-
# create short press container actions
for i, action_set in enumerate(self.profile_data.short_action_sets):
widget = self._create_action_set_widget(
action_set if action_set is not None else [],
f"Chain Short Action {i+1:d}",
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
self.short_layout.addWidget(widget)
self.short_layout_widget_list.append(widget)
@@ -182,19 +163,13 @@ def _create_action_ui(self):
widget = self._create_action_set_widget(
action_set if action_set is not None else [],
f"Chain Long Action {i:d}",
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
self.long_layout.addWidget(widget)
self.long_layout_widget_list.append(widget)
widget.redraw()
widget.model.data_changed.connect(self.container_modified.emit)
-
-
-
-
-
-
def _create_condition_ui(self):
if self.profile_data.action_sets:
if self.profile_data.short_action_sets:
@@ -204,7 +179,7 @@ def _create_condition_ui(self):
action_set,
"Short Press",
self.activation_condition_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
if self.profile_data.long_action_sets:
@@ -214,7 +189,7 @@ def _create_condition_ui(self):
action_set,
"Long Press",
self.activation_condition_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
def _create_action_widget(self, action_set, label, layout, view_type):
@@ -223,11 +198,7 @@ def _create_action_widget(self, action_set, label, layout, view_type):
:param index the index at which to store the created action
:param label the name of the action to create
"""
- widget = self._create_action_set_widget(
- action_set,
- label,
- view_type
- )
+ widget = self._create_action_set_widget(action_set, label, view_type)
layout.addWidget(widget)
widget.redraw()
widget.model.data_changed.connect(self.container_modified.emit)
@@ -241,16 +212,16 @@ def _add_short_action(self, action_name):
action_item = plugin_manager.get_class(action_name)(self.profile_data)
self.profile_data.short_action_sets.append([action_item])
self.profile_data.create_or_delete_virtual_button()
- self.container_modified.emit()
+ self.container_modified.emit()
def _paste_short_action(self, action, container):
- ''' called when a paste occurs '''
+ """called when a paste occurs"""
syslog.info("Paste short action")
plugin_manager = gremlin.plugin_manager.ActionPlugins()
action_item = plugin_manager.duplicate(action, self.profile_data)
self.profile_data.short_action_sets.append([action_item])
self.profile_data.create_or_delete_virtual_button()
- self.container_modified.emit()
+ self.container_modified.emit()
def _add_long_action(self, action_name):
"""Adds a new action to the long action list
@@ -261,16 +232,16 @@ def _add_long_action(self, action_name):
action_item = plugin_manager.get_class(action_name)(self.profile_data)
self.profile_data.long_action_sets.append([action_item])
self.profile_data.create_or_delete_virtual_button()
- self.container_modified.emit()
-
+ self.container_modified.emit()
+
def _paste_long_action(self, action, container):
- ''' called when a paste occurs '''
+ """called when a paste occurs"""
syslog.info("Paste long action")
plugin_manager = gremlin.plugin_manager.ActionPlugins()
action_item = plugin_manager.duplicate(action, self.profile_data)
self.profile_data.long_action_sets.append([action_item])
self.profile_data.create_or_delete_virtual_button()
- self.container_modified.emit()
+ self.container_modified.emit()
def _delay_changed_cb(self, value):
"""Updates the activation delay value.
@@ -290,12 +261,11 @@ def _activation_changed_cb(self, value):
self.profile_data.activate_on = "release"
def _chain_short_changed_cb(self, value):
- ''' occurs when short chain checkbox is changed '''
+ """occurs when short chain checkbox is changed"""
self.profile_data.chain_short = self.chain_short_widget.isChecked()
-
def _chain_long_changed_cb(self, value):
- ''' occurs when short chain checkbox is changed '''
+ """occurs when short chain checkbox is changed"""
self.profile_data.chain_long = self.chain_long_widget.isChecked()
def _timeout_changed_cb(self, value):
@@ -303,9 +273,7 @@ def _timeout_changed_cb(self, value):
:param value the new value of the timeout field
"""
- self.profile_data.timeout = value
-
-
+ self.profile_data.timeout = value
def _find_widget(self, widget):
"""Returns the short or long action set and its index of the provided widget as a pair (action_set, index) or (None, -1) if not found
@@ -313,7 +281,7 @@ def _find_widget(self, widget):
:param widget the widget for which to return the index
:return the index of the provided widget, -1 if not present
"""
-
+
if widget in self.short_layout_widget_list:
data = self.short_layout_widget_list
action_sets = self.profile_data.short_action_sets
@@ -322,15 +290,12 @@ def _find_widget(self, widget):
action_sets = self.profile_data.long_action_sets
else:
return (None, -1)
-
-
+
for i, entry in enumerate(data):
if entry == widget:
- return (action_sets,i)
-
- return (None, -1)
-
+ return (action_sets, i)
+ return (None, -1)
def _handle_interaction(self, widget, action):
"""Handles interaction icons being pressed on the individual actions.
@@ -342,15 +307,20 @@ def _handle_interaction(self, widget, action):
# determine which widget this is
action_sets, index = self._find_widget(widget)
if index != -1:
-
- if action == gremlin.ui.input_item.ActionSetView.Interactions.Edit:
+ if action == gremlin.ui.input_item.ActionSetView.Interactions.Edit:
action_sets[index] = []
- elif action == gremlin.ui.input_item.ActionSetView.Interactions.Up:
+ elif action == gremlin.ui.input_item.ActionSetView.Interactions.Up:
if index > 0:
- action_sets[index], action_sets[index-1] = action_sets[index-1], action_sets[index]
- elif action == gremlin.ui.input_item.ActionSetView.Interactions.Down:
+ action_sets[index], action_sets[index - 1] = (
+ action_sets[index - 1],
+ action_sets[index],
+ )
+ elif action == gremlin.ui.input_item.ActionSetView.Interactions.Down:
if index < len(action_sets) - 1:
- action_sets[index], action_sets[index + 1] = action_sets[index + 1], action_sets[index]
+ action_sets[index], action_sets[index + 1] = (
+ action_sets[index + 1],
+ action_sets[index],
+ )
self.container_modified.emit()
@@ -359,18 +329,20 @@ def _get_window_title(self):
:return title to use for the container
"""
- if self.profile_data.is_valid() \
- and len(self.profile_data.action_sets) == 2 \
- and None not in self.profile_data.action_sets:
+ if (
+ self.profile_data.is_valid()
+ and len(self.profile_data.action_sets) == 2
+ and None not in self.profile_data.action_sets
+ ):
return f"TempoEx: ({", ".join([a.name for a in self.profile_data.action_sets[0]])}) / ({", ".join([a.name for a in self.profile_data.action_sets[1]])})"
else:
return "TempoEx"
-class TempoExContainerFunctor(gremlin.base_conditions.AbstractFunctor):
- def __init__(self, container, parent = None):
+class TempoExContainerFunctor(gremlin.base_conditions.AbstractFunctor):
+ def __init__(self, container, parent=None):
super().__init__(container, parent)
- self.action_sets = [[],[]]
+ self.action_sets = [[], []]
for action_set in container.short_action_sets:
self.action_sets[0].append(
gremlin.execution_graph.ActionSetExecutionGraph(action_set, parent)
@@ -378,10 +350,10 @@ def __init__(self, container, parent = None):
for action_set in container.long_action_sets:
self.action_sets[1].append(
gremlin.execution_graph.ActionSetExecutionGraph(action_set, parent)
- )
-
+ )
+
self.short_set = self.action_sets[0]
- self.long_set = self.action_sets[1]
+ self.long_set = self.action_sets[1]
self.delay = container.delay
self.activate_on = container.activate_on
@@ -389,8 +361,8 @@ def __init__(self, container, parent = None):
self.timer = None
self.value_press = None
self.event_press = None
- self.chain_short = True # chain by default
- self.chain_long = True # chain by default
+ self.chain_short = True # chain by default
+ self.chain_long = True # chain by default
self.short_index = 0
self.long_index = 0
self.last_short_execution = 0.0
@@ -407,9 +379,7 @@ def __init__(self, container, parent = None):
for cond in container.activation_condition.conditions:
if isinstance(cond, gremlin.base_conditions.InputActionCondition):
if cond.comparison == "press":
- self.switch_on_press = True
-
-
+ self.switch_on_press = True
def profile_start(self):
# reset any prior values before start
@@ -417,18 +387,16 @@ def profile_start(self):
self.timer = None
self.value_press = None
self.event_press = None
- self.chain_short = True # chain by default
- self.chain_long = True # chain by default
+ self.chain_short = True # chain by default
+ self.chain_long = True # chain by default
self.short_index = 0
self.long_index = 0
self.last_short_execution = 0.0
self.last_long_execution = 0.0
self.last_short_value = None
-
-
def _trigger_short_press(self, event, value):
- ''' triggers a short press '''
+ """triggers a short press"""
if self.short_timeout > 0.0:
if self.last_short_execution + self.short_timeout < time.time():
@@ -438,17 +406,21 @@ def _trigger_short_press(self, event, value):
if self.short_index < len(self.short_set):
# syslog.info(f"execute short press {self.short_index}")
- if self.short_index == 1:
+ if self.short_index == 1:
pass
self.short_set[self.short_index].process_event(event, value)
- if self.chain_short and (self.switch_on_press and value.current) or not value.current:
+ if (
+ self.chain_short
+ and (self.switch_on_press and value.current)
+ or not value.current
+ ):
# bump short index if chaining
self.short_index = (self.short_index + 1) % len(self.short_set)
# syslog.info(f"bump short index {self.short_index}")
def _trigger_long_press(self, event, value):
- ''' triggers a long press '''
+ """triggers a long press"""
if self.long_timeout > 0.0:
if self.last_long_execution + self.long_timeout < time.time():
@@ -460,16 +432,20 @@ def _trigger_long_press(self, event, value):
# syslog.info(f"execute long press {self.long_index}")
self.long_set[self.long_index].process_event(event, value)
- if self.chain_long and (self.switch_on_press and value.current) or not value.current:
+ if (
+ self.chain_long
+ and (self.switch_on_press and value.current)
+ or not value.current
+ ):
# bump long index if chaining
self.long_index = (self.long_index + 1) % len(self.long_set)
- # syslog.info(f"bump long index {self.long_index}")
+ # syslog.info(f"bump long index {self.long_index}")
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
# TODO: Currently this does not handle hat or axis events, however
# virtual buttons created on those inputs is supported
if event.event_type == InputType.JoystickHat:
- is_pressed = value.current != (0,0)
+ is_pressed = value.current != (0, 0)
elif not isinstance(value.current, bool):
syslog.warning(
f"Invalid data type received in TempoEx container: {type(event.value)}"
@@ -498,19 +474,22 @@ def process_event(self, event, value, extra_data = None):
# raw button was released
# Short press (activate on button release)
if (self.start_time + self.delay) > time.time():
- self.timer.cancel() # kill long press timer - use short press
+ self.timer.cancel() # kill long press timer - use short press
if self.activate_on == "release":
- threading.Thread(target=lambda: self._short_press(
- self.short_index,
- self.event_press,
- self.value_press,
- event,
- value
- ), daemon=True).start()
+ threading.Thread(
+ target=lambda: self._short_press(
+ self.short_index,
+ self.event_press,
+ self.value_press,
+ event,
+ value,
+ ),
+ daemon=True,
+ ).start()
else:
self._trigger_short_press(event, value)
-
+
else:
# Long press
self._trigger_long_press(event, value)
@@ -518,7 +497,6 @@ def process_event(self, event, value, extra_data = None):
# syslog.info(f"execute short press (activation mode = press) in LONG PRESS")
self._trigger_short_press(event, value)
-
self.timer = None
return True
@@ -536,14 +514,13 @@ def _short_press(self, index, event_p, value_p, event_r, value_r):
time.sleep(0.05)
self._trigger_short_press(event_r, value_r)
-
def _long_press(self):
"""Callback executed, when the delay expires."""
self._trigger_long_press(self.event_press, self.value_press)
-class TempoExContainer(AbstractContainer):
+class TempoExContainer(AbstractContainer):
"""A container with two actions which are triggered based on the duration
of the activation.
@@ -565,10 +542,10 @@ class TempoExContainer(AbstractContainer):
# gremlin.ui.input_item.ActionSetView.Interactions.Up,
# gremlin.ui.input_item.ActionSetView.Interactions.Down,
# gremlin.ui.input_item.ActionSetView.Interactions.Edit,
-
+
# ]
- def __init__(self, parent=None, node = None):
+ def __init__(self, parent=None, node=None):
"""Creates a new instance.
:param parent the InputItem this container is linked to
@@ -581,24 +558,24 @@ def __init__(self, parent=None, node = None):
self.timeout = 0.0
self.chain_short = True
self.chain_long = True
- self.custom_action_sets = True # indicate we use custom action sets
+ self.custom_action_sets = True # indicate we use custom action sets
@property
def action_sets(self):
- ''' gets the action sets for this container '''
+ """gets the action sets for this container"""
return self.short_action_sets + self.long_action_sets
+
@action_sets.setter
def action_sets(self, value):
pass
-
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Populates the container with the XML node's contents.
:param node the XML node with which to populate the container
"""
# setup a noop action set as the only action set as we have a custom set we use
-
+
self.short_action_sets = []
self.long_action_sets = []
super()._parse_xml(node, data)
@@ -620,7 +597,6 @@ def _parse_xml(self, node, data = None):
self.long_action_sets.append(action_set)
self.action_sets.append(action_set)
-
def _generate_xml(self):
"""Returns an XML node representing this container's data.
@@ -630,8 +606,8 @@ def _generate_xml(self):
node.set("type", TempoExContainer.tag)
node.set("delay", str(self.delay))
node.set("activate-on", self.activate_on)
- node.set("chain_short",safe_format(self.chain_short, bool))
- node.set("chain_long",safe_format(self.chain_long, bool))
+ node.set("chain_short", safe_format(self.chain_short, bool))
+ node.set("chain_long", safe_format(self.chain_long, bool))
node.set("timeout", str(self.timeout))
for actions in self.short_action_sets:
as_node = ElementTree.Element("short-action-set")
@@ -645,7 +621,7 @@ def _generate_xml(self):
node.append(as_node)
return node
-
+
def is_valid_for_save(self):
# indicate always valid for saving
return True
@@ -655,11 +631,11 @@ def _is_container_valid(self):
:return True if the container is configured properly, False otherwise
"""
- valid = len(self.short_action_sets) > 0 or len(self.long_action_sets) > 0
+ valid = len(self.short_action_sets) > 0 or len(self.long_action_sets) > 0
return valid
-
+
def get_action_sets(self):
- """ override method: returns action sets - override because we have custom sets """
+ """override method: returns action sets - override because we have custom sets"""
return self.short_action_sets + self.long_action_sets
diff --git a/container_plugins/tick/__init__.py b/container_plugins/tick/__init__.py
index 65f5f5b7..5d684827 100644
--- a/container_plugins/tick/__init__.py
+++ b/container_plugins/tick/__init__.py
@@ -39,17 +39,17 @@
from gremlin.input_types import InputType
from gremlin.util import safe_format, safe_read
-class TickContainerWidget(AbstractContainerWidget):
+class TickContainerWidget(AbstractContainerWidget):
"""Container with two actions, one for input button is pressed, the other for when the input button is released
-
- While this can be duplicated with conditions - this is a helper container to simplify the profile setup.
- Works with buttons or hats
-
+ While this can be duplicated with conditions - this is a helper container to simplify the profile setup.
+
+ Works with buttons or hats
+
"""
- def __init__(self, action_data : TickContainer, parent=None):
+ def __init__(self, action_data: TickContainer, parent=None):
"""Creates a new instance.
:param profile_data the profile data represented by this widget
@@ -63,7 +63,7 @@ def _create_action_ui(self):
el = gremlin.event_handler.EventListener()
el.joystick_event.connect(self._joystick_event_handler)
- self.action_data : TickContainer = self.profile_data
+ self.action_data: TickContainer = self.profile_data
self.action_data.create_or_delete_virtual_button()
self.interval_widget = gremlin.ui.ui_common.QFloatLineEdit()
@@ -72,12 +72,12 @@ def _create_action_ui(self):
self.interval_widget.valueChanged.connect(self._interval_changed)
self.tick_count_widget = gremlin.ui.ui_common.QIntLineEdit()
- self.tick_count_widget.setRange(0,100)
+ self.tick_count_widget.setRange(0, 100)
self.tick_count_widget.setValue(self.action_data.getTickCount())
self.tick_count_widget.valueChanged.connect(self._tick_count_changed)
self.slider_widget = gremlin.ui.qsliderwidget.QSliderWidget()
- self.slider_widget.setRange(-1,1)
+ self.slider_widget.setRange(-1, 1)
self.slider_widget.setReadOnly(True)
self.slider_widget.setDrawHandles(False)
@@ -86,19 +86,15 @@ def _create_action_ui(self):
self.header_container = QtWidgets.QWidget()
self.header_layout = QtWidgets.QHBoxLayout(self.header_container)
- self.header_layout.addWidget(QtWidgets.QLabel("Interval:"))
+ self.header_layout.addWidget(QtWidgets.QLabel("Interval:"))
self.header_layout.addWidget(self.interval_widget)
- self.header_layout.addWidget(QtWidgets.QLabel("Tick Count:"))
+ self.header_layout.addWidget(QtWidgets.QLabel("Tick Count:"))
self.header_layout.addWidget(self.tick_count_widget)
self.header_layout.addWidget(self.slider_widget)
self.header_layout.addStretch()
-
-
-
self.options_layout = QtWidgets.QHBoxLayout()
-
self.action_layout.addWidget(self.header_container)
self.action_layout.addLayout(self.options_layout)
@@ -113,7 +109,7 @@ def _create_action_ui(self):
0,
"Tick Up",
self.action_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
if self.profile_data.action_sets[1] is None:
@@ -127,33 +123,30 @@ def _create_action_ui(self):
1,
"Tick Down",
self.action_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Action
+ gremlin.ui.ui_common.ContainerViewTypes.Action,
)
def _joystick_event_handler(self, event):
- ''' handles joystick events in the UI (functor handles the output when profile is running) so we see the output at design time '''
+ """handles joystick events in the UI (functor handles the output when profile is running) so we see the output at design time"""
if gremlin.shared_state.is_running:
- return
+ return
if not event.is_axis:
- return
-
+ return
+
value = None
-
+
if event.device_guid != self.action_data.hardware_device_guid:
return
if event.identifier != self.action_data.hardware_input_id:
return
-
+
value = event.value
-
-
- self._update_axis_widget(value)
-
- def _update_axis_widget(self, value : float = None):
+ self._update_axis_widget(value)
+
+ def _update_axis_widget(self, value: float = None):
self.slider_widget.setMarkerValue(value)
-
@QtCore.Slot()
def _interval_changed(self):
@@ -177,7 +170,6 @@ def _tick_count_changed(self):
self.slider_widget.setTickCount(count)
self.update()
-
def _create_condition_ui(self):
if self.profile_data.action_sets:
if self.profile_data.action_sets[0] is not None:
@@ -185,7 +177,7 @@ def _create_condition_ui(self):
0,
"Axis Increase",
self.activation_condition_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
if self.profile_data.action_sets[1] is not None:
@@ -193,7 +185,7 @@ def _create_condition_ui(self):
1,
"Axis Decrease",
self.activation_condition_layout,
- gremlin.ui.ui_common.ContainerViewTypes.Conditions
+ gremlin.ui.ui_common.ContainerViewTypes.Conditions,
)
def _add_action_selector(self, add_action_cb, label, paste_action_cb):
@@ -224,9 +216,7 @@ def _create_action_widget(self, index, label, layout, view_type):
:param label the name of the action to create
"""
widget = self._create_action_set_widget(
- self.profile_data.action_sets[index],
- label,
- view_type
+ self.profile_data.action_sets[index], label, view_type
)
layout.addWidget(widget)
widget.redraw()
@@ -246,7 +236,7 @@ def _add_action(self, index, action_name):
self.container_modified.emit()
def _paste_action(self, index, action):
- ''' paste action'''
+ """paste action"""
plugin_manager = gremlin.plugin_manager.ActionPlugins()
action_item = plugin_manager.duplicate(action, self.profile_data)
if self.profile_data.action_sets[index] is None:
@@ -254,8 +244,6 @@ def _paste_action(self, index, action):
self.profile_data.action_sets[index].append(action_item)
self.profile_data.create_or_delete_virtual_button()
-
-
def _handle_interaction(self, widget, action):
"""Handles interaction icons being pressed on the individual actions.
@@ -281,8 +269,7 @@ def _get_window_title(self):
class TickContainerFunctor(gremlin.base_conditions.AbstractFunctor):
-
- def __init__(self, container : TickContainer, parent = None):
+ def __init__(self, container: TickContainer, parent=None):
super().__init__(container, parent)
self.increase_set = gremlin.execution_graph.ActionSetExecutionGraph(
container.action_sets[0], parent
@@ -297,20 +284,19 @@ def profile_start(self):
# current position
count = self.action_data.getTickCount()
- interval = 2 / (count-1)
+ interval = 2 / (count - 1)
self.last_tick = self.action_data.currentTick()
# build the ranges for each tick
self._tick_map = [-1.0 + x * interval for x in range(count)]
self._last_value = self.action_data._get_value()
-
- def process_event(self, event, value, extra_data = None):
-
+
+ def process_event(self, event, value, extra_data=None):
if not event.is_axis:
return
-
+
last_tick = self.last_tick
-
+
value = event.value
last_value = self._last_value
index = 0
@@ -322,41 +308,37 @@ def process_event(self, event, value, extra_data = None):
elif value == v:
trigger = True
break
- index +=1
+ index += 1
tick = index
trigger = trigger or last_tick != tick
-
if trigger:
- #print (f"Value: {value:0.3f} last: {last_value:0.3f} index: {tick} last tick: {last_tick} map: {self._tick_map} trigger: {trigger}")
-
+ # print (f"Value: {value:0.3f} last: {last_value:0.3f} index: {tick} last tick: {last_tick} map: {self._tick_map} trigger: {trigger}")
+
trigger_count = abs(last_tick - tick)
event.is_axis = False
event.is_button = True
- event.is_pressed = True # tell each tick we're pressed
- if last_value < value:
- # going up
- #print (f"increase set trigger value: {value:0.3f} tick: {tick} last tick: {last_tick} count: {trigger_count}")
+ event.is_pressed = True # tell each tick we're pressed
+ if last_value < value:
+ # going up
+ # print (f"increase set trigger value: {value:0.3f} tick: {tick} last tick: {last_tick} count: {trigger_count}")
for _ in range(trigger_count):
-
self.increase_set.process_event(event, value)
elif last_value > value:
# going down
- #print (f"decrease set trigger value: {value:0.3f} tick: {tick} count: {trigger_count}")
+ # print (f"decrease set trigger value: {value:0.3f} tick: {tick} count: {trigger_count}")
for _ in range(trigger_count):
self.decrease_set.process_event(event, value)
self.last_tick = tick
-
self._last_value = value
-
+
return True
class TickContainer(AbstractContainer):
-
"""A container with two actions which are triggered based on the duration
of the activation.
@@ -376,31 +358,32 @@ class TickContainer(AbstractContainer):
gremlin.ui.input_item.ActionSetView.Interactions.Edit,
]
- def getTick(self, value : float) -> int:
- ''' gets the tick number for a given value -1 to +1 '''
- value += 1 # range 0..2
+ def getTick(self, value: float) -> int:
+ """gets the tick number for a given value -1 to +1"""
+ value += 1 # range 0..2
tick = int(2 / (value + 1))
- print (f"value: {value:0.3f} tick: {tick}")
+ print(f"value: {value:0.3f} tick: {tick}")
return tick
def _get_value(self) -> float:
- ''' current axis value '''
- return gremlin.joystick_handling.get_axis(self.hardware_device_guid, self.hardware_input_id)
-
+ """current axis value"""
+ return gremlin.joystick_handling.get_axis(
+ self.hardware_device_guid, self.hardware_input_id
+ )
+
def currentTick(self) -> int:
value = self._get_value()
return self.getTick(value)
-
+
def getTickCount(self) -> int:
- ''' gets the number of ticks in the container '''
+ """gets the number of ticks in the container"""
if self.interval == 0:
return 2
- count = round(2/self.interval)+1
- print (f"New count: {count} interval: {self.interval} {2/self.interval:0.3f}")
+ count = round(2 / self.interval) + 1
+ print(f"New count: {count} interval: {self.interval} {2/self.interval:0.3f}")
return count
-
- def __init__(self, parent=None, node = None):
+ def __init__(self, parent=None, node=None):
"""Creates a new instance.
:param parent the InputItem this container is linked to
@@ -409,19 +392,19 @@ def __init__(self, parent=None, node = None):
self.action_sets = [[], []]
self.delay = 0.5
self.activate_on = "release"
- self.interval = 0.2 # interval between ticks
+ self.interval = 0.2 # interval between ticks
# override the input type to a button
self.override_input_id = 1
self.override_input_type = InputType.JoystickButton
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Populates the container with the XML node's contents.
:param node the XML node with which to populate the container
"""
self.action_sets = []
- self.interval = safe_read(node,"interval",float,0.2)
+ self.interval = safe_read(node, "interval", float, 0.2)
super()._parse_xml(node, data)
def _generate_xml(self):
@@ -444,7 +427,7 @@ def _is_container_valid(self):
:return True if the container is configured properly, False otherwise
"""
- return True # len(self.action_sets) == 2 and None not in self.action_sets
+ return True # len(self.action_sets) == 2 and None not in self.action_sets
# Plugin definitions
diff --git a/dinput/__init__.py b/dinput/__init__.py
index b43767b1..1a158072 100644
--- a/dinput/__init__.py
+++ b/dinput/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -29,8 +29,8 @@
syslog = logging.getLogger("system")
-class DILLError(Exception):
+class DILLError(Exception):
"""Exception raised when an error occurs within the DILL module."""
def __init__(self, value):
@@ -38,14 +38,13 @@ def __init__(self, value):
class _GUID(ctypes.Structure):
-
"""Strcture mapping C information into a set of Python readable values."""
_fields_ = [
("Data1", ctypes.c_ulong),
("Data2", ctypes.c_ushort),
("Data3", ctypes.c_ushort),
- ("Data4", ctypes.c_uint8 * 8)
+ ("Data4", ctypes.c_uint8 * 8),
]
@@ -63,16 +62,16 @@ class _GUID(ctypes.Structure):
_GUID_SysKeyboard.Data4[7] = 0x00
_GUID_Virtual = _GUID()
-_GUID_Virtual.Data1 = 0x89d5e905
-_GUID_Virtual.Data2 = 0x1e26
-_GUID_Virtual.Data3 = 0x4c52
-_GUID_Virtual.Data4[0] = 0xad
+_GUID_Virtual.Data1 = 0x89D5E905
+_GUID_Virtual.Data2 = 0x1E26
+_GUID_Virtual.Data3 = 0x4C52
+_GUID_Virtual.Data4[0] = 0xAD
_GUID_Virtual.Data4[1] = 0x46
-_GUID_Virtual.Data4[2] = 0x7b
-_GUID_Virtual.Data4[3] = 0xcc
+_GUID_Virtual.Data4[2] = 0x7B
+_GUID_Virtual.Data4[3] = 0xCC
_GUID_Virtual.Data4[4] = 0x06
-_GUID_Virtual.Data4[5] = 0xdf
-_GUID_Virtual.Data4[6] = 0x4c
+_GUID_Virtual.Data4[5] = 0xDF
+_GUID_Virtual.Data4[6] = 0x4C
_GUID_Virtual.Data4[7] = 0x20
_GUID_Invalid = _GUID()
@@ -90,29 +89,23 @@ class _GUID(ctypes.Structure):
class _JoystickInputData(ctypes.Structure):
-
"""Mapping for the JoystickInputData C structure."""
_fields_ = [
("device_guid", _GUID),
("input_type", ctypes.c_uint8),
("input_index", ctypes.c_uint8),
- ("value", ctwt.LONG)
+ ("value", ctwt.LONG),
]
class _AxisMap(ctypes.Structure):
-
"""Mapping for the AxisMap C structure."""
- _fields_ = [
- ("linear_index", ctwt.DWORD),
- ("axis_index", ctwt.DWORD)
- ]
+ _fields_ = [("linear_index", ctwt.DWORD), ("axis_index", ctwt.DWORD)]
class _DeviceSummary(ctypes.Structure):
-
"""Mapping for the DeviceSummary C structure."""
_fields_ = [
@@ -126,12 +119,11 @@ class _DeviceSummary(ctypes.Structure):
("hat_count", ctwt.DWORD),
("axis_map", _AxisMap * 8),
("usage_page", ctwt.WORD),
- ("usage", ctwt.WORD)
+ ("usage", ctwt.WORD),
]
class GUID:
-
"""Python GUID class."""
def __init__(self, guid):
@@ -153,20 +145,24 @@ def __init__(self, guid):
guid.Data2,
guid.Data3,
(guid.Data4[0] << 8) + guid.Data4[1],
- (guid.Data4[2] << 40) + (guid.Data4[3] << 32) +
- (guid.Data4[4] << 24) + (guid.Data4[5] << 16) +
- (guid.Data4[6] << 8) + guid.Data4[7]
+ (guid.Data4[2] << 40)
+ + (guid.Data4[3] << 32)
+ + (guid.Data4[4] << 24)
+ + (guid.Data4[5] << 16)
+ + (guid.Data4[6] << 8)
+ + guid.Data4[7],
)
-
@property
def valid(self):
- ''' true if the GUID is valid '''
- return not (self._ctypes_guid.Data1 == 0 and \
- self._ctypes_guid.Data2 == 0 and \
- self._ctypes_guid.Data3 == 0 and \
- self._ctypes_guid.Data4 == 0)
-
+ """true if the GUID is valid"""
+ return not (
+ self._ctypes_guid.Data1 == 0
+ and self._ctypes_guid.Data2 == 0
+ and self._ctypes_guid.Data3 == 0
+ and self._ctypes_guid.Data4 == 0
+ )
+
@property
def ctypes(self):
"""Returns the object mapping the C structure.
@@ -226,19 +222,21 @@ def __hash__(self):
int
The has computed from this GUID
"""
- return hash((
- self._ctypes_guid.Data1,
- self._ctypes_guid.Data2,
- self._ctypes_guid.Data3,
- self._ctypes_guid.Data4[0],
- self._ctypes_guid.Data4[1],
- self._ctypes_guid.Data4[2],
- self._ctypes_guid.Data4[3],
- self._ctypes_guid.Data4[4],
- self._ctypes_guid.Data4[5],
- self._ctypes_guid.Data4[6],
- self._ctypes_guid.Data4[7]
- ))
+ return hash(
+ (
+ self._ctypes_guid.Data1,
+ self._ctypes_guid.Data2,
+ self._ctypes_guid.Data3,
+ self._ctypes_guid.Data4[0],
+ self._ctypes_guid.Data4[1],
+ self._ctypes_guid.Data4[2],
+ self._ctypes_guid.Data4[3],
+ self._ctypes_guid.Data4[4],
+ self._ctypes_guid.Data4[5],
+ self._ctypes_guid.Data4[6],
+ self._ctypes_guid.Data4[7],
+ )
+ )
GUID_Keyboard = GUID(_GUID_SysKeyboard)
@@ -247,11 +245,10 @@ def __hash__(self):
class InputType(Enum):
-
"""Enumeration of valid input types that can be reported."""
- Axis = 1,
- Button = 2,
+ Axis = (1,)
+ Button = (2,)
Hat = 3
@staticmethod
@@ -270,20 +267,19 @@ def from_ctype(value):
"""
from gremlin.util import log_sys_error
-
+
if value == 1:
return InputType.Axis
elif value == 2:
return InputType.Button
elif value == 3:
return InputType.Hat
-
+
log_sys_error(f"Invalid DLL input type value received: {value:d}")
return None
class DeviceActionType(Enum):
-
"""Represents the state change of a device."""
Connected = 1
@@ -312,7 +308,6 @@ def from_ctype(value):
class InputEvent:
-
"""Holds information about a single event.
An event is an axis, button, or hat changing its state. The type of
@@ -328,9 +323,9 @@ def __init__(self, data):
The data received from DILL and to be held by this instance
fix: if the type is not recognized, use a 0 Guid and handle nicely instead of throwing an error
-
+
"""
-
+
input_type = InputType.from_ctype(data.input_type)
if input_type:
self.device_guid = GUID(data.device_guid)
@@ -347,16 +342,14 @@ def __str__(self) -> str:
return f"InputEvent: GUID {self.device_guid} type: {self.input_type} index: {self.input_index} value: {self.value}"
-
class AxisMap:
-
"""Holds information about a single axis map entry.
An AxisMap holds a mapping from an axis' sequential index to the actual
descriptive DirectInput axis index.
"""
- def __init__(self, data = None):
+ def __init__(self, data=None):
"""Creates a new instance.
Parameters
@@ -370,18 +363,16 @@ def __init__(self, data = None):
if data is not None:
self.linear_index = data.linear_index
self.axis_index = data.axis_index
-
-
def getName(self) -> str:
- ''' gets the name of the axis based on its axis index '''
+ """gets the name of the axis based on its axis index"""
axis_index = self.axis_index
match axis_index:
case 0:
return ""
case 1:
return "(1) X"
- case 2:
+ case 2:
return "(2) Y"
case 3:
return "(3) Z"
@@ -395,19 +386,17 @@ def getName(self) -> str:
return "(7) S1"
case 8:
return "(8) S2"
-
+
return f"Invalid: {self.axis_index}"
-
class DeviceSummary:
-
"""Holds information about a single device.
This summary holds static information about a single device's layout.
"""
- def __init__(self, data = None):
+ def __init__(self, data=None):
"""Creates a new instance.
Parameters
@@ -415,7 +404,7 @@ def __init__(self, data = None):
data : _DeviceSummary
The data received from DILL and to be held by this instance
"""
- if data is not None:
+ if data is not None:
self.device_guid = GUID(data.device_guid)
self.device_id = str(self.device_guid)
self.device_type = DeviceType.Joystick
@@ -423,7 +412,7 @@ def __init__(self, data = None):
self.product_id = data.product_id
self.joystick_id = data.joystick_id
name = data.name.decode("utf-8", errors="replace")
- self.name = name.replace('\ufffd','') # remove junk characters
+ self.name = name.replace("\ufffd", "") # remove junk characters
self.axis_count = data.axis_count
self.button_count = data.button_count
self.hat_count = data.hat_count
@@ -432,7 +421,7 @@ def __init__(self, data = None):
self.usage = data.usage
self.axis_names = []
logical_count = 0
- self.is_input_enabled = True # allow usage as an input device
+ self.is_input_enabled = True # allow usage as an input device
for i in range(8):
axis_map = AxisMap(data.axis_map[i])
self.axis_map.append(axis_map)
@@ -440,7 +429,7 @@ def __init__(self, data = None):
if not axis_name:
# axis name is not reporting in via directinput
axis_name = f"({i+1})"
- #axis_name = f"({logical_count}/{i}/{axis_map.linear_index}/{axis_map.axis_index})"
+ # axis_name = f"({logical_count}/{i}/{axis_map.linear_index}/{axis_map.axis_index})"
else:
logical_count += 1
self.axis_names.append(axis_name)
@@ -461,23 +450,22 @@ def __init__(self, data = None):
self.usage = None
self.axis_names = []
logical_count = 0
- self.is_input_enabled = False # do not allow usage as an input device
+ self.is_input_enabled = False # do not allow usage as an input device
self.vjoy_id = -1
self.is_special = False
-
@property
def is_virtual(self):
- """ determins if a device is virtual.
+ """determins if a device is virtual.
Returns
=======
bool
True if the device is a virtual vJoy device, False otherwise
"""
- if self.vendor_id == 0x1234: # and self.product_id == 0xBEAD
+ if self.vendor_id == 0x1234: # and self.product_id == 0xBEAD
return True
- #if self.vendor_id == 0x
+ # if self.vendor_id == 0x
return False
def set_vjoy_id(self, vjoy_id):
@@ -494,10 +482,12 @@ def set_vjoy_id(self, vjoy_id):
"""
assert self.is_virtual is True
self.vjoy_id = vjoy_id
- self.name = f"VJoy {self.axis_count}/{self.button_count}/{self.hat_count} ({vjoy_id:d})"
+ self.name = (
+ f"VJoy {self.axis_count}/{self.button_count}/{self.hat_count} ({vjoy_id:d})"
+ )
def get_axis_name(self, input_id):
- ''' gets the axis name based on the input # '''
+ """gets the axis name based on the input #"""
if input_id == 1:
axis_name = "X"
elif input_id == 2:
@@ -520,25 +510,25 @@ def get_axis_name(self, input_id):
def get_button_name(self, input_id):
return f"Button {input_id}"
-
+
def get_hat_name(self, input_id):
import gremlin.util
+
if isinstance(input_id, int):
data = gremlin.util.hat_index_to_tuple(input_id)
else:
- data = input_id # tuple
+ data = input_id # tuple
return gremlin.util.hat_tuple_to_direction(data)
-
+
def axis_index_list(self) -> list:
- ''' returns the list of valid axis indices '''
+ """returns the list of valid axis indices"""
index_list = [data.axis_index for data in self.axis_map if data.axis_index > 0]
return index_list
-
@property
def hashkey(self):
- ''' gets the hash key for virtual devices '''
- return (self.axis_count,self.button_count,self.hat_count)
+ """gets the hash key for virtual devices"""
+ return (self.axis_count, self.button_count, self.hat_count)
def __str__(self):
vjoy_stub = f"VjoyID: {self.vjoy_id}" if self.vjoy_id != -1 else ""
@@ -549,9 +539,7 @@ def __str__(self):
C_DEVICE_CHANGE_CALLBACK = ctypes.CFUNCTYPE(None, _DeviceSummary, ctypes.c_uint8)
-
class DILL:
-
"""Exposes functions of the DILL library in an easy to use manner."""
# Attempt to find the correct location of the dll for development
@@ -559,9 +547,8 @@ class DILL:
_dll = None
version = None
-
# true if initialized
-
+
initalized = False
# Storage for the callback functions
@@ -571,46 +558,25 @@ class DILL:
# Declare argument and return types for all the functions
# exposed by the dll
api_functions = {
- "init": {
- "arguments": [],
- "returns": None
- },
- "set_input_event_callback": {
- "arguments": [C_EVENT_CALLBACK],
- "returns": None
- },
+ "init": {"arguments": [], "returns": None},
+ "set_input_event_callback": {"arguments": [C_EVENT_CALLBACK], "returns": None},
"set_device_change_callback": {
"arguments": [C_DEVICE_CHANGE_CALLBACK],
- "returns": None
+ "returns": None,
},
"get_device_information_by_index": {
"arguments": [ctypes.c_uint],
- "returns": _DeviceSummary
+ "returns": _DeviceSummary,
},
"get_device_information_by_guid": {
"arguments": [_GUID],
- "returns": _DeviceSummary
- },
- "get_device_count": {
- "arguments": [],
- "returns": ctypes.c_uint
- },
- "device_exists": {
- "arguments": [_GUID],
- "returns": ctypes.c_bool
- },
- "get_axis": {
- "arguments": [_GUID, ctwt.DWORD],
- "returns": ctwt.LONG
- },
- "get_button": {
- "arguments": [_GUID, ctwt.DWORD],
- "returns": ctypes.c_bool
+ "returns": _DeviceSummary,
},
- "get_hat": {
- "arguments": [_GUID, ctwt.DWORD],
- "returns": ctwt.LONG
- }
+ "get_device_count": {"arguments": [], "returns": ctypes.c_uint},
+ "device_exists": {"arguments": [_GUID], "returns": ctypes.c_bool},
+ "get_axis": {"arguments": [_GUID, ctwt.DWORD], "returns": ctwt.LONG},
+ "get_button": {"arguments": [_GUID, ctwt.DWORD], "returns": ctypes.c_bool},
+ "get_hat": {"arguments": [_GUID, ctwt.DWORD], "returns": ctwt.LONG},
}
@staticmethod
@@ -624,12 +590,10 @@ def init():
syslog = logging.getLogger("system")
-
if DILL._dll is None:
-
dll_folder = os.path.dirname(__file__)
dll_file = "dill.dll"
- _dll_path = os.path.join(dll_folder, dll_file )
+ _dll_path = os.path.join(dll_folder, dll_file)
if not os.path.isfile(_dll_path):
# look one level up for packaging in 3.12
parent = Path(dll_folder).parent
@@ -641,7 +605,6 @@ def init():
os._exit(1)
dll_folder = parent
-
# truncate the debug file (it gets large otherwise)
debug_file = "dill_debug.txt"
@@ -656,7 +619,6 @@ def init():
dll_version = get_dll_version(_dll_path)
DILL.version = dll_version
-
try:
_di_listener_dll = ctypes.cdll.LoadLibrary(_dll_path)
@@ -667,8 +629,12 @@ def init():
os._exit(1)
try:
- _di_listener_dll.get_device_information_by_index.argtypes = [ctypes.c_uint]
- _di_listener_dll.get_device_information_by_index.restype = _DeviceSummary
+ _di_listener_dll.get_device_information_by_index.argtypes = [
+ ctypes.c_uint
+ ]
+ _di_listener_dll.get_device_information_by_index.restype = (
+ _DeviceSummary
+ )
DILL._dll = _di_listener_dll
DILL._dll.init()
except Exception as error:
@@ -679,12 +645,9 @@ def init():
DILL.initalized = True
-
-
-
@staticmethod
def dumpDevices():
- ''' reload devices if count was 0 '''
+ """reload devices if count was 0"""
syslog = logging.getLogger("system")
device_count = DILL.get_device_count()
syslog.info("DILL: device detection summary")
@@ -693,7 +656,6 @@ def dumpDevices():
syslog.info(f"\tIndex: [{index}] {str(dev)}")
syslog.info("DILL: end device detection summary")
-
@staticmethod
def set_input_event_callback(callback):
"""Sets the callback function to use for input events.
@@ -733,8 +695,6 @@ def set_device_change_callback(callback):
DILL.device_change_callback_fn = C_DEVICE_CHANGE_CALLBACK(callback)
DILL._dll.set_device_change_callback(DILL.device_change_callback_fn)
-
-
@staticmethod
def get_device_count():
"""Returns the number of connected devices.
@@ -760,9 +720,7 @@ def get_device_information_by_index(index):
DeviceSummary
Structure containing detailed information about the desired device.
"""
- return DeviceSummary(
- DILL._dll.get_device_information_by_index(index)
- )
+ return DeviceSummary(DILL._dll.get_device_information_by_index(index))
@staticmethod
def get_device_information_by_guid(guid):
@@ -778,10 +736,7 @@ def get_device_information_by_guid(guid):
DeviceSummary
Structure containing detailed information about the desired device.
"""
- return DeviceSummary(
- DILL._dll.get_device_information_by_guid(guid.ctypes)
- )
-
+ return DeviceSummary(DILL._dll.get_device_information_by_guid(guid.ctypes))
@staticmethod
def get_axis(guid, index):
@@ -799,7 +754,7 @@ def get_axis(guid, index):
float
Current value of the specific axis for the desired device.
"""
-
+
return DILL._dll.get_axis(guid.ctypes, index)
@staticmethod
@@ -852,9 +807,7 @@ def get_device_name(guid):
str
Name of the specified device.
"""
- info = DeviceSummary(
- DILL._dll.get_device_information_by_guid(guid.ctypes)
- )
+ info = DeviceSummary(DILL._dll.get_device_information_by_guid(guid.ctypes))
return info.name
@staticmethod
@@ -882,4 +835,3 @@ def initialize_capi():
dll_fn.argtypes = params["arguments"]
if "returns" in params:
dll_fn.restype = params["returns"]
-
diff --git a/examples/ch stick and pedals/sc.py b/examples/ch stick and pedals/sc.py
index 45493651..0ec1f207 100644
--- a/examples/ch stick and pedals/sc.py
+++ b/examples/ch stick and pedals/sc.py
@@ -6,38 +6,30 @@
# Create joystick decorator for the CH Fighterstick in global mode
chfs = gremlin.input_devices.JoystickDecorator(
- "CH Fighterstick USB",
- 2382820288,
- "Global"
+ "CH Fighterstick USB", 2382820288, "Global"
)
chfs_roll = gremlin.input_devices.JoystickDecorator(
- "CH Fighterstick USB",
- 2382820288,
- "roll"
+ "CH Fighterstick USB", 2382820288, "roll"
)
chpp = gremlin.input_devices.JoystickDecorator(
- "CH Pro Pedals USB",
- 2382820032,
- "Global"
+ "CH Pro Pedals USB", 2382820032, "Global"
)
# Sensitivity curve setup
-default_curve = CubicSpline([
- (-1.00, -1.00),
- (-0.75, -0.65),
- (-0.25, -0.15),
- ( 0.00, 0.00),
- ( 0.25, 0.15),
- ( 0.75, 0.65),
- ( 1.00, 1.00),
-])
-sniper_curve = CubicSpline([
- (-1.00, -0.50),
- (-0.50, -0.175),
- ( 0.00, 0.00),
- ( 0.50, 0.175),
- ( 1.00, 0.50)
-])
+default_curve = CubicSpline(
+ [
+ (-1.00, -1.00),
+ (-0.75, -0.65),
+ (-0.25, -0.15),
+ (0.00, 0.00),
+ (0.25, 0.15),
+ (0.75, 0.65),
+ (1.00, 1.00),
+ ]
+)
+sniper_curve = CubicSpline(
+ [(-1.00, -0.50), (-0.50, -0.175), (0.00, 0.00), (0.50, 0.175), (1.00, 0.50)]
+)
left_pedal = 0
right_pedal = 0
@@ -49,7 +41,7 @@
"front": macro.Macro(),
"rear": macro.Macro(),
"left": macro.Macro(),
- "right": macro.Macro()
+ "right": macro.Macro(),
}
shield_macros["reset"].tap("KP5")
shield_macros["front"].press("KP8")
@@ -166,6 +158,7 @@ def reset_roll(event, vjoy):
if not event.is_pressed:
vjoy[1].axis[AxisName.RX].value = 0.0
import gremlin.control_action
+
gremlin.control_action.switch_mode("Global")
diff --git a/generate_wix.py b/generate_wix.py
index ac70b59c..7b2b2505 100644
--- a/generate_wix.py
+++ b/generate_wix.py
@@ -5,11 +5,14 @@
import uuid
import sys
import pickle
-#from xml.dom import minidom
+
+# from xml.dom import minidom
from lxml import etree as ElementTree
import logging
+
syslog = logging.getLogger("system")
+
def generate_file_list(root_folder):
"""Returns a list of file paths in the given folder.
@@ -19,9 +22,7 @@ def generate_file_list(root_folder):
file_list = []
for root, _, files in os.walk(root_folder):
for fname in files:
- file_list.append(
- os.path.relpath(os.path.join(root, fname), root_folder)
- )
+ file_list.append(os.path.relpath(os.path.join(root, fname), root_folder))
return file_list
@@ -34,9 +35,7 @@ def generate_folder_list(root_folder):
folder_list = []
for root, dirs, _ in os.walk(root_folder):
for folder in dirs:
- folder_list.append(
- os.path.relpath(os.path.join(root, folder), root_folder)
- )
+ folder_list.append(os.path.relpath(os.path.join(root, folder), root_folder))
return folder_list
@@ -59,7 +58,7 @@ def create_data_for_file(path):
"component_guid": uuid.uuid4(),
"component_id": f"component_{sanitize_path(path)}",
"file_id": f"file_{sanitize_path(path)}",
- "file_source": path
+ "file_source": path,
}
@@ -86,20 +85,14 @@ def create_folder_structure(folder_list):
# Create the basic structure for where to place the actual files
structure["root"] = create_node(
- "Directory",
- {"Id": "TARGETDIR", "Name": "SourceDir"}
+ "Directory", {"Id": "TARGETDIR", "Name": "SourceDir"}
)
structure["pfiles"] = create_node(
- "Directory",
- {"Id": "ProgramFilesFolder", "Name": "PFiles"}
- )
- structure["h2ik"] = create_node(
- "Directory",
- {"Id": "H2ik", "Name": "H2ik"}
+ "Directory", {"Id": "ProgramFilesFolder", "Name": "PFiles"}
)
+ structure["h2ik"] = create_node("Directory", {"Id": "H2ik", "Name": "H2ik"})
structure["jg"] = create_node(
- "Directory",
- {"Id": "INSTALLDIR", "Name": "Joystick Gremlin"}
+ "Directory", {"Id": "INSTALLDIR", "Name": "Joystick Gremlin"}
)
structure["root"].append(structure["pfiles"])
structure["pfiles"].append(structure["h2ik"])
@@ -110,12 +103,11 @@ def create_folder_structure(folder_list):
"Component",
{
"Guid": "cec7a9a7-d686-4355-8d9d-e1d211d3edb8",
- "Id": "H2ikProgramFilesFolder"
- }
+ "Id": "H2ikProgramFilesFolder",
+ },
)
- node.append(create_node(
- "RemoveFolder",
- {"Id": "RemoveH2iKFolder", "On": "uninstall"})
+ node.append(
+ create_node("RemoveFolder", {"Id": "RemoveH2iKFolder", "On": "uninstall"})
)
structure["h2ik"].append(node)
@@ -123,11 +115,10 @@ def create_folder_structure(folder_list):
for folder in folder_list:
dirs = folder.split("\\")
for i in range(len(dirs)):
- path = "__".join(dirs[:i+1])
+ path = "__".join(dirs[: i + 1])
if path not in structure:
structure[path] = create_node(
- "Directory",
- {"Id": path, "Name": dirs[i]}
+ "Directory", {"Id": path, "Name": dirs[i]}
)
if i > 0:
parent_path = "__".join(dirs[:i])
@@ -155,10 +146,7 @@ def add_file_nodes(structure, data):
f_node = ElementTree.Element("File")
f_node.set("Id", entry["file_id"])
f_node.set("KeyPath", "yes")
- f_node.set(
- "Source",
- os.path.join("joystick_gremlin", entry["file_source"])
- )
+ f_node.set("Source", os.path.join("joystick_gremlin", entry["file_source"]))
c_node.append(f_node)
@@ -176,26 +164,20 @@ def create_feature(data):
:return feature node
"""
node = create_node(
- "Feature",
- {
- "Id": "Complete",
- "Level": 1,
- "Title": "Joystick Gremlin Ex",
- "Description": "The main program",
- "Display": "expand",
- "ConfigurableDirectory": "INSTALLDIR"
- })
- node.append(create_node(
- "ComponentRef", {"Id": "ProgramMenuDir"}
- ))
- node.append(create_node(
- "ComponentRef", {"Id": "H2ikProgramFilesFolder"}
- ))
+ "Feature",
+ {
+ "Id": "Complete",
+ "Level": 1,
+ "Title": "Joystick Gremlin Ex",
+ "Description": "The main program",
+ "Display": "expand",
+ "ConfigurableDirectory": "INSTALLDIR",
+ },
+ )
+ node.append(create_node("ComponentRef", {"Id": "ProgramMenuDir"}))
+ node.append(create_node("ComponentRef", {"Id": "H2ikProgramFilesFolder"}))
for entry in data.values():
- node.append(create_node(
- "ComponentRef",
- {"Id": entry["component_id"]}
- ))
+ node.append(create_node("ComponentRef", {"Id": entry["component_id"]}))
return node
@@ -229,28 +211,29 @@ def create_document():
# "Id": "290a3110-0745-48d6-93d2-d954cb584b6f", # 12.0.0
# "Id": "6019660b-26bd-430b-9b95-ca6a55201060", # 13.0.0
# "Id": "0dad4221-c8cf-4424-8dcd-3886274e89ef", # 13.1.0
- #"Id": "6472cca8-d352-4186-8a98-ca6ba33d083c", # 13.40.6ex
- #"Id": "7cdb8375-66a1-4114-be79-b17027e8c0df", # 13.40.7ex
- #"Id": "739095a7-19cc-4154-ac9c-c51f5f516527", # 13.40.8ex
- #"Id": "654c694d-753c-4ec1-8a8d-8a0f2f3133d8", # 13.40.9ex
- #"Id": "2f6ff870-cfd7-4810-95ae-387c4a3f9007", # 13.40.10ex
- #"Id": "2f6ff870-cfd7-4810-95ae-387c4a3f9007", # 13.40.11ex
- #"Id": "ee7ed4b7-f969-477e-a0cc-90a555c535aa", # 13.40.12ex
- #"Id": "ee7ed4b7-f969-477e-a0cc-90a555c535aa", # 13.40.13ex
- "Id": "851832d3-6508-410c-a3e1-d8ae437fe32a", # 13.40.14ex
+ # "Id": "6472cca8-d352-4186-8a98-ca6ba33d083c", # 13.40.6ex
+ # "Id": "7cdb8375-66a1-4114-be79-b17027e8c0df", # 13.40.7ex
+ # "Id": "739095a7-19cc-4154-ac9c-c51f5f516527", # 13.40.8ex
+ # "Id": "654c694d-753c-4ec1-8a8d-8a0f2f3133d8", # 13.40.9ex
+ # "Id": "2f6ff870-cfd7-4810-95ae-387c4a3f9007", # 13.40.10ex
+ # "Id": "2f6ff870-cfd7-4810-95ae-387c4a3f9007", # 13.40.11ex
+ # "Id": "ee7ed4b7-f969-477e-a0cc-90a555c535aa", # 13.40.12ex
+ # "Id": "ee7ed4b7-f969-477e-a0cc-90a555c535aa", # 13.40.13ex
+ "Id": "851832d3-6508-410c-a3e1-d8ae437fe32a", # 13.40.14ex
"UpgradeCode": "e5ee68fe-ada4-46a7-b4f3-798bfe8de6cf",
"Language": "1033",
"Codepage": "1252",
- "Version": "13.40.14ex"
- })
-
+ "Version": "13.40.14ex",
+ },
+ )
+
# also change version number in joystick_gremlin.py line 60 APPLICATION_VERSION
-
- mug = create_node("MajorUpgrade",
+
+ mug = create_node(
+ "MajorUpgrade",
{
- "DowngradeErrorMessage":
- "Cannot directly downgrade, uninstall current version first."
- }
+ "DowngradeErrorMessage": "Cannot directly downgrade, uninstall current version first."
+ },
)
pkg = create_node(
"Package",
@@ -262,32 +245,25 @@ def create_document():
"InstallerVersion": "100",
"Languages": "1033",
"SummaryCodepage": "1252",
- "Compressed": "yes"
- }
+ "Compressed": "yes",
+ },
)
# Package needs to be added before media
prod.append(pkg)
prod.append(mug)
- prod.append(create_node(
- "Media",
- {
- "Id": "1",
- "Cabinet": "joystick_gremlin.cab",
- "EmbedCab": "yes"
- }
- ))
+ prod.append(
+ create_node(
+ "Media", {"Id": "1", "Cabinet": "joystick_gremlin.cab", "EmbedCab": "yes"}
+ )
+ )
# Add the icon to the software center
- prod.append(create_node(
- "Property",
- {"Id": "ARPPRODUCTICON", "Value": "icon.ico"}
- ))
+ prod.append(create_node("Property", {"Id": "ARPPRODUCTICON", "Value": "icon.ico"}))
# Remvoe the repair option from the installer
- prod.append(create_node(
- "Property",
- {"Id": "ARPNOREPAIR", "Value": "yes", "Secure": "yes"}
- ))
+ prod.append(
+ create_node("Property", {"Id": "ARPNOREPAIR", "Value": "yes", "Secure": "yes"})
+ )
doc.append(prod)
@@ -302,10 +278,9 @@ def create_ui_node(parent):
ui = create_node("UI", {})
ui.append(create_node("UIRef", {"Id": "WixUI_InstallDir"}))
ui.append(create_node("UIRef", {"Id": "WixUI_ErrorProgressText"}))
- ui.append(create_node(
- "Property",
- {"Id": "WIXUI_INSTALLDIR", "Value": "INSTALLDIR"}
- ))
+ ui.append(
+ create_node("Property", {"Id": "WIXUI_INSTALLDIR", "Value": "INSTALLDIR"})
+ )
# Skip the license screen
n1 = create_node(
@@ -315,8 +290,8 @@ def create_ui_node(parent):
"Control": "Next",
"Event": "NewDialog",
"Value": "InstallDirDlg",
- "Order": "2"
- }
+ "Order": "2",
+ },
)
n1.text = "1"
n2 = create_node(
@@ -326,8 +301,8 @@ def create_ui_node(parent):
"Control": "Back",
"Event": "NewDialog",
"Value": "WelcomeDlg",
- "Order": 2
- }
+ "Order": 2,
+ },
)
n2.text = "1"
ui.append(n1)
@@ -345,71 +320,68 @@ def create_shortcuts(doc, root):
# Find the executable node and add shortcut entries
for node in doc.iter("File"):
if node.get("Id") == "file_joystick_gremlin.exe":
- node.append(create_node(
- "Shortcut",
- {
- "Id": "startmenu_joystick_gremlin",
- "Directory": "ProgramMenuDir",
- "Name": "Joystick Gremlin Ex",
- "WorkingDirectory": "INSTALLDIR",
- "Advertise": "yes",
- "Icon": "icon.ico"
- }
- ))
- node.append(create_node(
- "Shortcut",
- {
- "Id": "desktop_joystick_gremlin",
- "Directory": "DesktopFolder",
- "Name": "Joystick Gremlin Ex",
- "WorkingDirectory": "INSTALLDIR",
- "Advertise": "yes",
- "Icon": "icon.ico"
- }
- ))
+ node.append(
+ create_node(
+ "Shortcut",
+ {
+ "Id": "startmenu_joystick_gremlin",
+ "Directory": "ProgramMenuDir",
+ "Name": "Joystick Gremlin Ex",
+ "WorkingDirectory": "INSTALLDIR",
+ "Advertise": "yes",
+ "Icon": "icon.ico",
+ },
+ )
+ )
+ node.append(
+ create_node(
+ "Shortcut",
+ {
+ "Id": "desktop_joystick_gremlin",
+ "Directory": "DesktopFolder",
+ "Name": "Joystick Gremlin Ex",
+ "WorkingDirectory": "INSTALLDIR",
+ "Advertise": "yes",
+ "Icon": "icon.ico",
+ },
+ )
+ )
# Create folder names used for the shortcuts
- n1 = create_node(
- "Directory",
- {"Id": "ProgramMenuFolder", "Name": "Programs"}
- )
+ n1 = create_node("Directory", {"Id": "ProgramMenuFolder", "Name": "Programs"})
n2 = create_node(
- "Directory",
- {"Id": "ProgramMenuDir", "Name": "Joystick Gremlin Ex"}
+ "Directory", {"Id": "ProgramMenuDir", "Name": "Joystick Gremlin Ex"}
)
n3 = create_node(
"Component",
- {"Id": "ProgramMenuDir", "Guid": "11ab7593-4b4e-470d-8a56-4791b40c0838"}
+ {"Id": "ProgramMenuDir", "Guid": "11ab7593-4b4e-470d-8a56-4791b40c0838"},
+ )
+ n3.append(create_node("RemoveFolder", {"Id": "ProgramMenuDir", "On": "uninstall"}))
+ n3.append(
+ create_node(
+ "RegistryValue",
+ {
+ "Root": "HKCU",
+ "Key": "Software\H2ik\Joystick Gremlin",
+ "Type": "string",
+ "Value": "",
+ "KeyPath": "yes",
+ },
+ )
)
- n3.append(create_node(
- "RemoveFolder",
- {"Id": "ProgramMenuDir", "On": "uninstall"}
- ))
- n3.append(create_node(
- "RegistryValue",
- {
- "Root": "HKCU",
- "Key": "Software\H2ik\Joystick Gremlin",
- "Type": "string",
- "Value": "",
- "KeyPath": "yes"
- }
- ))
n2.append(n3)
n1.append(n2)
root.append(n1)
- root.append(create_node(
- "Directory",
- {"Id": "DesktopFolder", "Name": "Desktop"}
- ))
+ root.append(create_node("Directory", {"Id": "DesktopFolder", "Name": "Desktop"}))
# Create the used icon
product = doc.find("Product")
- product.append(create_node(
- "Icon",
- {"Id": "icon.ico", "SourceFile": "joystick_gremlin\gfx\icon.ico"}
- ))
+ product.append(
+ create_node(
+ "Icon", {"Id": "icon.ico", "SourceFile": "joystick_gremlin\gfx\icon.ico"}
+ )
+ )
def write_xml(node, fname):
@@ -424,17 +396,15 @@ def write_xml(node, fname):
# with open(fname, "w") as out:
# out.write(dom_xml.toprettyxml(indent=" "))
-
tree = ElementTree.ElementTree(node)
- tree.write(fname, pretty_print=True,xml_declaration=True,encoding="utf-8")
+ tree.write(fname, pretty_print=True, xml_declaration=True, encoding="utf-8")
+
def main():
# Command line arguments
parser = argparse.ArgumentParser("Generate WIX component data")
parser.add_argument(
- "--folder",
- default="dist/joystick_gremlin",
- help="Folder to parse"
+ "--folder", default="dist/joystick_gremlin", help="Folder to parse"
)
args = parser.parse_args()
diff --git a/gremlin/__init__.py b/gremlin/__init__.py
index 3e6ebb92..e01fe792 100644
--- a/gremlin/__init__.py
+++ b/gremlin/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,7 +16,6 @@
# along with this program. If not, see .
-
# import gremlin.error
# import gremlin.util
# import gremlin.actions
diff --git a/gremlin/actions.py b/gremlin/actions.py
index 722e5d67..b4ccbda2 100644
--- a/gremlin/actions.py
+++ b/gremlin/actions.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -34,10 +34,9 @@
import gremlin.fsm
-
-
syslog = logging.getLogger("system")
+
def smart_all(conditions):
"""Returns True if all conditions are True, False otherwise.
@@ -67,10 +66,9 @@ def smart_any(conditions):
class Value:
-
"""Represents an input value, keeping track of raw and "seen" value."""
- def __init__(self, raw, is_pressed = None):
+ def __init__(self, raw, is_pressed=None):
"""Creates a new value and initializes it.
:param raw the initial raw data
@@ -78,7 +76,6 @@ def __init__(self, raw, is_pressed = None):
self._raw = raw
self._current = raw
self._is_pressed = is_pressed
-
@property
def raw(self):
@@ -115,13 +112,13 @@ def is_pressed(self, value: bool):
self._is_pressed = value
def clone(self):
- ''' clones this value '''
+ """clones this value"""
import copy
+
return copy.deepcopy(self)
-
-class ActivationCondition:
+class ActivationCondition:
"""Represents a set of conditions dictating the activation of actions.
This class contains a set of functions which evaluate to either True or
@@ -129,34 +126,33 @@ class ActivationCondition:
True or False.
"""
-
- rule_function = {
- ActivationRule.All: smart_all,
- ActivationRule.Any: smart_any
- }
+ rule_function = {ActivationRule.All: smart_all, ActivationRule.Any: smart_any}
- def __init__(self, conditions, rule, target, is_container_condition = False):
+ def __init__(self, conditions, rule, target, is_container_condition=False):
self._conditions = conditions
self._rule = rule
- self.enabled = True # always enabled
- self.target = target # the target this condition applies to (container or action)
- self.id = target.id # the id of this node is the same as the one for the container or action
+ self.enabled = True # always enabled
+ self.target = (
+ target # the target this condition applies to (container or action)
+ )
+ self.id = (
+ target.id
+ ) # the id of this node is the same as the one for the container or action
self.is_container_condition = is_container_condition
self.manual_callback = False
-
-
+
@property
def is_container(self) -> bool:
if self.target:
return isinstance(self.target, gremlin.base_profile.AbstractContainer)
return False
-
- @property
+
+ @property
def isAny(self) -> bool:
- ''' true if the activiation condition is any sub condition '''
+ """true if the activiation condition is any sub condition"""
return self._rule == ActivationRule.Any
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
"""Returns whether or not a condition is satisfied, i.e. true.
:param event the event this condition was triggered through
@@ -167,20 +163,19 @@ def process_event(self, event, value, extra_data = None):
[partial(c, event, value) for c in self._conditions]
)
-
- def condition_name(self)->str:
- ''' returns a condition name for diagnostics purposes '''
+ def condition_name(self) -> str:
+ """returns a condition name for diagnostics purposes"""
rule_name = "all" if self._rule == ActivationRule.All else "any"
condition_name = ""
for index, c in enumerate(self._conditions):
condition_name += f"[C{index}] {c.condition_name()} "
return f"Rule: {rule_name} Is container: {self.is_container} Is container condition: {self.is_container_condition} Conditions: {condition_name} "
-
+
def __str__(self):
return self.condition_name()
-class AbstractCondition(metaclass=ABCMeta):
+class AbstractCondition(metaclass=ABCMeta):
"""Represents an abstract condition.
Conditions evaluate to either True or False and are given an event as well
@@ -197,7 +192,7 @@ def __init__(self, comparison):
self.manual_callback = False
@abstractmethod
- def __call__(self, event, value, extra_data = None):
+ def __call__(self, event, value, extra_data=None):
"""Evaluates the condition using the condition and provided data.
:param event raw event that caused the condition to be evaluated
@@ -207,22 +202,21 @@ def __call__(self, event, value, extra_data = None):
pass
@abstractmethod
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
pass
- def condition_name(self)->str:
+ def condition_name(self) -> str:
return "condition_name() member not implemented: Condition not set"
-
-class KeyboardCondition(AbstractCondition):
+class KeyboardCondition(AbstractCondition):
"""Condition verifying the state of keyboard keys.
The conditions that can be checked on a keyboard is whether or not a
particular key is pressed or released.
"""
- def __init__(self, scan_code, is_extended, comparison, input_item = None):
+ def __init__(self, scan_code, is_extended, comparison, input_item=None):
"""Creates a new instance.
:param scan_code the scan code of the key to evaluate
@@ -230,21 +224,22 @@ def __init__(self, scan_code, is_extended, comparison, input_item = None):
:param comparison the comparison operation to perform when evaluated
"""
import gremlin.macro
+
super().__init__(comparison)
from gremlin.ui.keyboard_device import KeyboardInputItem
-
+
if not input_item:
input_item = KeyboardInputItem()
key = gremlin.macro.key_from_code(scan_code, is_extended)
input_item.key = key
self.input_item = input_item
-
- def __call__(self, event, value, extra_data = None):
+
+ def __call__(self, event, value, extra_data=None):
# default call
return self.process_event(event, value, extra_data)
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
"""Evaluates the condition using the condition and provided data.
:param event raw event that caused the condition to be evaluated
@@ -262,21 +257,22 @@ def process_event(self, event, value, extra_data = None):
if self.comparison == "pressed":
state = key_pressed
else:
- state = not key_pressed
+ state = not key_pressed
- if verbose: syslog.info(f"{logtabs}KeyboardCondition: key: {self.input_item.display_name} pressed {key_pressed} - condition return state: {"OK" if state else "FAILED"}")
+ if verbose:
+ syslog.info(
+ f"{logtabs}KeyboardCondition: key: {self.input_item.display_name} pressed {key_pressed} - condition return state: {"OK" if state else "FAILED"}"
+ )
return state
-
-
- def condition_name(self)->str:
+
+ def condition_name(self) -> str:
return f"KeyboardCondition {self.input_item.display_name}"
-
+
def __str__(self):
return self.condition_name()
class JoystickCondition(AbstractCondition):
-
"""Condition verifying the state of a joystick input.
Joysticks have three possible input types: axis, button, or hat and each
@@ -295,15 +291,16 @@ def __init__(self, condition):
self.input_type = condition.input_type
self.input_id = condition.input_id
# hat number or 0 for axis and buttons
- self.input_index = condition.input_index if hasattr(condition,"input_index") else 0
+ self.input_index = (
+ condition.input_index if hasattr(condition, "input_index") else 0
+ )
self.condition = condition
-
- def __call__(self, event, value, extra_data = None):
+ def __call__(self, event, value, extra_data=None):
# default call
return self.process_event(event, value, extra_data)
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
"""Evaluates the condition using the condition and provided data.
:param event raw event that caused the condition to be evaluated
@@ -311,10 +308,7 @@ def process_event(self, event, value, extra_data = None):
:return True if the condition is satisfied, False otherwise
"""
-
-
verbose = gremlin.config.Configuration().verbose_mode_condition
-
if verbose:
logtabs = gremlin.shared_state.logTabs(True)
@@ -324,65 +318,91 @@ def process_event(self, event, value, extra_data = None):
if joy is None:
# device not found - ignore
return False
-
-
-
-
if self.input_type == InputType.JoystickAxis:
retval = False
-
+
if self.condition.use_calibrated_data:
# calibrated value
- value = gremlin.joystick_handling.get_curved_axis(self.device_guid, self.input_id)
- if verbose:
- raw = gremlin.joystick_handling.get_axis(self.device_guid, self.input_id)
- syslog.info(f"{logtabs}condition input value (filtered): raw: {raw:0.3f} filtered: {value:0.3f}")
+ value = gremlin.joystick_handling.get_curved_axis(
+ self.device_guid, self.input_id
+ )
+ if verbose:
+ raw = gremlin.joystick_handling.get_axis(
+ self.device_guid, self.input_id
+ )
+ syslog.info(
+ f"{logtabs}condition input value (filtered): raw: {raw:0.3f} filtered: {value:0.3f}"
+ )
else:
# raw value
- value = gremlin.joystick_handling.get_axis(self.device_guid, self.input_id)
- if verbose: syslog.info(f"{logtabs}condition input value (raw): {value:0.3f}")
- #value = joy.axis(self.input_id).value
+ value = gremlin.joystick_handling.get_axis(
+ self.device_guid, self.input_id
+ )
+ if verbose:
+ syslog.info(f"{logtabs}condition input value (raw): {value:0.3f}")
+ # value = joy.axis(self.input_id).value
r1 = self.condition.range[0]
r2 = self.condition.range[1]
if r1 > r2:
- r1,r2 = r2,r1
+ r1, r2 = r2, r1
in_range = value >= r1 and value <= r2
if self.condition.comparison in ["inside", "outside"]:
retval = in_range if self.comparison == "inside" else not in_range
- if verbose: syslog.info(f"{logtabs}JoystickCondition: Axis range comparison: [{self.comparison}]: device {info.name} input: {self.input_id} range: {self.condition.range[0]:0.3f} to {self.condition.range[1]:0.3f} value: {joy.axis(self.input_id).value:0.3f} return: {"OK" if retval else "FAILED"}")
+ if verbose:
+ syslog.info(
+ f"{logtabs}JoystickCondition: Axis range comparison: [{self.comparison}]: device {info.name} input: {self.input_id} range: {self.condition.range[0]:0.3f} to {self.condition.range[1]:0.3f} value: {joy.axis(self.input_id).value:0.3f} return: {"OK" if retval else "FAILED"}"
+ )
return retval
-
+
elif self.input_type == InputType.JoystickButton:
retval = False
- is_pressed = gremlin.joystick_handling.get_button(self.device_guid, self.input_id)
+ is_pressed = gremlin.joystick_handling.get_button(
+ self.device_guid, self.input_id
+ )
if self.comparison == "pressed":
retval = is_pressed
elif self.comparison == "released":
retval = not is_pressed
else:
- syslog.error(f"Don't know how to handle joystick condition: {self.comparison}")
+ syslog.error(
+ f"Don't know how to handle joystick condition: {self.comparison}"
+ )
return False
- if verbose: syslog.info(f"{logtabs}JoystickCondition: Button {self.comparison}: device {info.name} input: {self.input_id} pressed: {is_pressed} return: {"OK" if retval else "FAILED"}")
+ if verbose:
+ syslog.info(
+ f"{logtabs}JoystickCondition: Button {self.comparison}: device {info.name} input: {self.input_id} pressed: {is_pressed} return: {"OK" if retval else "FAILED"}"
+ )
return retval
-
+
elif self.input_type == InputType.JoystickHat:
- direction = gremlin.joystick_handling.get_hat(self.device_guid, self.input_id)
+ direction = gremlin.joystick_handling.get_hat(
+ self.device_guid, self.input_id
+ )
retval = direction == gremlin.util.hat_direction_to_tuple(self.comparison)
- if verbose: syslog.info(f"{logtabs}JoystickCondition: Hat Device {info.name} input: {self.input_id} comparison: {self.comparison} direction: {direction} return: {"OK" if retval else "FAILED"}")
+ if verbose:
+ syslog.info(
+ f"{logtabs}JoystickCondition: Hat Device {info.name} input: {self.input_id} comparison: {self.comparison} direction: {direction} return: {"OK" if retval else "FAILED"}"
+ )
return retval
else:
- syslog.warning(f"{logtabs}JoystickCondition: Invalid input_type {self.input_type} received")
+ syslog.warning(
+ f"{logtabs}JoystickCondition: Invalid input_type {self.input_type} received"
+ )
return False
-
- def condition_name(self)->str:
+
+ def condition_name(self) -> str:
info = gremlin.joystick_handling.device_info_from_guid(self.device_guid)
match self.input_type:
case InputType.JoystickButton:
- state = gremlin.joystick_handling.get_button(self.device_guid, self.input_id)
+ state = gremlin.joystick_handling.get_button(
+ self.device_guid, self.input_id
+ )
case InputType.JoystickHat:
- state = gremlin.joystick_handling.get_hat(self.device_guid, self.input_id)
+ state = gremlin.joystick_handling.get_hat(
+ self.device_guid, self.input_id
+ )
case InputType.JoystickAxis:
state = f"{gremlin.joystick_handling.get_axis(self.device_guid, self.input_id):0.3f}"
case _:
@@ -393,8 +413,8 @@ def condition_name(self)->str:
def __str__(self):
return self.condition_name()
-class VJoyCondition(AbstractCondition):
+class VJoyCondition(AbstractCondition):
"""Condition verifying the state of a vJoy input.
vJoy devices have three possible input types: axis, button, or hat and each
@@ -410,8 +430,6 @@ def __init__(self, condition):
"""
super().__init__(condition.comparison)
-
-
self.vjoy_id = condition.vjoy_id
self.device_guid = None
for dev in gremlin.joystick_handling.vjoy_devices():
@@ -420,16 +438,18 @@ def __init__(self, condition):
break
self.input_type = condition.input_type
self.input_id = condition.input_id
-
- self.input_index = condition.input_index if hasattr(condition,"input_index") else 0
-
+
+ self.input_index = (
+ condition.input_index if hasattr(condition, "input_index") else 0
+ )
+
self.condition = condition
- def __call__(self, event, value, extra_data = None):
+ def __call__(self, event, value, extra_data=None):
# default call
return self.process_event(event, value, extra_data)
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
"""Evaluates the condition using the condition and provided data.
:param event raw event that caused the condition to be evaluated
@@ -448,49 +468,65 @@ def process_event(self, event, value, extra_data = None):
joy = gremlin.input_devices.JoystickProxy()[self.device_guid]
if joy is None:
# device not found - ignore
- if verbose: syslog.warning(f"{logtabs}VjoyCondition: device not found: {self.device_guid} {gremlin.joystick_handling.device_name_from_guid(self.device_guid)}")
+ if verbose:
+ syslog.warning(
+ f"{logtabs}VjoyCondition: device not found: {self.device_guid} {gremlin.joystick_handling.device_name_from_guid(self.device_guid)}"
+ )
return False
-
-
+
if verbose:
info = gremlin.joystick_handling.device_info_from_guid(self.device_guid)
-
if self.input_type == InputType.JoystickAxis:
retval = False
- in_range = self.condition.range[0] <= \
- joy.axis(self.input_id).value <= \
- self.condition.range[1]
+ in_range = (
+ self.condition.range[0]
+ <= joy.axis(self.input_id).value
+ <= self.condition.range[1]
+ )
if self.comparison in ["inside", "outside"]:
- retval = in_range if self.comparison == "inside" else not in_range
- if verbose: syslog.info(f"{logtabs}VjoyCondition: Axis {self.comparison}: device {info.name} input: {self.input_id} range: {self.condition.range[0]:0.3f} to {self.condition.range[1]:0.3f} value: {joy.axis(self.input_id).value:0.3f} return: {"OK" if retval else "FAILED"}")
+ retval = in_range if self.comparison == "inside" else not in_range
+ if verbose:
+ syslog.info(
+ f"{logtabs}VjoyCondition: Axis {self.comparison}: device {info.name} input: {self.input_id} range: {self.condition.range[0]:0.3f} to {self.condition.range[1]:0.3f} value: {joy.axis(self.input_id).value:0.3f} return: {"OK" if retval else "FAILED"}"
+ )
return retval
-
elif self.input_type == InputType.JoystickButton:
retval = False
- is_pressed = gremlin.joystick_handling.get_button(self.device_guid, self.input_id)
+ is_pressed = gremlin.joystick_handling.get_button(
+ self.device_guid, self.input_id
+ )
if self.comparison == "pressed":
- retval = is_pressed # joy.button(self.input_id).is_pressed
+ retval = is_pressed # joy.button(self.input_id).is_pressed
else:
- retval = not is_pressed # joy.button(self.input_id).is_pressed
- if verbose: syslog.info(f"{logtabs}VjoyCondition: Button {self.comparison}: device {info.name} input: {self.input_id} return: {"OK" if retval else "FAILED"}")
+ retval = not is_pressed # joy.button(self.input_id).is_pressed
+ if verbose:
+ syslog.info(
+ f"{logtabs}VjoyCondition: Button {self.comparison}: device {info.name} input: {self.input_id} return: {"OK" if retval else "FAILED"}"
+ )
return retval
-
+
elif self.input_type == InputType.JoystickHat:
- direction = gremlin.joystick_handling.get_hat(self.device_guid, self.input_id)
- retval = direction == gremlin.util.hat_direction_to_tuple(self.comparison)
- if verbose: syslog.info(f"{logtabs}VjoyCondition: Hat Device {info.name} input: {self.input_id} comparison: {self.comparison} direction: {direction} return: {"OK" if retval else "FAILED"}")
+ direction = gremlin.joystick_handling.get_hat(
+ self.device_guid, self.input_id
+ )
+ retval = direction == gremlin.util.hat_direction_to_tuple(self.comparison)
+ if verbose:
+ syslog.info(
+ f"{logtabs}VjoyCondition: Hat Device {info.name} input: {self.input_id} comparison: {self.comparison} direction: {direction} return: {"OK" if retval else "FAILED"}"
+ )
else:
- syslog.warning(f"VjoyCondition: Invalid input_type {self.input_type} received")
+ syslog.warning(
+ f"VjoyCondition: Invalid input_type {self.input_type} received"
+ )
return False
- def condition_name(self)->str:
+ def condition_name(self) -> str:
info = gremlin.joystick_handling.device_info_from_guid(self.device_guid)
return f"VJoyCondition: mode: {self.comparison} type: {gremlin.input_types.InputType.to_display_name(self.input_type)} input: {self.input_id} device: {info.name} "
-
def __str__(self):
return self.condition_name()
@@ -513,10 +549,9 @@ def process_event(self, event, value, extra_data = None):
def condition_name(self)->str:
return f"ModeCondition: mode: [{self.comparison}]"
-
-
-class InputActionCondition(AbstractCondition):
+
+class InputActionCondition(AbstractCondition):
"""Condition verifying the state of the triggering input itself. (ActionActivationCondition)
This checks the state of the input that triggered the event in the first
@@ -530,11 +565,11 @@ def __init__(self, comparison):
"""
super().__init__(comparison)
- def __call__(self, event, value, extra_data = None):
+ def __call__(self, event, value, extra_data=None):
# default call
return self.process_event(event, value, extra_data)
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
"""Evaluates the condition using the condition and provided data.
:param event raw event that caused the condition to be evaluated
@@ -554,20 +589,21 @@ def process_event(self, event, value, extra_data = None):
retval = not value.current
elif self.comparison == "always":
retval = True
-
- if verbose:
+
+ if verbose:
logtabs = gremlin.shared_state.logTabs(True)
- syslog.info(f"{logtabs}InputActionCondition: comparison {self.comparison}: return: {retval}")
+ syslog.info(
+ f"{logtabs}InputActionCondition: comparison {self.comparison}: return: {retval}"
+ )
return retval
- def condition_name(self)->str:
+ def condition_name(self) -> str:
return f"InputActionCondition: condition: {self.comparison}"
-class VirtualButton(metaclass=ABCMeta):
+class VirtualButton(metaclass=ABCMeta):
"""Implements a button like interface."""
-
# Next identifier ID to use
next_id = 1
@@ -590,7 +626,7 @@ def _initialize_fsm(self):
("up", "press"): gremlin.fsm.Transition(self._press, "down"),
("up", "release"): gremlin.fsm.Transition(self._noop, "up"),
("down", "release"): gremlin.fsm.Transition(self._release, "up"),
- ("down", "press"): gremlin.fsm.Transition(self._noop, "down")
+ ("down", "press"): gremlin.fsm.Transition(self._noop, "down"),
}
return gremlin.fsm.FiniteStateMachine("up", states, actions, transitions)
@@ -624,7 +660,7 @@ def _press(self):
self._identifier,
device_guid=dinput.GUID_Virtual,
is_pressed=self._is_pressed,
- raw_value=self._is_pressed
+ raw_value=self._is_pressed,
)
eh = gremlin.event_handler.EventListener()
eh.virtual_event.emit(event)
@@ -638,7 +674,7 @@ def _release(self):
self._identifier,
device_guid=dinput.GUID_Virtual,
is_pressed=self._is_pressed,
- raw_value=self._is_pressed
+ raw_value=self._is_pressed,
)
eh = gremlin.event_handler.EventListener()
eh.virtual_event.emit(event)
@@ -658,7 +694,6 @@ def is_pressed(self):
class AxisButton(VirtualButton):
-
"""Virtual button based around an axis."""
def __init__(self, lower_limit, upper_limit, direction):
@@ -681,6 +716,7 @@ def _do_process(self, event):
:return True if a state transition occurred, False otherwise
"""
from gremlin.types import AxisButtonDirection
+
self.forced_activation = False
direction = AxisButtonDirection.Anywhere
if self._last_value is None:
@@ -688,23 +724,23 @@ def _do_process(self, event):
else:
# Check if we moved over the activation region between two
# consecutive measurements
- if self._last_value < self._lower_limit or self._last_value > self._upper_limit:
+ if (
+ self._last_value < self._lower_limit
+ or self._last_value > self._upper_limit
+ ):
self.forced_activation = True
- if self._last_value < self._lower_limit and \
- event.value > self._upper_limit:
+ if self._last_value < self._lower_limit and event.value > self._upper_limit:
self.forced_activation = True
- elif self._last_value > self._upper_limit and \
- event.value < self._lower_limit:
+ elif (
+ self._last_value > self._upper_limit and event.value < self._lower_limit
+ ):
self.forced_activation = True
-
-
# Determine direction in which the axis is moving
if self._last_value < event.value:
direction = AxisButtonDirection.Below
elif self._last_value > event.value:
direction = AxisButtonDirection.Above
-
inside_range = self._lower_limit <= event.value <= self._upper_limit
self._last_value = event.value
@@ -718,8 +754,11 @@ def _do_process(self, event):
# Terminate early if the travel direction is incompatible with the
# one required by this instance
from gremlin.types import AxisButtonDirection
- if direction != AxisButtonDirection.Anywhere and \
- self._direction != AxisButtonDirection.Anywhere:
+
+ if (
+ direction != AxisButtonDirection.Anywhere
+ and self._direction != AxisButtonDirection.Anywhere
+ ):
# Ensure we can only press a button by moving in the desired
# direction, however, allow releasing in any direction
if inside_range and direction != self._direction:
@@ -738,7 +777,6 @@ def _do_process(self, event):
class HatButton(VirtualButton):
-
"""Virtual button based around a hat."""
def __init__(self, directions):
diff --git a/gremlin/base_buttons.py b/gremlin/base_buttons.py
index 2142a518..fc3a7534 100644
--- a/gremlin/base_buttons.py
+++ b/gremlin/base_buttons.py
@@ -1,15 +1,11 @@
-
-
from abc import abstractmethod, ABCMeta
-import enum
-import logging
from lxml import etree as ElementTree
from gremlin.input_types import InputType
from gremlin.util import *
-class AbstractVirtualButton(metaclass=ABCMeta):
+class AbstractVirtualButton(metaclass=ABCMeta):
"""Base class of all virtual buttons."""
def __init__(self):
@@ -17,7 +13,7 @@ def __init__(self):
pass
@abstractmethod
- def from_xml(self, node, data = None):
+ def from_xml(self, node, data=None):
"""Populates the virtual button based on the node's data.
:param node the node containing data for this instance
@@ -34,10 +30,11 @@ def to_xml(self):
class VirtualAxisButton(AbstractVirtualButton):
-
"""Virtual button which turns an axis range into a button."""
- def __init__(self, container = None): #, lower_limit=-1.0, upper_limit=1.0, device_guid = None, input_type = None, input_id = None, mode = None, enabled = False):
+ def __init__(
+ self, container=None
+ ): # , lower_limit=-1.0, upper_limit=1.0, device_guid = None, input_type = None, input_id = None, mode = None, enabled = False):
"""Creates a new instance.
:param lower_limit the lower limit of the virtual button
@@ -46,12 +43,13 @@ def __init__(self, container = None): #, lower_limit=-1.0, upper_limit=1.0, devi
from gremlin.types import AxisButtonDirection
from gremlin.base_profile import AbstractContainer
import gremlin.shared_state
+
container: AbstractContainer = container
super().__init__()
self.lower_limit = -1
self.upper_limit = 1
self.direction = AxisButtonDirection.Anywhere
- self.device_guid = container.device_guid # associated device
+ self.device_guid = container.device_guid # associated device
self.input_id = container.get_input_id()
self.mode = gremlin.shared_state.current_mode
self.input_type = container.get_input_type()
@@ -60,18 +58,20 @@ def __init__(self, container = None): #, lower_limit=-1.0, upper_limit=1.0, devi
@property
def enabled(self) -> bool:
return self.container.virtual_button_user_enabled
+
@enabled.setter
- def enabled(self, value : bool):
+ def enabled(self, value: bool):
import gremlin.event_handler
+
self.container.virtual_button_user_enabled = value
el = gremlin.event_handler.EventListener()
- el.condition_state_changed.emit(self.container) # updates the UI status flags
+ el.condition_state_changed.emit(self.container) # updates the UI status flags
@property
def range(self):
return [self.lower_limit, self.upper_limit]
-
- def from_xml(self, node, data = None):
+
+ def from_xml(self, node, data=None):
"""Populates the virtual button based on the node's data.
:param node the node containing data for this instance
@@ -84,14 +84,14 @@ def from_xml(self, node, data = None):
self.mode = mode
self.input_id = input_id
-
from gremlin.types import AxisButtonDirection
+
self.lower_limit = safe_read(node, "lower-limit", float, -1.0)
self.upper_limit = safe_read(node, "upper-limit", float, 1.0)
self.direction = AxisButtonDirection.to_enum(
safe_read(node, "direction", default_value="anywhere")
)
- self.enabled = safe_read(node,"enabled", bool, False)
+ self.enabled = safe_read(node, "enabled", bool, False)
def to_xml(self):
"""Returns an XML node representing the data of this instance.
@@ -99,29 +99,29 @@ def to_xml(self):
:return XML node containing the instance's data
"""
from gremlin.types import AxisButtonDirection
+
node = ElementTree.Element("virtual-button")
node.set("lower-limit", str(self.lower_limit))
node.set("upper-limit", str(self.upper_limit))
- node.set("direction",AxisButtonDirection.to_string(self.direction))
+ node.set("direction", AxisButtonDirection.to_string(self.direction))
node.set("enabled", str(self.enabled))
return node
class VirtualHatButton(AbstractVirtualButton):
-
"""Virtual button which combines hat directions into a button."""
# Mapping from event directions to names
direction_to_name = {
- ( 0, 0): "center",
- ( 0, 1): "north",
- ( 1, 1): "north-east",
- ( 1, 0): "east",
- ( 1, -1): "south-east",
- ( 0, -1): "south",
+ (0, 0): "center",
+ (0, 1): "north",
+ (1, 1): "north-east",
+ (1, 0): "east",
+ (1, -1): "south-east",
+ (0, -1): "south",
(-1, -1): "south-west",
- (-1, 0): "west",
- (-1, 1): "north-west"
+ (-1, 0): "west",
+ (-1, 1): "north-west",
}
# Mapping from names to event directions
@@ -134,10 +134,12 @@ class VirtualHatButton(AbstractVirtualButton):
"south": (0, -1),
"south-west": (-1, -1),
"west": (-1, 0),
- "north-west": (-1, 1)
+ "north-west": (-1, 1),
}
- def __init__(self, container, directions=()): #, device_guid = None, input_type = None, input_id = None, mode = None):
+ def __init__(
+ self, container, directions=()
+ ): # , device_guid = None, input_type = None, input_id = None, mode = None):
"""Creates a instance.
:param directions list of direction that form the virtual button
@@ -145,16 +147,17 @@ def __init__(self, container, directions=()): #, device_guid = None, input_type
super().__init__()
from gremlin.base_profile import AbstractContainer
import gremlin.shared_state
+
container: AbstractContainer = container
self.directions = list(set(directions))
- self.device_guid = container.device_guid
+ self.device_guid = container.device_guid
self.input_type = InputType.JoystickHat
self.input_id = container.get_input_id()
self.mode = gremlin.shared_state.current_mode
self.input_type = container.get_input_type()
self.container = container
- def from_xml(self, node, data = None):
+ def from_xml(self, node, data=None):
"""Populates the activation condition based on the node's data.
:param node the node containing data for this instance
@@ -167,8 +170,7 @@ def from_xml(self, node, data = None):
self.input_id = input_id
for key, value in node.items():
- if key in VirtualHatButton.name_to_direction and \
- parse_bool(value):
+ if key in VirtualHatButton.name_to_direction and parse_bool(value):
self.directions.append(key)
def to_xml(self):
@@ -180,4 +182,4 @@ def to_xml(self):
for direction in self.directions:
if direction in VirtualHatButton.name_to_direction:
node.set(direction, "1")
- return node
\ No newline at end of file
+ return node
diff --git a/gremlin/base_classes.py b/gremlin/base_classes.py
index d018d3e0..c30967c6 100644
--- a/gremlin/base_classes.py
+++ b/gremlin/base_classes.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,14 +21,12 @@
from PySide6 import QtCore
-
-
class TraceableList(MutableSequence):
- ''' implements a custom list that can be traced when it changes '''
+ """implements a custom list that can be traced when it changes"""
- def __init__(self, initlist=None, callback = None):
+ def __init__(self, initlist=None, callback=None):
MutableSequence.__init__(self)
-
+
self.data = []
self._callbacks = []
if callback:
@@ -44,24 +42,23 @@ def __init__(self, initlist=None, callback = None):
self.data = list(initlist)
def add_callback(self, value):
- ''' adds a callback - signature (action: str, index: int, value [optional object])'''
- if not value in self._callbacks:
+ """adds a callback - signature (action: str, index: int, value [optional object])"""
+ if value not in self._callbacks:
self._callbacks.append(value)
def remove_callback(self, value):
- ''' removes a callback '''
+ """removes a callback"""
if value in self._callbacks:
self._callbacks.remove(value)
def clear_callbacks(self):
- ''' removes all callbacks '''
+ """removes all callbacks"""
self._callbacks.clear()
- def _trigger(self, action, index = None, value = None):
+ def _trigger(self, action, index=None, value=None):
for callback in self._callbacks:
callback(self, action, index, value)
-
def __repr__(self):
return """<{} data: {}>""".format(self.__class__.__name__, repr(self.data))
@@ -93,10 +90,10 @@ def __getitem__(self, idx):
if isinstance(idx, slice):
return self.__class__(self.data[idx])
return self.data[idx]
-
+
def __iter__(self):
return self.data.__iter__()
-
+
def __next__(self):
return self.data.__next__()
@@ -160,17 +157,16 @@ def __copy__(self):
def append(self, value):
self.data.append(value)
if self._callbacks:
- self._trigger("append",value)
-
+ self._trigger("append", value)
def insert(self, idx, value):
if self._callbacks:
- self._trigger("insert",value)
+ self._trigger("insert", value)
self.data.insert(idx, value)
def pop(self, idx=-1):
if self._callbacks:
- self._trigger("pop",idx)
+ self._trigger("pop", idx)
return self.data.pop(idx)
def remove(self, value):
@@ -201,133 +197,136 @@ def sort(self, /, *args, **kwds):
def extend(self, other):
data = other.data if isinstance(other, TraceableList) else other
self.data.extend(data)
- self._trigger("extend", value = data)
+ self._trigger("extend", value=data)
def to_list(self):
return self.data
-
+
+
def empty_copy(obj):
class Empty(obj.__class__):
- def __init__(self): pass
- newcopy = Empty( )
+ def __init__(self):
+ pass
+
+ newcopy = Empty()
newcopy.__class__ = obj.__class__
- return newcopy
+ return newcopy
+
class ABCMetaQObject(ABCMeta, type(QtCore.QObject)):
pass
class AbstractInputItem(QtCore.QObject, metaclass=ABCMetaQObject):
- ''' base class for input items for MIDI, OSC and KEYBOARD items '''
+ """base class for input items for MIDI, OSC and KEYBOARD items"""
def __init__(self):
-
super().__init__()
import uuid
- self._id = uuid.uuid4() # GUID (unique) if loaded from XML - will reload that one
- self._guid = str(self.id).replace("-","")
+
+ self._id = (
+ uuid.uuid4()
+ ) # GUID (unique) if loaded from XML - will reload that one
+ self._guid = str(self.id).replace("-", "")
self._display_name = None
self._description = None
self._input_description = None
self._axis_value = None
- self._button_value = False # true if the equivalent of "pressed"
+ self._button_value = False # true if the equivalent of "pressed"
@property
def guid(self):
- ''' id in string format '''
+ """id in string format"""
return self._guid
-
+
@property
def id(self):
return self._id
-
+
@id.setter
def id(self, value):
self._id = value
- self._guid = str(value).replace("-","")
+ self._guid = str(value).replace("-", "")
@property
def display_name(self):
- ''' display name for this input '''
+ """display name for this input"""
return self._display_name
-
- def setDisplayName(self, value : str):
+
+ def setDisplayName(self, value: str):
self._display_name = value
-
+
@property
def description(self) -> str:
return self._description
-
- def setDescription(self, value : str):
+
+ def setDescription(self, value: str):
self._description = value
-
@property
def input_description(self) -> str:
return self._input_description
-
- def setInputDescription(self, value : str):
+
+ def setInputDescription(self, value: str):
self._input_description = value
-
@property
def axis_value(self) -> float:
- ''' gets the current axis value '''
+ """gets the current axis value"""
return self.getAxisValue()
-
+
def getAxisValue(self):
if self._axis_value is None:
return 0.0
return self._axis_value
-
- def setAxisValue(self, value : float):
- ''' sets the axis value and triggers a joystick input event
-
+
+ def setAxisValue(self, value: float):
+ """sets the axis value and triggers a joystick input event
+
:param value: the floating point value to set (-1 to +1)
:param emit: flag to trigger a joystick event if the value is set
-
- '''
+
+ """
if self.axis_value is None or value != self._axis_value:
self._axis_value = value
-
def getOverrideInputType(self):
- # override input type
+ # override input type
return None
@property
def button_value(self) -> bool:
return self._button_value
-
+
def setButtonValue(self, value: bool):
self._button_value = value
-
@property
def message_key(self):
- assert False,"message_key property must be implemented by subclasses"
+ assert False, "message_key property must be implemented by subclasses"
@abstractmethod
def to_xml(self):
- ''' must implement '''
+ """must implement"""
pass
@abstractmethod
def parse_xml(self):
- ''' must implement '''
+ """must implement"""
pass
def __getstate__(self):
- ''' manual pickle to XML '''
+ """manual pickle to XML"""
return self.to_xml()
-
+
def __setstate__(self, data):
- ''' manual unpickle '''
+ """manual unpickle"""
self.parse_xml(data)
class SpecialInputItem(AbstractInputItem):
- ''' specialized input item '''
+ """specialized input item"""
+
def __init__(self, name):
super().__init__()
self._display_name = name
@@ -336,53 +335,50 @@ def __init__(self, name):
@property
def message_key(self):
return self.display_name
-
+
def __str__(self):
return "special"
-
+
pickle_targets = {}
-class PickleTarget():
- ''' helper class to pickle objects that don't want to be pickled
-
+
+class PickleTarget:
+ """helper class to pickle objects that don't want to be pickled
+
The way this works is we store the object to pickle in a local cache, give it a unique ID, and use that as the pickled value because the ID does pickle.
When the object is unpickled, we retrieve the object from the cache, remove it from the cache and return the original.
Pickling is automatic and occurs when cloning objects for example.
- '''
+ """
def __init__(self, item):
self._item = item
def __getstate__(self):
- ''' pickle '''
+ """pickle"""
from gremlin.util import get_guid
+
id = get_guid()
pickle_targets[id] = self.item
self.id = id
- #print (f"pickled to id: {id}")
+ # print (f"pickled to id: {id}")
return self.id
-
+
def __setstate__(self, id):
- ''' unpickle '''
- #print (f"pickled from id: {id}")
+ """unpickle"""
+ # print (f"pickled from id: {id}")
if id in pickle_targets:
- #print ("target found")
+ # print ("target found")
self.item = pickle_targets[id]
del pickle_targets[id]
return self
-
+
@property
def item(self):
return self._item
-
+
@item.setter
def item(self, value):
self._item = value
-
-
-
-
-
\ No newline at end of file
diff --git a/gremlin/base_conditions.py b/gremlin/base_conditions.py
index e6b05481..84e0e2ee 100644
--- a/gremlin/base_conditions.py
+++ b/gremlin/base_conditions.py
@@ -1,6 +1,5 @@
from __future__ import annotations
-from abc import abstractmethod, ABCMeta
-import enum
+from abc import ABCMeta
import logging
from lxml import etree as ElementTree
import gremlin.base_profile
@@ -8,8 +7,8 @@
from gremlin.input_types import InputType
import gremlin.shared_state
import gremlin.util
-from gremlin.util import safe_format, safe_read, parse_bool, parse_guid, write_guid
-from PySide6 import QtWidgets, QtCore, QtGui
+from gremlin.util import safe_read, parse_bool, parse_guid, write_guid
+from PySide6 import QtCore
from gremlin.singleton_decorator import SingletonDecorator
from gremlin.types import ActivationRule
import dinput
@@ -17,14 +16,11 @@
syslog = logging.getLogger("system")
-
class ABCMetaQObject(ABCMeta, type(QtCore.QObject)):
pass
-
class AbstractCondition(QtCore.QObject, metaclass=ABCMetaQObject):
-
"""Base class of all individual condition representations."""
id_changed = QtCore.Signal(str, str) # triggers when the ID changes
@@ -33,21 +29,23 @@ def __init__(self):
"""Creates a new condition."""
super().__init__()
import gremlin.util
+
self._id = gremlin.util.get_guid()
self._comparison = ""
- self._id = None # unique ID of this condition
-
+ self._id = None # unique ID of this condition
+
@property
def id(self):
- ''' unique ID for this condition, persisted '''
+ """unique ID for this condition, persisted"""
if not self._id:
import gremlin.util
+
self._id = gremlin.util.get_guid()
return self._id
-
+
@id.setter
def id(self, new_id):
- ''' changes the ID '''
+ """changes the ID"""
old_id = self._id
if old_id != new_id:
self._id = new_id
@@ -56,13 +54,12 @@ def id(self, new_id):
@property
def comparison(self):
return self._comparison
-
+
@comparison.setter
def comparison(self, value):
self._comparison = value
-
- def from_xml(self, node, data = None):
+ def from_xml(self, node, data=None):
"""Populates the object with data from an XML node.
:param node the XML node to parse for data
@@ -72,7 +69,6 @@ def from_xml(self, node, data = None):
if str_id:
self._id = str_id
-
def to_xml(self):
"""Returns an XML node containing the objects data.
@@ -81,7 +77,6 @@ def to_xml(self):
node = ElementTree.Element("condition")
node.set("condition_id", self._id)
return node
-
def is_valid(self):
"""Returns whether or not a condition is fully specified.
@@ -92,7 +87,6 @@ def is_valid(self):
class KeyboardCondition(AbstractCondition):
-
"""Keyboard state based condition.
The condition is for a single key and as such contains the key's scan
@@ -107,7 +101,7 @@ def __init__(self):
self.is_extended = None
self.comparison = "pressed"
- def from_xml(self, node, data = None):
+ def from_xml(self, node, data=None):
"""Populates the object with data from an XML node.
:param node the XML node to parse for data
@@ -119,29 +113,26 @@ def from_xml(self, node, data = None):
self.is_extended = parse_bool(safe_read(node, "extended"))
input_item = None
for child in node:
- if child.tag=="input":
- from gremlin.keyboard import Key
+ if child.tag == "input":
from gremlin.ui.keyboard_device import KeyboardInputItem
+
input_item = KeyboardInputItem()
input_item.parse_xml(child, data)
-
self.input_item = input_item
-
-
def to_xml(self):
"""Returns an XML node containing the objects data.
:return XML node containing the object's data
"""
- node = super().to_xml() #ElementTree.Element("condition")
+ node = super().to_xml() # ElementTree.Element("condition")
node.set("condition-type", "keyboard")
node.set("input", "keyboard")
node.set("comparison", str(self.comparison))
node.set("scan-code", str(self.scan_code))
node.set("extended", str(self.is_extended))
-
+
if self.input_item:
child = self.input_item.to_xml()
node.append(child)
@@ -153,19 +144,20 @@ def is_valid(self):
:return True if the condition is properly specified, False otherwise
"""
- return super().is_valid() and \
- self.scan_code is not None and \
- self.is_extended is not None
-
+ return (
+ super().is_valid()
+ and self.scan_code is not None
+ and self.is_extended is not None
+ )
def __str__(self):
from gremlin.ui.keyboard_device import Key
+
key = Key(scan_code=self.scan_code, is_extended=self.is_extended)
return f"Keyboard condition: id: {self.id} comparison: {self.comparison} key {key.debug_name}"
class JoystickCondition(AbstractCondition):
-
"""Joystick state based condition.
This condition is based on the state of a joystick axis, button, or hat.
@@ -174,14 +166,16 @@ class JoystickCondition(AbstractCondition):
def __init__(self):
"""Creates a new instance."""
super().__init__()
- self.device_guid = 0 # use this as the invalid GUID
+ self.device_guid = 0 # use this as the invalid GUID
self.input_type = None
self.input_id = 0
self.range = [0.0, 0.0]
self.device_name = ""
- self.use_calibrated_data = True # true if the input should use the calibrated data if any
+ self.use_calibrated_data = (
+ True # true if the input should use the calibrated data if any
+ )
- def from_xml(self, node, data = None):
+ def from_xml(self, node, data=None):
"""Populates the object with data from an XML node.
:param node the XML node to parse for data
@@ -201,23 +195,22 @@ def from_xml(self, node, data = None):
comparison = "center"
self.comparison = comparison
-
self.input_id = safe_read(node, "id", int, 1)
self.device_guid = parse_guid(node.get("device-guid"))
self.device_name = safe_read(node, "device-name", str, "")
self.range = [
safe_read(node, "range-low", float, 0),
- safe_read(node, "range-high", float, 0)
+ safe_read(node, "range-high", float, 0),
]
- self.use_calibrated_data = safe_read(node,"use-calibrated",bool,False)
+ self.use_calibrated_data = safe_read(node, "use-calibrated", bool, False)
def to_xml(self):
"""Returns an XML node containing the objects data.
:return XML node containing the object's data
"""
- #node = ElementTree.Element("condition")
- node = super().to_xml()
+ # node = ElementTree.Element("condition")
+ node = super().to_xml()
node.set("comparison", str(self.comparison))
node.set("condition-type", "joystick")
node.set("input", InputType.to_string(self.input_type))
@@ -226,7 +219,7 @@ def to_xml(self):
node.set("device-name", str(self.device_name))
node.set("range-low", str(self.range[0]))
node.set("range-high", str(self.range[1]))
-
+
node.set("use-calibrated", str(self.use_calibrated_data))
return node
@@ -235,14 +228,15 @@ def is_valid(self):
:return True if the condition is properly specified, False otherwise
"""
- return self.input_type is not None # super().is_valid() and self.input_type is not None
+ return (
+ self.input_type is not None
+ ) # super().is_valid() and self.input_type is not None
def __str__(self):
return f"Joystick Condition: id: {self.id} comparison: {self.comparison} input type: {self.input_type.name} device: {self.device_name} input id: {self.input_id} range: [{self.range[0]:0.3f},{self.range[0]:0.3f}] use calibrated: {self.use_calibrated_data}"
class VJoyCondition(AbstractCondition):
-
"""vJoy device state based condition.
This condition is based on the state of a vjoy axis, button, or hat.
@@ -256,7 +250,7 @@ def __init__(self):
self.input_id = 0
self.range = [0.0, 0.0]
- def from_xml(self, node, data = None):
+ def from_xml(self, node, data=None):
"""Populates the object with data from an XML node.
Parameters
@@ -273,7 +267,7 @@ def from_xml(self, node, data = None):
self.vjoy_id = safe_read(node, "vjoy-id", int)
self.range = [
safe_read(node, "range-low", float, 0),
- safe_read(node, "range-high", float, 0)
+ safe_read(node, "range-high", float, 0),
]
def to_xml(self):
@@ -284,8 +278,8 @@ def to_xml(self):
ElementTree.Element
XML node containing the object's data
"""
- #node = ElementTree.Element("condition")
- node = super().to_xml()
+ # node = ElementTree.Element("condition")
+ node = super().to_xml()
node.set("comparison", str(self.comparison))
node.set("condition-type", "vjoy")
node.set("input", InputType.to_string(self.input_type))
@@ -293,7 +287,7 @@ def to_xml(self):
node.set("vjoy-id", write_guid(self.vjoy_id))
node.set("range-low", str(self.range[0]))
node.set("range-high", str(self.range[1]))
-
+
return node
def is_valid(self):
@@ -305,11 +299,9 @@ def is_valid(self):
def __str__(self):
return f"Vjoy Condition: id: {self.id} comparison: {self.comparison} input type: {self.input_type.name} vjoy device: {self.vjoy_id} input id: {self.input_id} range: [{self.range[0]:0.3f},{self.range[0]:0.3f}]"
-
class InputActionCondition(AbstractCondition):
-
"""Input item press / release state based condition.
The condition is for the current input item, triggering based on whether
@@ -320,22 +312,21 @@ def __init__(self):
"""Creates a new instance."""
super().__init__()
- def from_xml(self, node, data = None):
+ def from_xml(self, node, data=None):
"""Populates the object with data from an XML node.
:param node the XML node to parse for data
"""
super().from_xml(node, data)
self.comparison = safe_read(node, "comparison")
-
def to_xml(self):
"""Returns an XML node containing the objects data.
:return XML node containing the object's data
"""
- #node = ElementTree.Element("condition")
- node = super().to_xml()
+ # node = ElementTree.Element("condition")
+ node = super().to_xml()
node.set("condition-type", "action")
node.set("input", "action")
node.set("comparison", str(self.comparison))
@@ -343,19 +334,19 @@ def to_xml(self):
def __str__(self):
return f"Input Condition: id: {self.id} comparison: {self.comparison}"
-
class AbstractFunctor(QtCore.QObject):
-
"""Abstract base class defining the interface for functor like classes.
These classes are used in the internal code execution system.
"""
- functor_complete = QtCore.Signal() # fires when a functor has completed its execution completely
+ functor_complete = (
+ QtCore.Signal()
+ ) # fires when a functor has completed its execution completely
- def __init__(self, action_data, parent = None):
+ def __init__(self, action_data, parent=None):
"""Creates a new instance, extracting needed information.
:param instance the object which contains the information needed to
@@ -370,18 +361,14 @@ def __init__(self, action_data, parent = None):
self.node = parent
self.action_data = action_data
self.id = action_data.id
- self.manual_callback = False # functor uses automatic mode
-
+ self.manual_callback = False # functor uses automatic mode
el = gremlin.event_handler.EventListener()
el.profile_start.connect(self.profile_start)
el.profile_stop.connect(self.profile_stop)
- el.abort.connect(self.profile_stop) # abort also stops the profile
+ el.abort.connect(self.profile_stop) # abort also stops the profile
-
-
-
- def process_event(self, event, value, extra_data = None):
+ def process_event(self, event, value, extra_data=None):
"""Processes the functor using the provided event and value data.
:param event the raw event that caused the functor to be executed
@@ -393,65 +380,71 @@ def process_event(self, event, value, extra_data = None):
pass
def profile_start(self):
- ''' called when the profile starts '''
+ """called when the profile starts"""
pass
def profile_stop(self):
- ''' called when the profile stops '''
+ """called when the profile stops"""
pass
-
- @property
+
+ @property
def profile_mode(self) -> str:
- ''' gets the mode of this action '''
+ """gets the mode of this action"""
return self.action_data.get_mode()
-
+
@property
def hardware_device_guid(self) -> dinput.GUID:
- ''' gets the currently attached hardware GUID '''
+ """gets the currently attached hardware GUID"""
return self.action_data.hardware_device_guid
-
+
@property
def hardware_device_id(self) -> str:
- ''' gets the currently attached hardware GUID '''
+ """gets the currently attached hardware GUID"""
return self.action_data.hardware_device_id
-
- @property
+
+ @property
def hardware_input_id(self):
return self.action_data.hardware_input_id
-
+
@property
def hardware_input_type(self) -> InputType:
return self.action_data.hardware_input_type
def latch_extra_inputs(self):
- ''' returns any extra inputs as a list of (device_guid, input_id) to latch to this action (trigger on change) '''
+ """returns any extra inputs as a list of (device_guid, input_id) to latch to this action (trigger on change)"""
return []
-
+
def getContainerNode(self):
- ''' gets the container node the action belongs to '''
+ """gets the container node the action belongs to"""
import gremlin.execution_graph
+
if self.node:
- container_node = None
for node in self.node.ancestors:
- if node.nodeType == gremlin.execution_graph.ExecutionGraphNodeType.Container:
+ if (
+ node.nodeType
+ == gremlin.execution_graph.ExecutionGraphNodeType.Container
+ ):
return node
return None
-
+
def getSiblings(self) -> list:
- ''' gets action node siblings'''
+ """gets action node siblings"""
import gremlin.execution_graph
+
container_node = self.getContainerNode()
nodes = []
if container_node:
# grab all the curve nodes attached to that container
for node in container_node.descendants:
- if node.nodeType == gremlin.execution_graph.ExecutionGraphNodeType.Action:
+ if (
+ node.nodeType
+ == gremlin.execution_graph.ExecutionGraphNodeType.Action
+ ):
nodes.append(node)
return nodes
-
def _check_for_auto_release(self, action):
- ''' auto release check for functors '''
+ """auto release check for functors"""
activation_condition = None
if action.parent.activation_condition:
activation_condition = action.parent.activation_condition
@@ -470,7 +463,7 @@ def _check_for_auto_release(self, action):
needs_auto_release = False
return needs_auto_release
-
+
def __str__(self):
if self.action_data:
return str(self.action_data)
@@ -478,9 +471,10 @@ def __str__(self):
class AbstractContainerActionFunctor(AbstractFunctor):
- ''' used by action functors for actions that have containers '''
- def process_event(self, event, value, extra_data = None):
- ''' Processes the functor using the provided event '''
+ """used by action functors for actions that have containers"""
+
+ def process_event(self, event, value, extra_data=None):
+ """Processes the functor using the provided event"""
result = True
for functor in self.action_data.functors:
# only fire the appropriate type
@@ -491,43 +485,44 @@ def process_event(self, event, value, extra_data = None):
break
return result
-
-class ConditionTrackerData():
+
+class ConditionTrackerData:
def __init__(self, mode, input_item, container, condition, rule):
self.condition = condition
self.container = container
self.input_item = input_item
self.mode = mode
self.rule = rule
-
+
+
@SingletonDecorator
-class ConditionTracker():
- ''' tracks conditions '''
+class ConditionTracker:
+ """tracks conditions"""
+
def __init__(self):
import gremlin.event_handler
- self._cache = {} # map of known conditions keyed by mode and condition ID
- self._owner_map = {} # map of condition ID to its input item owner so we know which input item has which condition
- self._data_map = {} # map of condition ID to tracker data
+
+ self._cache = {} # map of known conditions keyed by mode and condition ID
+ self._owner_map = {} # map of condition ID to its input item owner so we know which input item has which condition
+ self._data_map = {} # map of condition ID to tracker data
self._el = gremlin.event_handler.EventListener()
self._el.shutdown.connect(self.reset)
self._el.profile_unloaded.connect(self.reset)
-
+
@QtCore.Slot()
def reset(self):
- ''' triggered on app exit or profile unload '''
+ """triggered on app exit or profile unload"""
self._cache.clear()
self._owner_map.clear()
self._data_map.clear()
-
-
- def registerCondition(self, data : ConditionTrackerData):
- ''' registers a condition and its owner - owner is an input_item'''
+ def registerCondition(self, data: ConditionTrackerData):
+ """registers a condition and its owner - owner is an input_item"""
mode = data.mode
condition = data.condition
input_item = data.input_item
- if not mode in self._cache:
+ if mode not in self._cache:
self._cache[mode] = {}
self._cache[mode][condition.id] = data
self._owner_map[condition.id] = input_item
@@ -537,13 +532,14 @@ def registerCondition(self, data : ConditionTrackerData):
verbose = gremlin.config.Configuration().verbose_mode_condition
if verbose:
syslog = logging.getLogger("system")
- syslog.info(f"creating condition: {condition.id} for input: {data.input_item.display_name if hasattr(data.input_item,"display_name") else data.input_item} mode: {data.mode}")
+ syslog.info(
+ f"creating condition: {condition.id} for input: {data.input_item.display_name if hasattr(data.input_item,"display_name") else data.input_item} mode: {data.mode}"
+ )
data.condition.id_changed.connect(self._condition_id_changed)
-
@QtCore.Slot(str, str)
def _condition_id_changed(self, old_id, new_id):
- ''' handle an ID swap for the condition in the tracking objects '''
+ """handle an ID swap for the condition in the tracking objects"""
if old_id in self._owner_map:
input_item = self._owner_map[old_id]
self._owner_map[new_id] = input_item
@@ -551,10 +547,9 @@ def _condition_id_changed(self, old_id, new_id):
data = self._data_map[old_id]
self._data_map[new_id] = data
del self._data_map[old_id]
-
- def unregisterCondition(self, condition : AbstractCondition):
- ''' unregisters a condition '''
+ def unregisterCondition(self, condition: AbstractCondition):
+ """unregisters a condition"""
syslog = logging.getLogger("system")
syslog.info(f"delete condition: {condition.id}")
id = condition.id
@@ -564,92 +559,103 @@ def unregisterCondition(self, condition : AbstractCondition):
del self._cache[mode][id]
del self._owner_map[id]
# (input_item, mode, condition)
- self._el.condition_removed.emit(data.input_item, data.mode, data.condition)
+ self._el.condition_removed.emit(
+ data.input_item, data.mode, data.condition
+ )
self._el.condition_state_changed.emit(data.container)
return
-
def count(self):
- ''' gets a count of registered conditions '''
+ """gets a count of registered conditions"""
return len(self._cache)
-
- def getInputItemConditionCount(self, input_item, mode : str = None):
- ''' gets a count of registered condition for a specific owner - owner is an input_item'''
+
+ def getInputItemConditionCount(self, input_item, mode: str = None):
+ """gets a count of registered condition for a specific owner - owner is an input_item"""
import gremlin.shared_state
+
if not mode:
mode = gremlin.shared_state.current_mode
if mode in self._cache:
- id_list = [item.condition.id for item in self._cache[mode].values() if item.input_item == input_item]
+ id_list = [
+ item.condition.id
+ for item in self._cache[mode].values()
+ if item.input_item == input_item
+ ]
return len(id_list)
return 0
-
- def getContainerConditionCount(self, container, mode : str = None):
- ''' gets a count of registered condition for a specific owner - owner is an input_item'''
+
+ def getContainerConditionCount(self, container, mode: str = None):
+ """gets a count of registered condition for a specific owner - owner is an input_item"""
import gremlin.shared_state
+
if not mode:
mode = gremlin.shared_state.current_mode
if mode in self._cache:
- id_list = [item.condition.id for item in self._cache[mode].values() if item.container == container]
+ id_list = [
+ item.condition.id
+ for item in self._cache[mode].values()
+ if item.container == container
+ ]
return len(id_list)
return 0
-
- def getConditionInputItem(self, condition : AbstractCondition):
- ''' gets the input item attached to a condition '''
+ def getConditionInputItem(self, condition: AbstractCondition):
+ """gets the input item attached to a condition"""
id = condition.id
if id in self._owner_map:
return self._owner_map[id]
return None
-
- def getConditionsForInputItem(self, input_item, mode : str):
- ''' checks to see if conditions are defined for this input item'''
+
+ def getConditionsForInputItem(self, input_item, mode: str):
+ """checks to see if conditions are defined for this input item"""
import gremlin.shared_state
+
if not mode:
mode = gremlin.shared_state.current_mode
if mode in self._cache:
- id_list = [id for id, item in self._owner_map[mode].items() if item == input_item]
+ id_list = [
+ id for id, item in self._owner_map[mode].items() if item == input_item
+ ]
conditions = [self._cache[mode][id] for id in id_list]
return conditions
return None
-
+
def getConditionForAction(self, action):
- ''' gets a condition for an action '''
- data : ConditionTrackerData = self.getActionData(action)
+ """gets a condition for an action"""
+ data: ConditionTrackerData = self.getActionData(action)
if data:
return data.condition
return None
-
+
def getRuleForAction(self, action):
- ''' gets the condition rule for an action '''
- data : ConditionTrackerData = self.getActionData(action)
+ """gets the condition rule for an action"""
+ data: ConditionTrackerData = self.getActionData(action)
if data:
return data.rule
return None
-
- def owner(self, condition : AbstractCondition):
- ''' what input item owns the condition '''
+ def owner(self, condition: AbstractCondition):
+ """what input item owns the condition"""
if condition.id in self._cache:
return self._owner_map[condition.id]
return None
- def getData(self, condition : AbstractCondition):
- ''' gets the condition tracking data '''
+ def getData(self, condition: AbstractCondition):
+ """gets the condition tracking data"""
if condition.id in self._data_map:
return self._data_map[condition.id]
return None
-
+
def getActionData(self, action):
if action.action_id in self._data_map:
return self._data_map[action.action_id]
return None
-
class ActivationCondition(QtCore.QObject):
-
"""Dictates under what circumstances an associated code can be executed."""
- id_changed = QtCore.Signal(str, str) # fires when id changes (old_id, new_id)
+
+ id_changed = QtCore.Signal(str, str) # fires when id changes (old_id, new_id)
rule_lookup = {
# String to enum
@@ -674,25 +680,25 @@ def __init__(self, conditions, rule):
self.conditions = conditions
self._id = gremlin.util.get_guid()
- @property
+ @property
def rule(self) -> ActivationRule:
# rule for the activation condition
return self._rule
@rule.setter
- def rule(self, value : ActivationRule):
+ def rule(self, value: ActivationRule):
self._rule = value
-
@property
def id(self):
- ''' unique ID for this condition, persisted '''
+ """unique ID for this condition, persisted"""
if not self._id:
import gremlin.util
+
self._id = gremlin.util.get_guid()
return self._id
-
+
@id.setter
def id(self, new_id):
old_id = self._id
@@ -700,9 +706,7 @@ def id(self, new_id):
self._id = new_id
self.id_changed.emit(old_id, new_id)
-
-
- def from_xml(self, node, data = None):
+ def from_xml(self, node, data=None):
"""Extracts activation condition data from an XML node.
:param node: the XML node to parse
@@ -716,7 +720,6 @@ def from_xml(self, node, data = None):
if str_id:
self._id = str_id
-
rule = ActivationCondition.rule_lookup[safe_read(node, "rule")]
tracker = ConditionTracker()
mode_node = node
@@ -726,17 +729,21 @@ def from_xml(self, node, data = None):
mode = mode_node.get("name")
else:
import gremlin.shared_state
+
mode = gremlin.shared_state.edit_mode
- assert data is not None,"XML: error: data not provided for activation condition"
+ assert (
+ data is not None
+ ), "XML: error: data not provided for activation condition"
input_item, container = data
self.rule = rule
-
- #assert input_item is not None,"XML: error:input_item not provided for activation condition"
+ # assert input_item is not None,"XML: error:input_item not provided for activation condition"
if input_item is None:
- gremlin.ui.ui_common.MessageBox(prompt="The source action does not support pasting conditions to the new input.")
+ gremlin.ui.ui_common.MessageBox(
+ prompt="The source action does not support pasting conditions to the new input."
+ )
return
-
+
for cond_node in node.findall("condition"):
condition_type = safe_read(cond_node, "condition-type")
condition = ActivationCondition.condition_lookup[condition_type]()
@@ -744,8 +751,6 @@ def from_xml(self, node, data = None):
self.conditions.append(condition)
item = ConditionTrackerData(mode, input_item, container, condition, rule)
tracker.registerCondition(item)
-
-
def to_xml(self):
"""Returns an XML node containing the activation condition information.
@@ -760,9 +765,9 @@ def to_xml(self):
# save the condition, valid or not so the data is saved
node.append(condition.to_xml())
return node
-
+
def condition_name(self):
return f"Activation Condition: [{self.id}] rule: {self._rule.name} contains: {len(self.conditions)} condition(s)"
-
+
def __str__(self):
- return f"Activation Condition: [{self.id}] rule: {self._rule.name} contains: {len(self.conditions)} condition(s)"
\ No newline at end of file
+ return f"Activation Condition: [{self.id}] rule: {self._rule.name} contains: {len(self.conditions)} condition(s)"
diff --git a/gremlin/base_profile.py b/gremlin/base_profile.py
index 5b791444..0a56d093 100644
--- a/gremlin/base_profile.py
+++ b/gremlin/base_profile.py
@@ -17,13 +17,9 @@
from abc import abstractmethod, ABCMeta
-from collections import namedtuple
-import codecs
import collections
import os
-import copy
import logging
-import time
import gremlin.actions
import gremlin.base_buttons
@@ -49,8 +45,7 @@
from gremlin.plugin_manager import ContainerPlugins
from gremlin.base_conditions import *
from gremlin.base_buttons import VirtualAxisButton, VirtualHatButton
-from gremlin.input_types import InputType
-from gremlin.plugin_manager import ActionPlugins, ContainerPlugins
+from gremlin.plugin_manager import ActionPlugins
import gremlin.joystick_handling
import gremlin.profile
import gremlin.input_devices
@@ -669,12 +664,12 @@ def _parse_action_xml(self, node, action_set, data = None):
tag = child.tag
if config.convert_response_curve and gremlin.base_profile._is_curve_tag(tag):
tag = "response-curve-ex"
- if not tag in action_name_map:
+ if tag not in action_name_map:
# new mapper not found
tag = child.tag
elif config.convert_vjoy_remap and tag == "remap":
tag = "vjoyremap"
- if not tag in action_name_map:
+ if tag not in action_name_map:
# new mapper not found
tag = child.tag
@@ -1128,7 +1123,7 @@ def get_item_data(self, index, autocreate = True):
'''
- if autocreate and not index in self._item_data_map.keys():
+ if autocreate and index not in self._item_data_map.keys():
# get the input item behind the parent action
current = self.parent
while current and not isinstance(current, InputItem):
@@ -1551,7 +1546,7 @@ def input_id(self):
@input_id.setter
def input_id(self, value):
from gremlin.base_classes import AbstractInputItem
- assert value == None or isinstance(value, int) or isinstance(value, AbstractInputItem)
+ assert value is None or isinstance(value, int) or isinstance(value, AbstractInputItem)
self._input_id = value
self._update_input()
@@ -1725,7 +1720,7 @@ def from_xml(self, node, data = None):
if "id" in node.attrib and node.tag == "key":
# legacy format
scan_code = safe_read(node, "id", int, 0)
- description = safe_read(node, "description", str, "")
+ safe_read(node, "description", str, "")
key = Key(scan_code=scan_code, is_extended=False, is_mouse = False)
input_item.key = key
else:
@@ -1739,7 +1734,7 @@ def from_xml(self, node, data = None):
for child in node:
if child.tag == "latched":
latched_key = Key(scan_code=safe_read(child,"id",int), is_extended= read_bool(child,"extended"))
- if not latched_key in key.latched_keys:
+ if latched_key not in key.latched_keys:
key.latched_keys.append(latched_key)
else:
# new style
@@ -1814,7 +1809,7 @@ def from_xml(self, node, data = None):
if child.tag in ("latched", "input", "keylatched") or gremlin.base_profile._is_curve_tag(child.tag):
# ignore extra data
continue
- if not "type" in child.attrib:
+ if "type" not in child.attrib:
syslog.error(
f"XML {child.tag} is missing container 'type' attribute"
)
@@ -1837,7 +1832,6 @@ def from_xml(self, node, data = None):
def is_valid_for_save(self) -> bool:
''' true if the item has something to save to a profile '''
- from gremlin.keyboard import Key
if self.input_type in (InputType.Keyboard, InputType.KeyboardLatched):
# if isinstance(self.input_id, Key):
# # has a key definition, save
@@ -2088,7 +2082,7 @@ def _edit_mode_changed_cb(self):
remove_list = []
for device in self.vjoy_devices.values():
for mode in device.modes.keys():
- if not mode in modes:
+ if mode not in modes:
remove_list.append(mode)
for mode in remove_list:
@@ -2097,7 +2091,7 @@ def _edit_mode_changed_cb(self):
# check default startup mode
mode = self._default_start_mode
- if not mode in modes:
+ if mode not in modes:
self._default_start_mode = self.get_default_mode()
@@ -2510,7 +2504,7 @@ def remove_mode(self, name, force = False, emit = True):
import gremlin.event_handler
mode_list = self.mode_list()
- if not name in self.mode_list():
+ if name not in self.mode_list():
syslog.warning(f"Remove Mode: error: mode {name} not found")
return False
@@ -2602,9 +2596,9 @@ def get_mode_map(self, casefold = False) -> dict:
self._ensure_mode_tree()
if self._mode_tree:
if casefold:
- modes = [node.name.casefold() for node in self._mode_tree.descendants]
+ [node.name.casefold() for node in self._mode_tree.descendants]
else:
- modes = [node.name for node in self._mode_tree.descendants]
+ [node.name for node in self._mode_tree.descendants]
for node in anytree.PreOrderIter(self._mode_tree):
mode_name = node.name
@@ -2637,7 +2631,7 @@ def reload_modes(self, update_devices = False):
for device in self.devices.values():
for mode in device.modes.values():
mode_name = mode.name
- if not mode_name in mode_list:
+ if mode_name not in mode_list:
mode_list.append(mode_name)
node = Node(mode_name)
@@ -2645,7 +2639,7 @@ def reload_modes(self, update_devices = False):
parent_mode_name = mode.inherit
if parent_mode_name:
# find the parent node, create if it does not exist
- if not parent_mode_name in node_map:
+ if parent_mode_name not in node_map:
parent_node = Node(parent_mode_name)
parent_node.name = parent_mode_name
parent_node.parent = self._mode_tree
@@ -2803,7 +2797,7 @@ def from_xml(self, fname, data = None):
:param fname the path to the XML file to parse
"""
# Check for outdated profile structure and warn user / convert
- verbose = gremlin.config.Configuration().verbose
+ gremlin.config.Configuration().verbose
profile_converter = gremlin.profile.ProfileConverter()
profile_was_updated = False
if not profile_converter.is_current(fname):
@@ -2870,7 +2864,7 @@ def from_xml(self, fname, data = None):
if "inherit" in mode_node.attrib:
parent_mode = mode_node.get("inherit")
- if not parent_mode in mode_node_map:
+ if parent_mode not in mode_node_map:
tree_parent_mode = ModeNode(parent_mode)
nodes[parent_mode] = tree_parent_mode
mode_node_map[parent_mode] = tree_parent_mode
@@ -2878,7 +2872,7 @@ def from_xml(self, fname, data = None):
else:
parent_mode = None
- if not mode in mode_node_map:
+ if mode not in mode_node_map:
tree_node = ModeNode(mode)
mode_node_map[mode] = tree_node
nodes[mode] = tree_node
@@ -2944,7 +2938,7 @@ def from_xml(self, fname, data = None):
# update missing modes from devices
for device in self.devices.values():
device_modes = [mode.name for mode in device.modes.values()]
- missing_modes = [mode for mode in mode_list if not mode in device_modes]
+ missing_modes = [mode for mode in mode_list if mode not in device_modes]
for mode_name in missing_modes:
mode = Mode(device)
mode.name = mode_name
@@ -3242,7 +3236,7 @@ def get_default_start_mode(self):
self._default_start_mode = self.get_default_mode()
mode = self._default_start_mode
modes = self.get_modes()
- if not mode in modes:
+ if mode not in modes:
self._default_start_mode = self.get_root_mode()
return self._default_start_mode
@@ -3657,7 +3651,7 @@ def from_xml(self, node, data = None):
elif self.type == PluginVariableType.Mode:
self.value = safe_read(node, "value", str, "")
elif self.type == PluginVariableType.PhysicalInput:
- if not "device-guid" in node.attrib:
+ if "device-guid" not in node.attrib:
# partial data save
self.value = {
"device_id": None,
@@ -3673,7 +3667,7 @@ def from_xml(self, node, data = None):
}
elif self.type == PluginVariableType.VirtualInput:
- if not "vjoy-id" in node.attrib:
+ if "vjoy-id" not in node.attrib:
# partial data save
self.value = {
"device_id": None,
@@ -3858,13 +3852,13 @@ def _get_profile_data(self) -> ProfileOptionsData:
restore_last = safe_read(element, "restore_last", bool, False)
force_numlock_off = safe_read(element,"force_numlock", bool, True)
- if not restore_last is None:
+ if restore_last is not None:
for element in tree.xpath("//startup-mode"):
start_mode = element.text
break
- if not restore_last is None:
+ if restore_last is not None:
restore_last = False # default value
@@ -4102,12 +4096,12 @@ def validate(self):
if not (item.process or item.profile):
valid = False
- warning = f"Mapping incomplete"
+ warning = "Mapping incomplete"
self._valid = False
pd = item._get_profile_data()
if pd.mode_list:
- if item.default_mode is not None and not item.default_mode in pd.mode_list:
+ if item.default_mode is not None and item.default_mode not in pd.mode_list:
valid = False
warning = f"Startup mode '{item.default_mode}' does not exist for this profile"
self._valid = False
diff --git a/gremlin/cheatsheet.py b/gremlin/cheatsheet.py
index bccfb643..768a5e6e 100644
--- a/gremlin/cheatsheet.py
+++ b/gremlin/cheatsheet.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,8 +19,16 @@
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import cm
from reportlab.lib.colors import HexColor
-from reportlab.platypus import BaseDocTemplate, Paragraph, \
- Spacer, Frame, PageTemplate, Table, Flowable, PageBreak
+from reportlab.platypus import (
+ BaseDocTemplate,
+ Paragraph,
+ Spacer,
+ Frame,
+ PageTemplate,
+ Table,
+ Flowable,
+ PageBreak,
+)
import gremlin
import gremlin.base_profile
@@ -30,7 +38,7 @@
import os
import gremlin.shared_state
import gremlin.util
-from PySide6 import QtWidgets, QtCore, QtGui
+from PySide6 import QtWidgets, QtCore
from enum import Enum, auto
import gremlin.ui.ui_common
@@ -43,12 +51,11 @@
"south": "S",
"south-west": "SW",
"west": "W",
- "north-west": "NW"
+ "north-west": "NW",
}
class InputItemData:
-
"""Represents the the data about a single InputItem entry."""
style = getSampleStyleSheet()["Normal"]
@@ -59,9 +66,8 @@ def __init__(self, input_item, inherited_from):
:param input_item the InputItem instance this represents
:param inherited_from mode from which this InputItem was inherited
"""
- self.input_item : gremlin.base_profile.InputItem = input_item
+ self.input_item: gremlin.base_profile.InputItem = input_item
self.inherited_from = inherited_from
-
def table_data(self):
"""Returns the data necessary to create the data table.
@@ -78,12 +84,11 @@ def table_data(self):
# Basic information
input_name = format_input_name(
- self.input_item.input_type,
- self.input_item.input_id
+ self.input_item.input_type, self.input_item.input_id
)
inherited = Paragraph(
f"{"" if self.inherited_from is None else self.inherited_from}",
- InputItemData.style
+ InputItemData.style,
)
output = []
@@ -115,17 +120,17 @@ def table_data(self):
if container.button_count == 4:
direction_lookup = ["N", "E", "S", "W"]
elif container.button_count == 8:
- direction_lookup = [
- "N", "NE", "E", "SE", "S", "SW", "W", "NW"
- ]
+ direction_lookup = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]
for i, action_set in enumerate(container.action_sets):
if len(action_set) > 0:
- hat_outputs.append((
- f"{input_name} {direction_lookup[i]}",
- self.extract_action_set_descriptions(action_set),
- inherited
- ))
+ hat_outputs.append(
+ (
+ f"{input_name} {direction_lookup[i]}",
+ self.extract_action_set_descriptions(action_set),
+ inherited,
+ )
+ )
# Virtual button
elif container.virtual_button is not None:
@@ -134,11 +139,13 @@ def table_data(self):
c_dirs.append(f"{input_name} {hat_direction_abbrev[direction]}")
c_input_name = "\n".join(c_dirs)
- hat_outputs.append((
- c_input_name,
- "\n".join(self.extract_description_actions(container)),
- inherited
- ))
+ hat_outputs.append(
+ (
+ c_input_name,
+ "\n".join(self.extract_description_actions(container)),
+ inherited,
+ )
+ )
# Standard hat
else:
@@ -204,12 +211,14 @@ def recursive(device, tree, storage):
for items in mode.config.values():
for item in items.values():
if len(item.containers) > 0:
- storage[parent][(item.input_type, item.input_id)] = \
- InputItemData(item, None)
+ storage[parent][(item.input_type, item.input_id)] = InputItemData(
+ item, None
+ )
for child in children:
- storage[child][(item.input_type, item.input_id)] = \
+ storage[child][(item.input_type, item.input_id)] = (
InputItemData(item, parent)
+ )
# Recursively process the remainder of the inheritance tree
recursive(device, children, storage)
@@ -234,7 +243,6 @@ def sort_data(data):
class DeviceFloat(Flowable):
-
"""Creates a device header element."""
def __init__(self, device_name):
@@ -247,23 +255,18 @@ def __init__(self, device_name):
def draw(self):
self.canv.setFillColor(HexColor("#364151"))
- self.canv.rect(-1.25*cm, 0.0, A4[0]+0.1*cm, cm, stroke=False, fill=True)
+ self.canv.rect(-1.25 * cm, 0.0, A4[0] + 0.1 * cm, cm, stroke=False, fill=True)
self.canv.setFillColor(HexColor("#ffffff"))
- self.canv.drawCentredString(
- A4[0]/2.0,
- 0.35*cm,
- self._device_name
- )
+ self.canv.drawCentredString(A4[0] / 2.0, 0.35 * cm, self._device_name)
def wrap(self, availWidth, availHeight):
- return (A4[0]-2*cm, cm)
+ return (A4[0] - 2 * cm, cm)
def split(self, availWidth, availheight):
return []
class ModeFloat(Flowable):
-
"""Creates a mode header element."""
def __init__(self, mode_name):
@@ -274,9 +277,9 @@ def __init__(self, mode_name):
super().__init__()
self._mode_name = mode_name
- self._bar_offset = 0.1*cm
- self._bar_width = 0.25*cm
- self._bar_height = 0.75*cm
+ self._bar_offset = 0.1 * cm
+ self._bar_width = 0.25 * cm
+ self._bar_height = 0.75 * cm
def _draw_bar(self, path, offset):
"""Draws a single angle bar element.
@@ -288,12 +291,9 @@ def _draw_bar(self, path, offset):
path.lineTo(offset + self._bar_offset + self._bar_width, 0)
path.lineTo(
offset + self._bar_height + self._bar_offset + self._bar_width,
- self._bar_height
- )
- path.lineTo(
- offset + self._bar_height + self._bar_offset,
- self._bar_height
+ self._bar_height,
)
+ path.lineTo(offset + self._bar_height + self._bar_offset, self._bar_height)
path.lineTo(offset + self._bar_offset, 0)
return offset + self._bar_width + self._bar_offset
@@ -301,14 +301,14 @@ def _draw_bar(self, path, offset):
def draw(self):
self.canv.setFillColor(HexColor("#798593"))
- offset = 7*cm
+ offset = 7 * cm
path = self.canv.beginPath()
- path.moveTo(-1.25*cm, 0)
+ path.moveTo(-1.25 * cm, 0)
path.lineTo(offset, 0)
- path.lineTo(offset+self._bar_height, self._bar_height)
- path.lineTo(-1.25*cm, self._bar_height)
- path.lineTo(-1.25*cm, 0)
+ path.lineTo(offset + self._bar_height, self._bar_height)
+ path.lineTo(-1.25 * cm, self._bar_height)
+ path.lineTo(-1.25 * cm, 0)
offset = self._draw_bar(path, offset)
offset = self._draw_bar(path, offset)
@@ -317,14 +317,10 @@ def draw(self):
self.canv.drawPath(path, stroke=False, fill=True)
self.canv.setFillColor(HexColor("#000000"))
self.canv.setFillColor(HexColor("#ffffff"))
- self.canv.drawString(
- 0,
- 0.25*cm,
- self._mode_name
- )
+ self.canv.drawString(0, 0.25 * cm, self._mode_name)
def wrap(self, availWidth, availHeight):
- return (A4[0]-2*cm, 0.75*cm)
+ return (A4[0] - 2 * cm, 0.75 * cm)
def split(self, availWidth, availheight):
return []
@@ -340,12 +336,12 @@ def generate_cheatsheet(fname, profile):
width, height = A4
styles = getSampleStyleSheet()
- main_frame = Frame(cm, cm, width-2*cm, height-2*cm, showBoundary=False)
+ main_frame = Frame(cm, cm, width - 2 * cm, height - 2 * cm, showBoundary=False)
main_template = PageTemplate(id="main", frames=[main_frame])
doc = BaseDocTemplate(fname, pageTemplates=[main_template])
story = []
- style = styles["Normal"]
+ styles["Normal"]
# Build device actions considering inheritance
inheritance_tree = profile.build_inheritance_tree()
@@ -372,22 +368,24 @@ def generate_cheatsheet(fname, profile):
for entry in mode_data.values():
table_data.extend(entry.table_data())
- table_style = style = [
+ table_style = [
("LINEBELOW", (0, 0), (-1, -2), 0.25, HexColor("#c0c0c0")),
- ("VALIGN", (0, 0), (-1, -1), "TOP")
+ ("VALIGN", (0, 0), (-1, -1), "TOP"),
]
if len(table_data) == 1:
table_style = []
- story.append(Table(
- table_data,
- colWidths=[
- 0.15 * (width - 2 * cm),
- 0.30 * (width - 2 * cm),
- 0.55 * (width - 2 * cm)
- ],
- rowHeights=[None] * len(table_data),
- style=table_style
- ))
+ story.append(
+ Table(
+ table_data,
+ colWidths=[
+ 0.15 * (width - 2 * cm),
+ 0.30 * (width - 2 * cm),
+ 0.55 * (width - 2 * cm),
+ ],
+ rowHeights=[None] * len(table_data),
+ style=table_style,
+ )
+ )
story.append(Spacer(1, 0.50 * cm))
if dev_float_added:
@@ -414,15 +412,17 @@ def format_input_name(input_type, identifier):
else:
return f"{InputType.to_display_name(input_type)} {identifier}"
+
class ViewInputMode(Enum):
Device = auto()
Mode = auto()
+
class ViewInput(gremlin.ui.ui_common.QRememberDialog):
- ''' displays a dialog that lets the user pick from a list of mapped inputs '''
+ """displays a dialog that lets the user pick from a list of mapped inputs"""
def __init__(self, parent=None):
- super().__init__(self.__class__.__name__, parent = parent)
+ super().__init__(self.__class__.__name__, parent=parent)
# make modal
self.setWindowModality(QtCore.Qt.ApplicationModal)
self.setMinimumWidth(600)
@@ -430,7 +430,7 @@ def __init__(self, parent=None):
profile = gremlin.shared_state.current_profile
# get a list of mapped objects
- # Build device actions considering inheritance
+ # Build device actions considering inheritance
inheritance_tree = profile.build_inheritance_tree()
map_data = {}
for _, device in profile.devices.items():
@@ -440,16 +440,18 @@ def __init__(self, parent=None):
self._display_mode = ViewInputMode.Mode
self.option_container_widget = QtWidgets.QWidget()
- self.option_container_layout = QtWidgets.QHBoxLayout(self.option_container_widget)
+ self.option_container_layout = QtWidgets.QHBoxLayout(
+ self.option_container_widget
+ )
self.cb_display_by_device_widget = QtWidgets.QRadioButton("By device")
self.cb_display_by_mode_widget = QtWidgets.QRadioButton("By mode")
- if self._display_mode ==ViewInputMode.Device:
+ if self._display_mode == ViewInputMode.Device:
self.cb_display_by_device_widget.setChecked(True)
else:
self.cb_display_by_mode_widget.setChecked(True)
-
+
self.cb_display_by_device_widget.clicked.connect(self._mode_by_device_cb)
self.cb_display_by_mode_widget.clicked.connect(self._mode_by_mode_cb)
self.to_clipboard_widget = QtWidgets.QPushButton()
@@ -463,15 +465,15 @@ def __init__(self, parent=None):
self.option_container_layout.addWidget(self.to_clipboard_widget)
self.option_container_layout.addStretch()
-
self._map_data = map_data
self._tree_widget = QtWidgets.QTreeWidget()
self._tree_widget.setColumnCount(3)
- self._tree_widget.setHeaderLabels(["Input","Mapping", "Value"])
+ self._tree_widget.setHeaderLabels(["Input", "Mapping", "Value"])
header = self._tree_widget.header()
- header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
+ header.setSectionResizeMode(
+ 0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents
+ )
header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeMode.Stretch)
-
self.main_layout = QtWidgets.QVBoxLayout(self)
self.main_layout.addWidget(self.option_container_widget)
@@ -480,59 +482,59 @@ def __init__(self, parent=None):
self._update()
QtCore.Slot()
+
def _mode_by_device_cb(self):
if self.cb_display_by_device_widget.isChecked():
self._display_mode = ViewInputMode.Device
self._update()
QtCore.Slot()
+
def _mode_by_mode_cb(self):
if self.cb_display_by_device_widget.isChecked():
self._display_mode = ViewInputMode.Mode
self._update()
QtCore.Slot()
+
def _to_clipboard_cb(self):
- ''' copies the data to the clipboard '''
+ """copies the data to the clipboard"""
lines = []
it = QtWidgets.QTreeWidgetItemIterator(self._tree_widget)
- item : QtWidgets.QTreeWidgetItem
+ item: QtWidgets.QTreeWidgetItem
while it.value():
item = it.value()
depth = 0
parent = item.parent()
while parent:
- depth +=1
+ depth += 1
parent = parent.parent()
text = f"{'\t'*depth if depth else ''}{item.text(0)}\t{item.text(1)}\t{item.text(2)}\n"
lines.append(text)
- it+=1
-
+ it += 1
+
text = "".join(lines)
gremlin.clipboard.Clipboard().set_windows_clipboard_text(text)
-
-
-
-
def _update(self):
-
nodes = []
-
+
is_mode = self._display_mode == ViewInputMode.Mode
if is_mode:
# display data by mode
mode_nodes = {}
- for dev, dev_data in self._map_data.items():
+ for dev, dev_data in self._map_data.items():
for mode_name, mode_data in dev_data.items():
# Only proceed if we actually have input items available
if len(mode_data.values()) == 0:
continue
if is_mode:
- if not mode_name in mode_nodes.keys():
- mode_node = QtWidgets.QTreeWidgetItem([f"Mode: [{mode_name}]"])
+ if mode_name not in mode_nodes.keys():
+ mode_node = QtWidgets.QTreeWidgetItem(
+ [f"Mode: [{mode_name}]"]
+ )
mode_nodes[mode_name] = mode_node
else:
mode_node = mode_nodes[mode_name]
@@ -547,46 +549,56 @@ def _update(self):
for container in entry.input_item.containers:
for action_set in container.action_sets:
for action in action_set:
- action_node = QtWidgets.QTreeWidgetItem([entry.input_item.display_name, action.name, action.display_name()])
+ action_node = QtWidgets.QTreeWidgetItem(
+ [
+ entry.input_item.display_name,
+ action.name,
+ action.display_name(),
+ ]
+ )
device_node.addChild(action_node)
has_containers = True
if has_containers:
# has data
mode_node.addChild(device_node)
- if not mode_node in nodes:
+ if mode_node not in nodes:
nodes.append(mode_node)
-
+
else:
# display data by device
- for dev, dev_data in self._map_data.items():
+ for dev, dev_data in self._map_data.items():
device_node = QtWidgets.QTreeWidgetItem([f"Device: '{dev.name}'"])
nodes.append(device_node)
-
+
for mode_name, mode_data in dev_data.items():
# Only proceed if we actually have input items available
if len(mode_data.values()) == 0:
continue
mode_node = QtWidgets.QTreeWidgetItem([f"Mode: [{mode_name}]"])
-
+
has_containers = False
for entry in mode_data.values():
if entry.input_item.containers:
for container in entry.input_item.containers:
for action_set in container.action_sets:
for action in action_set:
- action_node = QtWidgets.QTreeWidgetItem([entry.input_item.display_name, action.name, action.display_name()])
+ action_node = QtWidgets.QTreeWidgetItem(
+ [
+ entry.input_item.display_name,
+ action.name,
+ action.display_name(),
+ ]
+ )
mode_node.addChild(action_node)
has_containers = True
if has_containers:
# has data
device_node.addChild(mode_node)
- if not device_node in nodes:
+ if device_node not in nodes:
nodes.append(device_node)
self._tree_widget.clear()
self._tree_widget.insertTopLevelItems(0, nodes)
self._tree_widget.expandAll()
-
-
diff --git a/gremlin/clipboard.py b/gremlin/clipboard.py
index 33936b34..b18f5276 100644
--- a/gremlin/clipboard.py
+++ b/gremlin/clipboard.py
@@ -1,68 +1,66 @@
-
import dill
import base64
import os
-import lxml
import logging
from PySide6 import QtCore
from PySide6.QtWidgets import QApplication
from PySide6.QtGui import QClipboard
-import lxml.etree
+
# import jsonpickle
# import importlib
# import msgpack
from enum import IntEnum
-import gremlin.base_profile
from gremlin.singleton_decorator import SingletonDecorator
syslog = logging.getLogger("system")
+
class EncoderType(IntEnum):
- Action = 1 # single action
- Container = 2 # single container
- MultiContainer = 3 # data holds multiple containers
+ Action = 1 # single action
+ Container = 2 # single container
+ MultiContainer = 3 # data holds multiple containers
+
-class ObjectEncoder():
- ''' helper class to encode objects '''
- def __init__(self, obj, data, name, encoder_type : EncoderType ):
+class ObjectEncoder:
+ """helper class to encode objects"""
+
+ def __init__(self, obj, data, name, encoder_type: EncoderType):
cls = type(obj)
self._name = name
self._class_name = cls.__name__
self._module = cls.__module__
self._data = data
- self._type : EncoderType = encoder_type
-
-
+ self._type: EncoderType = encoder_type
@property
def data(self):
return self._data
-
+
@property
def module(self):
return self._module
-
+
@property
def name(self):
return self._name
-
+
@property
def class_name(self):
return self._class_name
-
+
@name.setter
def name(self, value):
self._name = value
-
@property
- def encoder_type(self)->EncoderType:
+ def encoder_type(self) -> EncoderType:
return self._type
+
@SingletonDecorator
class Clipboard(QtCore.QObject):
- ''' clipboard data '''
+ """clipboard data"""
# occurs on clipboard changes
clipboard_changed = QtCore.Signal(QtCore.QObject)
@@ -70,24 +68,22 @@ class Clipboard(QtCore.QObject):
def __init__(self):
from gremlin.util import userprofile_path
from gremlin.config import Configuration
+
super().__init__()
self._data = None
self._enabled_count = 0
config = Configuration()
self._persist_to_file = config.persist_clipboard
- self._clipboard_file = os.path.join(userprofile_path(),"clipboard.data")
+ self._clipboard_file = os.path.join(userprofile_path(), "clipboard.data")
- #self._decode() # initialize windows clipboard data if any
+ # self._decode() # initialize windows clipboard data if any
# user profile path
-
-
@property
def data(self):
if self.enabled:
-
if not self._data:
# see if we can use windows clipboard
self._decode()
@@ -96,20 +92,21 @@ def data(self):
# internal clipboard
return self._data
return None
-
+
def _decode(self):
# external clipboard
from gremlin.base_profile import AbstractContainer, AbstractAction
+
data = None
if self._persist_to_file:
# see if the file exists
if os.path.isfile(self._clipboard_file):
# load from that
read_ok = True
- with open(self._clipboard_file,"rb") as f:
+ with open(self._clipboard_file, "rb") as f:
try:
data = dill.load(f)
- except Exception as error:
+ except Exception:
data = None
read_ok = False
if not read_ok:
@@ -122,7 +119,7 @@ def _decode(self):
try:
if pickled.endswith("="):
data = dill.loads(base64.b64decode(pickled)).encode()
-
+
except:
# attempt json pickle
data = dill.loads(pickled)
@@ -133,17 +130,17 @@ def _decode(self):
self.set_windows_clipboard_text(None)
pass
- if data and isinstance(data, AbstractContainer) \
- or isinstance(data, AbstractAction) \
- or isinstance(data, ObjectEncoder):
+ if (
+ data
+ and isinstance(data, AbstractContainer)
+ or isinstance(data, AbstractAction)
+ or isinstance(data, ObjectEncoder)
+ ):
self._data = data
-
-
-
@data.setter
def data(self, value):
- import gremlin.util
+
if self.enabled:
self._data = value
# indicate the clipboard was changed so UI can be updated
@@ -152,7 +149,7 @@ def data(self, value):
# persist to a temporary file
if self._persist_to_file:
write_ok = True
- with open(self._clipboard_file,"wb") as f:
+ with open(self._clipboard_file, "wb") as f:
try:
dill.dump(value, f)
f.flush()
@@ -162,25 +159,23 @@ def data(self, value):
if not write_ok and os.path.isfile(self._clipboard_file):
os.unlink(self._clipboard_file)
-
else:
# persist to windows clipboard
try:
- pickled = dill.dumps(value) # binary
- packed = base64.b64encode(pickled).decode('ascii') # text encoded
+ pickled = dill.dumps(value) # binary
+ packed = base64.b64encode(pickled).decode("ascii") # text encoded
self.set_windows_clipboard_text(packed)
except Exception as error:
- syslog.error(f"DILL serializationf failed: {error}")
-
+ syslog.error(f"DILL serializationf failed: {error}")
- def set_windows_clipboard_text(self, value : str):
- ''' sets the windows clipboard text '''
+ def set_windows_clipboard_text(self, value: str):
+ """sets the windows clipboard text"""
# method 1
clipboard = QApplication.clipboard()
- clipboard.clear(mode = QClipboard.Mode.Clipboard)
+ clipboard.clear(mode=QClipboard.Mode.Clipboard)
if value is not None:
- clipboard.setText(value, mode = QClipboard.Mode.Clipboard)
-
+ clipboard.setText(value, mode=QClipboard.Mode.Clipboard)
+
# method 2
# win32clipboard.OpenClipboard()
# win32clipboard.EmptyClipboard()
@@ -188,61 +183,62 @@ def set_windows_clipboard_text(self, value : str):
# win32clipboard.CloseClipboard()
def get_windows_clipboard_text(self) -> str:
- ''' gets the windows clipboard text '''
+ """gets the windows clipboard text"""
try:
clipboard = QApplication.clipboard()
- return clipboard.text(mode = QClipboard.Mode.Clipboard)
+ return clipboard.text(mode=QClipboard.Mode.Clipboard)
except:
return None
-
-
-
@property
def enabled(self):
- ''' true if the clipboard is enabled '''
+ """true if the clipboard is enabled"""
return self._enabled_count == 0
-
+
def disable(self):
- ''' pushess a disable on the stack '''
+ """pushess a disable on the stack"""
self._enabled_count += 1
-
- def enable(self, reset = False):
- ''' enables the clipboard - pops the disabled stack'''
+
+ def enable(self, reset=False):
+ """enables the clipboard - pops the disabled stack"""
if reset:
- self._enabled_count = 0
+ self._enabled_count = 0
elif self._enabled_count > 0:
self._enabled_count -= 1
def clear_persisted(self):
- ''' clears the persisted data on disk '''
+ """clears the persisted data on disk"""
if os.path.isfile(self._clipboard_file):
try:
os.unlink(self._clipboard_file)
except:
pass
-
+
@property
def is_container(self):
- ''' true if the data item is a container '''
+ """true if the data item is a container"""
from gremlin.base_profile import AbstractContainer
+
data = self.data
if isinstance(data, ObjectEncoder):
- return data.encoder_type in (EncoderType.Container, EncoderType.MultiContainer)
+ return data.encoder_type in (
+ EncoderType.Container,
+ EncoderType.MultiContainer,
+ )
return self.data is not None and isinstance(self.data, AbstractContainer)
-
+
@property
def is_action(self):
- ''' true if the data item is an action '''
+ """true if the data item is an action"""
from gremlin.base_profile import AbstractAction
+
data = self.data
if isinstance(data, ObjectEncoder):
return data.encoder_type == EncoderType.Action
return self.data is not None and isinstance(self.data, AbstractAction)
-
+
@property
def is_valid(self):
- ''' true if cliboard data is valid '''
+ """true if cliboard data is valid"""
return self.data is not None and self.is_action or self.is_container
-
diff --git a/gremlin/code_runner.py b/gremlin/code_runner.py
index 1c46f52f..16fc7910 100644
--- a/gremlin/code_runner.py
+++ b/gremlin/code_runner.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,9 +21,7 @@
import random
import string
import sys
-import time
-import action_plugins.map_to_simconnect
import dinput
import gremlin
@@ -48,13 +46,12 @@
import gremlin.user_plugin
import gremlin.sendinput as sendinput
import gremlin.execution_graph
-import anytree
import traceback
syslog = logging.getLogger("system")
-class CodeRunner:
+class CodeRunner:
"""Runs the actual profile code."""
def __init__(self):
@@ -66,20 +63,17 @@ def __init__(self):
eh = gremlin.event_handler.EventListener()
eh.action_created.connect(self._action_created_cb)
-
self._inheritance_tree = None
- #self._vjoy_curves = VJoyCurves()
+ # self._vjoy_curves = VJoyCurves()
self._merge_axes = []
self._startup_profile = None
self._startup_mode = None
- self._actions = [] # tracks functors in this profile
-
+ self._actions = [] # tracks functors in this profile
def _action_created_cb(self, action):
- if not action in self._actions:
+ if action not in self._actions:
self._actions.append(action)
-
def is_running(self):
"""Returns whether or not the code runner is executing code.
@@ -87,7 +81,6 @@ def is_running(self):
:return True if code is being executed, False otherwise
"""
return gremlin.shared_state.is_running
-
def setUIState(self, enabled):
ui = gremlin.shared_state.ui.ui
@@ -102,24 +95,23 @@ def setUIState(self, enabled):
ui.actionMergeAxis.setEnabled(enabled)
ui.actionSwapDevices.setEnabled(enabled)
ui.actionModifyProfile.setEnabled(enabled)
-
-
-
-
-
def disableUi(self):
- ''' disables UI '''
+ """disables UI"""
if not gremlin.config.Configuration().runtime_ui_active:
self.setUIState(False)
-
-
def enableUI(self):
- ''' enables UI '''
+ """enables UI"""
self.setUIState(True)
- def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_profile.Profile):
+ def start(
+ self,
+ inheritance_tree,
+ settings,
+ start_mode,
+ profile: gremlin.base_profile.Profile,
+ ):
"""Starts listening to events and loads all existing callbacks.
:param inheritance_tree tree encoding inheritance between the
@@ -133,12 +125,12 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
config = gremlin.config.Configuration()
- gremlin.shared_state.profile_state = True # assume profile start ok
- gremlin.shared_state.profile_start_error = None # assume no error
+ gremlin.shared_state.profile_state = True # assume profile start ok
+ gremlin.shared_state.profile_start_error = None # assume no error
self.disableUi()
- # update hardware list for any missing devices
+ # update hardware list for any missing devices
gremlin.joystick_handling._scan_dinput()
# indicate we're in run mode
@@ -158,8 +150,7 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
config = gremlin.config.Configuration()
verbose_detailed = config.verbose_mode_details
- verbose = config.verbose_mode_details
-
+ verbose = config.verbose_mode_details
# store the startup mode in the UI so it can be restored later
self._startup_profile = gremlin.shared_state.current_profile
@@ -170,7 +161,7 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
start_mode = gremlin.shared_state.current_profile.get_start_mode()
syslog.info(f"Startup mode: {start_mode}")
-
+
# Set default macro action delay
gremlin.macro.MacroManager().default_delay = settings.default_delay
@@ -178,12 +169,11 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
system_paths = [os.path.normcase(os.path.abspath(p)) for p in sys.path]
ec = gremlin.execution_graph.ExecutionContext()
-
# Load the generated code
try:
# Populate custom module variable registry
- var_reg =gremlin.user_plugin.variable_registry
+ var_reg = gremlin.user_plugin.variable_registry
for plugin in profile.plugins:
# Perform system path mangling for import statements
path, _ = os.path.split(
@@ -196,7 +186,7 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
# instances if desired
spec = importlib.util.spec_from_file_location(
"".join(random.choices(string.ascii_lowercase, k=16)),
- plugin.file_name
+ plugin.file_name,
)
_, plugin_basename = os.path.split(plugin.file_name)
@@ -205,16 +195,15 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
for instance in plugin.instances:
# Skip all instances that are not fully configured
if not instance.is_configured():
- syslog.warn(f"Warning: User plugin '{plugin_basename}': instance '{instance.name}' reports not configured - skipping runtime activation")
+ syslog.warn(
+ f"Warning: User plugin '{plugin_basename}': instance '{instance.name}' reports not configured - skipping runtime activation"
+ )
continue
# Store variable values in the registry
for var in instance.variables.values():
var_reg.set(
- plugin.file_name,
- instance.name,
- var.name,
- var.value
+ plugin.file_name, instance.name, var.name, var.value
)
# Load the modules
@@ -224,51 +213,45 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
spec.loader.exec_module(tmp)
else:
basename = os.path.basename(plugin.file_name)
- gremlin.ui.ui_common.MessageBox(prompt = f"Plugin {basename} was not found and will not be loaded.")
-
-
+ gremlin.ui.ui_common.MessageBox(
+ prompt=f"Plugin {basename} was not found and will not be loaded."
+ )
# Update system path list searched by Python
sys.path = system_paths
# Create callbacks fom the user code
callback_count = 0
- for dev_id, modes in gremlin.input_devices.callback_registry.registry.items():
+ for (
+ dev_id,
+ modes,
+ ) in gremlin.input_devices.callback_registry.registry.items():
for mode, events in modes.items():
for event, callback_list in events.items():
for callback in callback_list.values():
self.event_handler.add_callback(
- dev_id,
- mode,
- event,
- callback[0],
- callback[1]
+ dev_id, mode, event, callback[0], callback[1]
)
callback_count += 1
# Add a fake keyboard action which does nothing to the callbacks
# in every mode in order to have empty modes be "present"
for mode_name in gremlin.profile.mode_list():
- self.event_handler.add_callback(
- 0,
- mode_name,
- None,
- lambda x: x,
- False
- )
-
+ self.event_handler.add_callback(0, mode_name, None, lambda x: x, False)
# reset functor latching
container_plugins = gremlin.plugin_manager.ContainerPlugins()
container_plugins.reset_functors()
mode_source = gremlin.shared_state.current_profile.traverse_mode()
- mode_source.sort(key = lambda x: x[0]) # sort parent to child
- mode_list = [mode for (_,mode) in mode_source] # parent mode first
+ mode_source.sort(key=lambda x: x[0]) # sort parent to child
+ mode_list = [mode for (_, mode) in mode_source] # parent mode first
mode_nodes = {}
for mode in mode_list:
- mode_node = gremlin.execution_graph.ExecutionGraphNode(gremlin.execution_graph.ExecutionGraphNodeType.Mode)
+ mode_node = gremlin.execution_graph.ExecutionGraphNode(
+ gremlin.execution_graph.ExecutionGraphNodeType.Mode
+ )
mode_node.parent = ec.root
mode_node.mode = mode
mode_nodes[mode] = mode_node
@@ -278,7 +261,9 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
verbose = gremlin.config.Configuration().verbose_mode_exec
for device in profile.devices.values():
if verbose:
- device_name = gremlin.joystick_handling.device_name_from_guid(device.device_guid)
+ device_name = gremlin.joystick_handling.device_name_from_guid(
+ device.device_guid
+ )
syslog.info(f"CALLBACK: device: {device_name}")
for mode in device.modes.values():
mode_node = mode_nodes[mode.name]
@@ -286,59 +271,91 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
for input_item in input_items.values():
# Only add callbacks for input items that actually
# contain actions
-
+
if len(input_item.containers) == 0:
# no containers = no actions = skip
- #if verbose: syslog.info(f"\t\tno containers")
+ # if verbose: syslog.info(f"\t\tno containers")
continue
- if verbose: syslog.info(f"\t{input_item.display_name}")
+ if verbose:
+ syslog.info(f"\t{input_item.display_name}")
self.event_handler.registerInputItem(mode.name, input_item)
event = gremlin.event_handler.Event(
event_type=input_item.input_type,
device_guid=device.device_guid,
- identifier=input_item.input_id
+ identifier=input_item.input_id,
)
-
-
-
# Create possibly several callbacks depending
# on the input item's content
callbacks = []
for container in input_item.containers:
if not container.is_valid():
- #test = container.is_valid()
- syslog.warning(f"CALLBACK: device: {device_name}: input: {input_item.display_name}: warning: Incomplete container ignored")
+ # test = container.is_valid()
+ syslog.warning(
+ f"CALLBACK: device: {device_name}: input: {input_item.display_name}: warning: Incomplete container ignored"
+ )
continue
- callbacks.extend(container.generate_callbacks(mode_node))
+ callbacks.extend(
+ container.generate_callbacks(mode_node)
+ )
for cb_data in callbacks:
if cb_data.event is None:
- if verbose:
- syslog.info(f"\t\tcallback: ")
- for functor in cb_data.callback.execution_graph.functors:
- if hasattr(functor,"action_set"):
- for action in functor.action_set.functors:
- if isinstance(action, gremlin.actions.ActivationCondition):
- syslog.info(f"\t\t\tActivation Condition: target :{action.target.name}")
+ if verbose:
+ syslog.info("\t\tcallback: ")
+ for (
+ functor
+ ) in cb_data.callback.execution_graph.functors:
+ if hasattr(functor, "action_set"):
+ for (
+ action
+ ) in functor.action_set.functors:
+ if isinstance(
+ action,
+ gremlin.actions.ActivationCondition,
+ ):
+ syslog.info(
+ f"\t\t\tActivation Condition: target :{action.target.name}"
+ )
else:
import action_plugins.map_to_simconnect
- syslog.info(f"\t\t\tAction: {action._name}")
- if isinstance(action, action_plugins.map_to_simconnect.MapToSimConnectFunctor):
- syslog.info(f"\t\t\t\tCommand:: {action.command}")
- elif hasattr(functor,"action_sets"):
+
+ syslog.info(
+ f"\t\t\tAction: {action._name}"
+ )
+ if isinstance(
+ action,
+ action_plugins.map_to_simconnect.MapToSimConnectFunctor,
+ ):
+ syslog.info(
+ f"\t\t\t\tCommand:: {action.command}"
+ )
+ elif hasattr(functor, "action_sets"):
for action_set in functor.action_sets:
for action in action_set.functors:
- if isinstance(action, gremlin.actions.ActivationCondition):
- syslog.info(f"\t\t\tActivation Condition: target :{action.target.name}")
+ if isinstance(
+ action,
+ gremlin.actions.ActivationCondition,
+ ):
+ syslog.info(
+ f"\t\t\tActivation Condition: target :{action.target.name}"
+ )
else:
import action_plugins.map_to_simconnect
- syslog.info(f"\t\t\tAction: {action._name}")
- if isinstance(action, action_plugins.map_to_simconnect.MapToSimConnectFunctor):
- syslog.info(f"\t\t\t\tCommand:: {action.command}")
+
+ syslog.info(
+ f"\t\t\tAction: {action._name}"
+ )
+ if isinstance(
+ action,
+ action_plugins.map_to_simconnect.MapToSimConnectFunctor,
+ ):
+ syslog.info(
+ f"\t\t\t\tCommand:: {action.command}"
+ )
else:
syslog.info(f"\t\t\tFunctor: {functor}")
self.event_handler.add_callback(
@@ -346,7 +363,7 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
mode.name,
event,
cb_data.callback,
- input_item.always_execute
+ input_item.always_execute,
)
else:
self.event_handler.add_callback(
@@ -354,21 +371,18 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
mode.name,
cb_data.event,
cb_data.callback,
- input_item.always_execute
+ input_item.always_execute,
)
-
# if verbose_detailed:
# self.event_handler.dump_callbacks()
-
-
# Create merge axis callbacks
for entry in profile.merge_axes:
merge_axis = MergeAxis(
entry["vjoy"]["vjoy_id"],
entry["vjoy"]["axis_id"],
- entry["operation"]
+ entry["operation"],
)
self._merge_axes.append(merge_axis)
@@ -376,32 +390,30 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
event = gremlin.event_handler.Event(
event_type=InputType.JoystickAxis,
device_guid=entry["lower"]["device_guid"],
- identifier=entry["lower"]["axis_id"]
+ identifier=entry["lower"]["axis_id"],
)
self.event_handler.add_callback(
event.device_guid,
entry["mode"],
event,
merge_axis.update_axis1,
- False
+ False,
)
# Upper axis callback
event = gremlin.event_handler.Event(
event_type=InputType.JoystickAxis,
device_guid=entry["upper"]["device_guid"],
- identifier=entry["upper"]["axis_id"]
+ identifier=entry["upper"]["axis_id"],
)
self.event_handler.add_callback(
event.device_guid,
entry["mode"],
event,
merge_axis.update_axis2,
- False
+ False,
)
-
-
# Use inheritance to build input action lookup table
self.event_handler.build_event_lookup(inheritance_tree)
@@ -414,11 +426,9 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
if verbose_detailed:
self.event_handler.dump_callbacks()
-
# Connect signals
evt_listener = gremlin.event_handler.EventListener()
-
# hook mouse events
evt_listener.mouse_event.connect(self.event_handler.execute_event)
@@ -439,16 +449,15 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
# set keyboard startup state for numlock - use global numlock or profile numlock
numlock_off = numlock_off or profile.get_force_numlock()
-
+
if numlock_off:
state = gremlin.keyboard.KeyMap.numlock_state()
syslog.info(f"Numlock state: {state}")
if state:
# toggle numlock off
- syslog.info(f"Numlock state: Forcing Off")
+ syslog.info("Numlock state: Forcing Off")
gremlin.keyboard.KeyMap.toggle_numlock()
-
-
+
# monitor keyboard input state
kb = gremlin.input_devices.Keyboard()
evt_listener.keyboard_event.connect(kb.keyboard_event)
@@ -467,75 +476,72 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
# listen to OSC
if config.osc_enabled:
evt_listener.request_osc.emit(True)
-
+
# hook mode change callbacks
- evt_listener.runtime_mode_changed.connect(gremlin.input_devices.mode_registry.runtime_mode_changed)
+ evt_listener.runtime_mode_changed.connect(
+ gremlin.input_devices.mode_registry.runtime_mode_changed
+ )
# hook state change callbacks
- evt_listener.broadcast_changed.connect(gremlin.input_devices.state_registry.state_changed)
+ evt_listener.broadcast_changed.connect(
+ gremlin.input_devices.state_registry.state_changed
+ )
# call start functions
gremlin.input_devices.start_registry.start()
gremlin.input_devices.periodic_registry.start()
-
-
gremlin.macro.MacroManager().start()
verbose = gremlin.config.Configuration().verbose
# determine the profile start mode
-
mode = start_mode
if config.restore_profile_mode_on_start or profile.get_restore_mode():
# restore the profile mode
mode = profile.get_last_runtime_mode()
- syslog.error(f"Start: Restoring the last active profile mode for this profile: '{mode}' - overriding profile start mode '{start_mode}' at user request")
-
+ syslog.error(
+ f"Start: Restoring the last active profile mode for this profile: '{mode}' - overriding profile start mode '{start_mode}' at user request"
+ )
if mode:
- if not mode in mode_list:
- syslog.error(f"Unable to restore profile mode: '{mode}' no longer exists - using '{start_mode}' instead.")
+ if mode not in mode_list:
+ syslog.error(
+ f"Unable to restore profile mode: '{mode}' no longer exists - using '{start_mode}' instead."
+ )
mode = start_mode
else:
- syslog.error(f"Start: Using default Restoring the last active profile mode for this profile: '{mode}'")
-
+ syslog.error(
+ f"Start: Using default Restoring the last active profile mode for this profile: '{mode}'"
+ )
sendinput.MouseController().start()
-
- if not mode in mode_list:
- syslog.error(f"Unable to select startup mode: '{mode}' no longer exists")
+ if mode not in mode_list:
+ syslog.error(
+ f"Unable to select startup mode: '{mode}' no longer exists"
+ )
else:
if verbose:
syslog.info(f"Using profile start mode: '{mode}'")
self.event_handler.change_mode(mode)
-
-
-
# tell listener profiles are starting
# start listen
evt_listener.start()
-
- #print ("resume!")
+ # print ("resume!")
self.event_handler.resume()
-
# register callbacks with the execution tree
eh = gremlin.event_handler.EventHandler()
- ec.reset(force_rebuild = True) # rebuild the execution tree
+ ec.reset(force_rebuild=True) # rebuild the execution tree
ec.registerCallbacks(eh.callbacks)
-
-
# tell GremlinEx the profile started
el.profile_start.emit()
el.profile_started.emit()
-
-
except Exception as e:
tb_msg = traceback.format_exc()
# re-enable tabs
@@ -544,36 +550,32 @@ def start(self, inheritance_tree, settings, start_mode, profile : gremlin.base_p
syslog.error(f"Error: {e}")
syslog.error(f"Traceback: {tb_msg}")
- gremlin.util.display_error(f"Unable to launch profile due to an error: {tb_msg}")
-
-
-
-
+ gremlin.util.display_error(
+ f"Unable to launch profile due to an error: {tb_msg}"
+ )
def stop(self):
"""Stops listening to events and unloads all callbacks."""
if not gremlin.shared_state.is_running:
- return # not running - nothing to do
+ return # not running - nothing to do
el = gremlin.event_handler.EventListener()
eh = gremlin.event_handler.EventHandler()
-
+
# tell components we're stopping
el.profile_stop.emit()
-
-
# stop remote client
gremlin.input_devices.remote_client.stop()
gremlin.input_devices.remote_server.stop()
# call stop function in plugins
- #gremlin.input_devices.stop_registry.start()
+ # gremlin.input_devices.stop_registry.start()
gremlin.input_devices.stop_registry.stop()
gremlin.input_devices.stop_registry.clear()
gremlin.input_devices.mode_registry.clear()
-
+
# reset functor latching
container_plugins = gremlin.plugin_manager.ContainerPlugins()
container_plugins.reset_functors()
@@ -592,7 +594,6 @@ def stop(self):
# self.event_handler.runtime_mode_changed.disconnect(
# self._vjoy_curves.runtime_mode_changed
# )
-
# Empty callback registry
gremlin.input_devices.callback_registry.clear()
@@ -606,7 +607,6 @@ def stop(self):
gremlin.input_devices.start_registry.stop()
gremlin.input_devices.start_registry.clear()
-
gremlin.macro.MacroManager().stop()
sendinput.MouseController().stop()
@@ -615,21 +615,22 @@ def stop(self):
# restore the startup mode and profile
gremlin.shared_state.is_running = False
- if self._startup_profile and gremlin.shared_state.current_profile != self._startup_profile:
+ if (
+ self._startup_profile
+ and gremlin.shared_state.current_profile != self._startup_profile
+ ):
eh.change_profile(self._startup_profile)
# change back to edit mode
- eh.change_mode(gremlin.shared_state.edit_mode, emit=True, force_update = False)
-
-
+ eh.change_mode(gremlin.shared_state.edit_mode, emit=True, force_update=False)
+
# re-enable tabs
self.enableUI()
# check if devices changed at runtime
if gremlin.shared_state.has_device_changes:
- gremlin.shared_state.has_device_changes = False # mark as changes processed
+ gremlin.shared_state.has_device_changes = False # mark as changes processed
gremlin.joystick_handling.reset_devices()
-
def _reset_state(self):
"""Resets all states to their default values."""
first_node = self._inheritance_tree.children[0].name
@@ -639,7 +640,6 @@ def _reset_state(self):
class VJoyCurves:
-
"""Handles setting response curves on vJoy devices."""
def __init__(self):
@@ -657,30 +657,29 @@ def mode_changed(self, mode_name):
vjoy = gremlin.joystick_handling.VJoyProxy()
for guid, device in self.profile_data.items():
if mode_name in device.modes:
- for aid, data in device.modes[mode_name].config[InputType.JoystickAxis].items():
+ for aid, data in (
+ device.modes[mode_name].config[InputType.JoystickAxis].items()
+ ):
# Get integer axis id in case an axis enum was used
axis_id = vjoy_module.vjoy.VJoy.axis_equivalence.get(aid, aid)
vjoy_id = gremlin.joystick_handling.vjoy_id_from_guid(guid)
- if len(data.containers) > 0 and vjoy[vjoy_id].is_axis_valid(axis_id):
+ if len(data.containers) > 0 and vjoy[vjoy_id].is_axis_valid(
+ axis_id
+ ):
action = data.containers[0].action_sets[0][0]
- if hasattr(action,"deadzone"):
+ if hasattr(action, "deadzone"):
vjoy[vjoy_id].axis(aid).set_deadzone(*action.deadzone)
vjoy[vjoy_id].axis(aid).set_response_curve(
- action.mapping_type,
- action.control_points
+ action.mapping_type, action.control_points
)
class MergeAxis:
-
"""Merges inputs from two distinct axes into a single one."""
def __init__(
- self,
- vjoy_id: int,
- input_id: int,
- operation: gremlin.types.MergeAxisOperation
+ self, vjoy_id: int, input_id: int, operation: gremlin.types.MergeAxisOperation
):
self.axis_values = [0.0, 0.0]
self.vjoy_id = vjoy_id
@@ -698,17 +697,16 @@ def _update(self):
value = max(self.axis_values[0], self.axis_values[1])
elif self.operation == gremlin.types.MergeAxisOperation.Sum:
value = gremlin.util.clamp(
- self.axis_values[0] + self.axis_values[1],
- -1.0,
- 1.0
+ self.axis_values[0] + self.axis_values[1], -1.0, 1.0
)
else:
raise gremlin.error.GremlinError(
- f"Invalid merge axis operation detected, \"{str(self.operation)}\""
+ f'Invalid merge axis operation detected, "{str(self.operation)}"'
)
- gremlin.joystick_handling.VJoyProxy()[self.vjoy_id]\
- .axis(self.input_id).value = value
+ gremlin.joystick_handling.VJoyProxy()[self.vjoy_id].axis(
+ self.input_id
+ ).value = value
def update_axis1(self, event: gremlin.event_handler.Event):
"""Updates information for the first axis.
@@ -724,4 +722,4 @@ def update_axis2(self, event: gremlin.event_handler.Event):
:param event data event for the second axis
"""
self.axis_values[1] = event.value
- self._update()
\ No newline at end of file
+ self._update()
diff --git a/gremlin/common.py b/gremlin/common.py
index 53e4decb..63000433 100644
--- a/gremlin/common.py
+++ b/gremlin/common.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,19 +16,14 @@
# along with this program. If not, see .
import enum
-import logging
import gremlin.error
-import os
-import sys
-from PySide6 import QtGui
from gremlin.input_types import InputType
import gremlin.types
import gremlin.keyboard
-
def input_to_ui_string(input_type, input_id):
"""Returns a string for UI usage of an input.
@@ -36,7 +31,7 @@ def input_to_ui_string(input_type, input_id):
:param input_id the corresponding id
:return string for UI usage of the given data
"""
-
+
from gremlin.keyboard import key_from_code
if hasattr(input_id, "display_name"):
@@ -53,14 +48,13 @@ def input_to_ui_string(input_type, input_id):
return input_id.name
elif input_type in (InputType.Keyboard, InputType.KeyboardLatched):
if isinstance(input_id, gremlin.keyboard.Key):
- return key_from_code(input_id.scan_code, input_id.is_extended).name
-
- return key_from_code(input_id[0],input_id[1]).name
+ return key_from_code(input_id.scan_code, input_id.is_extended).name
+
+ return key_from_code(input_id[0], input_id[1]).name
else:
return f"{InputType.to_string(input_type).capitalize()} {input_id}"
-
def index_to_direction(direction):
"""Returns a direction index to a direction name.
@@ -75,7 +69,7 @@ def index_to_direction(direction):
5: "Down",
6: "Down & Left",
7: "Left",
- 8: "Up & Left"
+ 8: "Up & Left",
}
return lookup[int(direction)]
@@ -99,13 +93,11 @@ def index_to_direction(direction):
"South": (0, -1),
"South West": (-1, -1),
"West": (-1, 0),
- "North West": (-1, 1)
+ "North West": (-1, 1),
}
-
class PluginVariableType(enum.Enum):
-
"""Enumeration of all supported variable types."""
Int = 1
@@ -122,19 +114,14 @@ def to_string(value):
try:
return _PluginVariableType_to_string_lookup[value]
except KeyError:
- raise gremlin.error.GremlinError(
- "Invalid PluginVariableType in lookup"
- )
+ raise gremlin.error.GremlinError("Invalid PluginVariableType in lookup")
@staticmethod
def to_enum(value):
try:
return _PluginVariableType_to_enum_lookup[value]
except KeyError:
- raise gremlin.error.GremlinError(
- "Invalid PluginVariableType in lookup"
- )
-
+ raise gremlin.error.GremlinError("Invalid PluginVariableType in lookup")
_PluginVariableType_to_string_lookup = {
@@ -145,7 +132,7 @@ def to_enum(value):
PluginVariableType.PhysicalInput: "PhysicalInput",
PluginVariableType.VirtualInput: "VirtualInput",
PluginVariableType.Mode: "Mode",
- PluginVariableType.Selection: "Selection"
+ PluginVariableType.Selection: "Selection",
}
_PluginVariableType_to_enum_lookup = {
@@ -156,5 +143,5 @@ def to_enum(value):
"PhysicalInput": PluginVariableType.PhysicalInput,
"VirtualInput": PluginVariableType.VirtualInput,
"Mode": PluginVariableType.Mode,
- "Selection": PluginVariableType.Selection
+ "Selection": PluginVariableType.Selection,
}
diff --git a/gremlin/config.py b/gremlin/config.py
index e1d11a0d..ed05f6eb 100644
--- a/gremlin/config.py
+++ b/gremlin/config.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -35,17 +35,18 @@
syslog = logging.getLogger("system")
import gremlin.singleton_decorator
+
+
@gremlin.singleton_decorator.SingletonDecorator
class Configuration:
-
"""Responsible for loading and saving configuration data."""
def get_config(sef):
fname = os.path.join(gremlin.util.userprofile_path(), "config.json")
return fname
-
+
def get_profile_config(self):
- ''' profile specific config file '''
+ """profile specific config file"""
if self._profile_fname:
fname, ext = os.path.splitext(self._profile_fname)
return fname + ".json"
@@ -54,13 +55,11 @@ def get_profile_config(self):
def __init__(self):
"""Creates a new instance, loading the current configuration."""
- self._data = {} # gremlin items
- self._profile_data = {} # profile specific options
+ self._data = {} # gremlin items
+ self._profile_data = {} # profile specific options
self._profile_loaded = False
- self._profile_fname = None # current profile to use for the conig
- self._profile_config_fname = None # config file specific to the profile
-
-
+ self._profile_fname = None # current profile to use for the conig
+ self._profile_config_fname = None # config file specific to the profile
self._midi_enabled = None
self._osc_enabled = None
@@ -70,38 +69,26 @@ def __init__(self):
# create a stub - first time run
self.save()
-
- gettrace = getattr(sys, 'gettrace', None)
- frozen = getattr(sys, 'frozen', False)
+ gettrace = getattr(sys, "gettrace", None)
+ frozen = getattr(sys, "frozen", False)
if frozen:
self._is_debug = False
else:
self._is_debug = gettrace is not None
-
self._last_reload = None
self._last_profile_reload = None
self.reload()
+ self.watcher = QtCore.QFileSystemWatcher(
+ [os.path.join(gremlin.util.userprofile_path(), "config.json")]
+ )
-
- self.watcher = QtCore.QFileSystemWatcher([
- os.path.join(gremlin.util.userprofile_path(), "config.json")
- ])
-
self.watcher.fileChanged.connect(self.reload)
-
-
-
-
-
-
-
def reload(self):
"""Loads the configuration file's content."""
- if self._last_reload is not None and \
- time.time() - self._last_reload < 1:
+ if self._last_reload is not None and time.time() - self._last_reload < 1:
return
fname = self.get_config()
@@ -117,11 +104,7 @@ def reload(self):
except ValueError:
pass
if not load_successful:
- self._data = {
- "calibration": {},
- "profiles": {},
- "last_mode": {}
- }
+ self._data = {"calibration": {}, "profiles": {}, "last_mode": {}}
# Ensure required fields are present and if they are missing
# add empty ones.
@@ -135,18 +118,18 @@ def reload(self):
def reload_profile(self):
"""Loads the profile's configuration file's content."""
- if self._last_profile_reload is not None and \
- time.time() - self._last_profile_reload < 1:
+ if (
+ self._last_profile_reload is not None
+ and time.time() - self._last_profile_reload < 1
+ ):
return
-
+
self._profile_data = {}
fname = self._profile_config_fname
if not fname:
- return # nothing to load
-
+ return # nothing to load
-
# Attempt to load the configuration file if this fails set
# default empty values.
if os.path.isfile(fname):
@@ -154,7 +137,6 @@ def reload_profile(self):
try:
decoder = json.JSONDecoder()
self._profile_data = decoder.decode(hdl.read())
- load_successful = True
except ValueError:
pass
@@ -162,34 +144,25 @@ def reload_profile(self):
self._last_profile_reload = time.time()
self.save_profile()
-
def save(self):
"""Writes the configuration file to disk."""
fname = self.get_config()
with open(fname, "w") as hdl:
- encoder = json.JSONEncoder(
- sort_keys=True,
- indent=4
- )
+ encoder = json.JSONEncoder(sort_keys=True, indent=4)
hdl.write(encoder.encode(self._data))
-
def save_profile(self):
- ''' saves to the profile specific config file '''
+ """saves to the profile specific config file"""
fname = self._profile_config_fname
if fname:
with open(fname, "w") as hdl:
- encoder = json.JSONEncoder(
- sort_keys=True,
- indent=4
- )
+ encoder = json.JSONEncoder(sort_keys=True, indent=4)
hdl.write(encoder.encode(self._profile_data))
-
@property
def is_debug(self):
return self._is_debug
-
+
def set_last_runtime_mode(self, profile_path, mode_name):
"""Stores the last active mode of the given profile.
@@ -198,14 +171,16 @@ def set_last_runtime_mode(self, profile_path, mode_name):
"""
if profile_path is None or mode_name is None:
return
-
+
profile_path = os.path.normpath(profile_path).casefold()
item = self._data.get("last_mode", None)
if not item:
self._data["last_mode"] = {}
self._data["last_mode"][profile_path] = mode_name
# syslog = logging.getLogger("system")
- syslog.info(f"CONFIG: storing last runtime profile mode: [{mode_name}] for profile [{os.path.basename(profile_path)}]")
+ syslog.info(
+ f"CONFIG: storing last runtime profile mode: [{mode_name}] for profile [{os.path.basename(profile_path)}]"
+ )
self.save()
def get_last_runtime_mode(self, profile_path):
@@ -221,7 +196,6 @@ def get_last_runtime_mode(self, profile_path):
if profile_path in item:
return item[profile_path]
return None
-
def set_last_edit_mode(self, profile_path, mode_name):
"""Stores the last active mode of the given profile.
@@ -231,7 +205,7 @@ def set_last_edit_mode(self, profile_path, mode_name):
"""
if profile_path is None or mode_name is None:
return
-
+
profile_path = os.path.normpath(profile_path).casefold()
item = self._data.get("last_edit_mode", None)
if not item:
@@ -245,7 +219,7 @@ def get_last_edit_mode(self, profile_path):
:param profile_path profile path for which to return the mode
:return name of the mode if present, None otherwise
"""
-
+
profile_path = os.path.normpath(profile_path).casefold()
item = self._data.get("last_edit_mode", None)
if not item:
@@ -255,69 +229,66 @@ def get_last_edit_mode(self, profile_path):
return None
def set_profile_last_runtime_mode(self, mode_name):
- ''' sets the profile's last used mode '''
+ """sets the profile's last used mode"""
fname = self.last_profile
if fname:
self.set_last_runtime_mode(fname, mode_name)
def get_profile_last_runtime_mode(self):
- ''' gets the save last used profile mode '''
+ """gets the save last used profile mode"""
fname = self.last_profile
if fname:
return self.get_last_runtime_mode(fname)
return None
-
+
def set_profile_last_edit_mode(self, mode_name):
- ''' sets the profile's last used mode '''
+ """sets the profile's last used mode"""
fname = self.last_profile
if fname:
self.set_last_edit_mode(fname, mode_name)
def get_profile_last_edit_mode(self):
- ''' gets the save last used profile mode '''
+ """gets the save last used profile mode"""
fname = self.last_profile
if fname:
return self.get_last_edit_mode(fname)
return None
-
-
+
@property
def initial_load_mode_tts(self):
- ''' if set, JGEX outputs a verbal readout of the current mode on profile load '''
+ """if set, JGEX outputs a verbal readout of the current mode on profile load"""
return self._data.get("initial_load_mode_tts", True)
-
+
@initial_load_mode_tts.setter
def initial_load_mode_tts(self, value):
self._data["initial_load_mode_tts"] = value
self.save()
-
@property
def initial_load_rate_tts(self):
- ''' if set, JGEX outputs a verbal readout of the current playback rate on profile load '''
+ """if set, JGEX outputs a verbal readout of the current playback rate on profile load"""
return self._data.get("initial_load_rate_tts", 100)
-
+
@initial_load_rate_tts.setter
def initial_load_rate_tts(self, value):
self._data["initial_load_rate_tts"] = value
- self.save()
+ self.save()
@property
def runtime_ui_update(self):
- ''' if set, JGEX will update the UI when a profile is activated '''
+ """if set, JGEX will update the UI when a profile is activated"""
return self._data.get("runtime_ui_update", False)
-
+
@runtime_ui_update.setter
def runtime_ui_update(self, value):
self._data["runtime_ui_update"] = value
self.save()
-
@property
def reset_mode_on_process_activate(self):
- ''' if set, the mode is reset when the process is reactivated to the default mode '''
+ """if set, the mode is reset when the process is reactivated to the default mode"""
return self._data.get("reset_mode_on_process_activate", False)
-
+
@reset_mode_on_process_activate.setter
def reset_mode_on_process_activate(self, value):
self._data["reset_mode_on_process_activate"] = value
@@ -339,7 +310,9 @@ def set_calibration(self, dev_id, limits):
continue
axis_name = f"axis_{i+1}"
self._data["calibration"][identifier][axis_name] = [
- limit[0], limit[1], limit[2]
+ limit[0],
+ limit[1],
+ limit[2],
]
self.save()
@@ -358,42 +331,36 @@ def get_calibration(self, dev_id, axis_id):
return [-32768, 0, 32767]
return self._data["calibration"][identifier][axis_name]
-
+
# @property
# def legacy_calibration_imported(self)->bool:
# return self._data.get("legacy_calibration_imported",False)
-
+
# @legacy_calibration_imported.setter
# def legacy_calibration_imported(self, value : bool):
# self._data["legacy_calibration_imported"] = value
-
@property
def last_options_tab(self):
- ''' index of the last option tab selected'''
+ """index of the last option tab selected"""
key = "last_options_tab"
if key in self._data.keys():
index = self._data[key]
else:
index = 0
return index
-
+
@last_options_tab.setter
def last_options_tab(self, value):
self._data["last_options_tab"] = value
self.save()
-
-
def get_executable_list(self):
"""Returns a list of all executables with associated profiles.
:return list of executable paths
"""
- return list(sorted(
- self._data["profiles"].keys(),
- key=lambda x: x.lower())
- )
+ return list(sorted(self._data["profiles"].keys(), key=lambda x: x.lower()))
def remove_profile(self, exec_path):
"""Removes the executable from the configuration.
@@ -427,16 +394,13 @@ def get_profile_with_regex(self, exec_path):
# Handle the normal case where the path matches directly
profile_path = self.get_profile(exec_path)
if profile_path is not None:
- syslog.info(
- f"Found exact match for {exec_path}, returning {profile_path}"
- )
+ syslog.info(f"Found exact match for {exec_path}, returning {profile_path}")
return profile_path
# Handle non files by treating them as regular expressions, returning
# the first successful match.
for key, value in sorted(
- self._data["profiles"].items(),
- key=lambda x: x[0].lower()
+ self._data["profiles"].items(), key=lambda x: x[0].lower()
):
# Ignore valid files
if os.path.exists(key):
@@ -459,7 +423,6 @@ def set_profile(self, exec_path, profile_path):
self._data["profiles"][exec_path] = profile_path
self.save()
-
def set_start_mode(self, profile_path, mode_name):
"""Stores the last active mode of the given profile.
@@ -479,7 +442,6 @@ def get_start_mode(self, profile_path):
"""
return self._data["start_mode"].get(profile_path, None)
-
def _has_profile(self, exec_path):
"""Returns whether or not a profile exists for a given executable.
@@ -506,7 +468,7 @@ def last_profile(self, value):
# Update recent profiles
if value is not None:
- value = os.path.normpath(value.casefold()) # normalize the profile path
+ value = os.path.normpath(value.casefold()) # normalize the profile path
current = self.recent_profiles
if value in current:
@@ -514,9 +476,8 @@ def last_profile(self, value):
current.insert(0, value)
# normalize and remove duplicates
current = list(set([os.path.normpath(item.casefold()) for item in current]))
- current = current[0:19] # limit
+ current = current[0:19] # limit
-
self._data["recent_profiles"] = current
self.save()
@@ -527,13 +488,6 @@ def recent_profiles(self):
:return list of recently used profiles
"""
return self._data.get("recent_profiles", [])
-
-
-
-
-
-
-
@property
def autoload_profiles(self):
@@ -558,11 +512,12 @@ def autoload_profiles(self, value):
self._data["autoload_profiles"] = value
self.save()
el = gremlin.event_handler.EventListener()
- el.process_monitor_changed.emit() # tell the UI the parameter changed
+ el.process_monitor_changed.emit() # tell the UI the parameter changed
@property
def keep_profile_active_on_focus_loss(self):
- return self._data.get("keep_active_on_focus_loss",True)
+ return self._data.get("keep_active_on_focus_loss", True)
+
@keep_profile_active_on_focus_loss.setter
def keep_profile_active_on_focus_loss(self, value):
self._data["keep_active_on_focus_loss"] = value
@@ -593,12 +548,11 @@ def keep_last_autoload(self, value):
self._data["keep_last_autoload"] = value
self.save()
-
@property
def restore_profile_mode_on_start(self):
- ''' determines if a profile mode, if it exists is restored when the profile is activated '''
+ """determines if a profile mode, if it exists is restored when the profile is activated"""
return self._data.get("restore_mode_on_start", False)
-
+
@restore_profile_mode_on_start.setter
def restore_profile_mode_on_start(self, value):
self._data["restore_mode_on_start"] = value
@@ -606,17 +560,15 @@ def restore_profile_mode_on_start(self, value):
@property
def highlight_autoswitch(self):
- ''' true if in design mode and tab switching is allowed on input detect change '''
+ """true if in design mode and tab switching is allowed on input detect change"""
return self._data.get("highlight_switch", False)
-
+
@highlight_autoswitch.setter
def highlight_autoswitch(self, value):
-
if value != self.highlight_autoswitch:
self._data["highlight_switch"] = value
self.save()
-
@property
def highlight_input_axis(self):
"""Returns whether or not to highlight inputs for an axis
@@ -642,7 +594,6 @@ def highlight_input_axis(self, value):
self._data["highlight_input_axis"] = value
self.save()
-
@property
def highlight_input_buttons(self):
"""Returns whether or not to highlight inputs for a button
@@ -656,7 +607,7 @@ def highlight_input_buttons(self):
@highlight_input_buttons.setter
def highlight_input_buttons(self, value):
- """Sets whether or not to highlight inputs for a button
+ """Sets whether or not to highlight inputs for a button
This enables / disables the feature where using a physical input
automatically selects it in the UI.
@@ -668,9 +619,6 @@ def highlight_input_buttons(self, value):
self._data["highlight_input_buttons"] = value
self.save()
-
-
-
@property
def highlight_enabled(self):
"""Returns whether or not highlighting swaps device tabs.
@@ -698,26 +646,23 @@ def highlight_enabled(self, value) -> bool:
el = gremlin.event_handler.EventListener()
el.config_option_changed.emit()
-
-
@property
def highlight_hotkey_autoswitch(self):
- ''' when enabled - pressing the control or shift hotkeys to toggle axis / button highlight also allows tab switch '''
+ """when enabled - pressing the control or shift hotkeys to toggle axis / button highlight also allows tab switch"""
return self._data.get("highlight_hotkey_autoswitch", False)
-
+
@highlight_hotkey_autoswitch.setter
- def highlight_hotkey_autoswitch(self, value : bool):
+ def highlight_hotkey_autoswitch(self, value: bool):
self._data["highlight_hotkey_autoswitch"] = value
el = gremlin.event_handler.EventListener()
el.config_option_changed.emit()
-
@property
def enable_remote_control(self):
- ''' enables or disables remote control from another gremlin instance on the network '''
- return self._data.get("allow_remote_control",False)
-
+ """enables or disables remote control from another gremlin instance on the network"""
+ return self._data.get("allow_remote_control", False)
+
@enable_remote_control.setter
def enable_remote_control(self, value):
if type(value) == bool:
@@ -726,14 +671,18 @@ def enable_remote_control(self, value):
@property
def enable_remote_broadcast(self):
- ''' enables gremlin to broadcast control changes over UDP multicast '''
- return self._data.get("enable_remote_broadcast",False)
+ """enables gremlin to broadcast control changes over UDP multicast"""
+ return self._data.get("enable_remote_broadcast", False)
@enable_remote_broadcast.setter
def enable_remote_broadcast(self, value):
- ''' remote broadcast master switch enable '''
+ """remote broadcast master switch enable"""
import gremlin.event_handler
- if type(value) == bool and self._data.get("enable_remote_broadcast",False)!= value:
+
+ if (
+ type(value) == bool
+ and self._data.get("enable_remote_broadcast", False) != value
+ ):
self._data["enable_remote_broadcast"] = value
self.save()
@@ -742,24 +691,20 @@ def enable_remote_broadcast(self, value):
@property
def enable_broadcast_speech(self):
- ''' speech on broadcast change mode enable'''
- return self._data.get("enable_broadcast_speech",True)
-
+ """speech on broadcast change mode enable"""
+ return self._data.get("enable_broadcast_speech", True)
+
@enable_broadcast_speech.setter
def enable_broadcast_speech(self, value):
-
if self.enable_broadcast_speech != value:
self._data["enable_broadcast_speech"] = value
self.save()
-
-
-
@property
def server_port(self):
- ''' port number to use for the gremlin server '''
- return self._data.get("server_port",6012)
-
+ """port number to use for the gremlin server"""
+ return self._data.get("server_port", 6012)
+
@server_port.setter
def server_port(self, value):
if type(value) == float:
@@ -805,7 +750,6 @@ def activate_on_launch(self, value):
self._data["activate_on_launch"] = bool(value)
self.save()
-
@property
def activate_on_process_focus(self):
"""Returns whether or not to activate the profile on process focus."""
@@ -817,9 +761,7 @@ def activate_on_process_focus(self, value):
self._data["activate_on_process_focus"] = bool(value)
self.save()
el = gremlin.event_handler.EventListener()
- el.process_monitor_changed.emit() # tell the UI the process options changed
-
-
+ el.process_monitor_changed.emit() # tell the UI the process options changed
@property
def close_to_tray(self):
@@ -879,7 +821,7 @@ def last_action(self):
:return default action to show in action selection drop downs
"""
return self._data.get("last_action", Configuration().default_action)
-
+
@last_action.setter
def last_action(self, value):
"""Sets the default action to show in action drop downs.
@@ -893,7 +835,7 @@ def last_action(self, value):
def last_container(self):
"""Returns the last container to show in container drop downs."""
return self._data.get("last_container", "basic")
-
+
@last_container.setter
def last_container(self, value):
"""Sets the last container to show in container drop downs.
@@ -903,7 +845,6 @@ def last_container(self, value):
self._data["last_container"] = str(value)
self.save()
-
@property
def macro_axis_polling_rate(self):
"""Returns the polling rate to use when recording axis macro actions.
@@ -920,6 +861,7 @@ def macro_axis_polling_rate(self, value):
@property
def macro_key_delay(self) -> int:
return self._data.get("macro_key_delay", 250)
+
@macro_key_delay.setter
def macro_key_delay(self, value: int):
self._data["macro_key_delay"] = value
@@ -1017,12 +959,11 @@ def window_location(self, value):
self._data["window_location"] = value
self.save()
-
@property
def persist_clipboard(self):
- ''' true if clipboard data is persisted from one session to the next '''
+ """true if clipboard data is persisted from one session to the next"""
return self._data.get("persist_clipboard", False)
-
+
@persist_clipboard.setter
def persist_clipboard(self, value):
self._data["persist_clipboard"] = value
@@ -1031,12 +972,13 @@ def persist_clipboard(self, value):
if not value:
# remove from disk any old data
from gremlin.clipboard import Clipboard
+
clipboard = Clipboard()
clipboard.clear_persisted()
@property
def verbose(self):
- ''' determines loging level '''
+ """determines loging level"""
value = self._data.get("verbose", None)
if value is None:
# not set - set other defaults
@@ -1052,26 +994,26 @@ def verbose(self, value):
@property
def verbose_mode(self):
- ''' sub logging level '''
- if not "verbose_mode" in self._data:
+ """sub logging level"""
+ if "verbose_mode" not in self._data:
self._data["verbose_mode"] = VerboseMode.All
self.save()
return VerboseMode(self._data["verbose_mode"])
-
+
def is_verbose_mode(self, mode):
value = self.verbose_mode
result = mode in value
return result
-
+
@verbose_mode.setter
def verbose_mode(self, value):
self._data["verbose_mode"] = value
self.save()
def verbose_set_mode(self, mode, enabled):
- ''' enables the specified verbose mode '''
- if not "verbose_mode" in self._data:
- self._data["verbose_mode"] = 0 # none
+ """enables the specified verbose mode"""
+ if "verbose_mode" not in self._data:
+ self._data["verbose_mode"] = 0 # none
value = self._data["verbose_mode"]
if enabled:
value |= mode
@@ -1081,116 +1023,113 @@ def verbose_set_mode(self, mode, enabled):
@property
def verbose_mode_keyboard(self):
- ''' true if verbose mode is in keyboard mode '''
+ """true if verbose mode is in keyboard mode"""
return self.verbose and VerboseMode.Keyboard in self.verbose_mode
-
@property
def verbose_mode_ui(self):
- ''' true if verbose mode is in UI mode '''
+ """true if verbose mode is in UI mode"""
return self.verbose and VerboseMode.UI in self.verbose_mode
-
-
+
@property
def verbose_mode_joystick(self):
- ''' true if verbose mode is in joystick mode '''
+ """true if verbose mode is in joystick mode"""
return self.verbose and VerboseMode.Joystick in self.verbose_mode
-
+
@property
def verbose_mode_inputs(self):
- ''' true if verbose mode is in inputs mode '''
+ """true if verbose mode is in inputs mode"""
return self.verbose and VerboseMode.Inputs in self.verbose_mode
@property
def verbose_mode_mouse(self):
- ''' true if verbose mode is in mouse mode '''
+ """true if verbose mode is in mouse mode"""
return self.verbose and VerboseMode.Mouse in self.verbose_mode
-
+
@property
def verbose_mode_details(self):
- ''' true if verbose mode is in detail mode '''
+ """true if verbose mode is in detail mode"""
return self.verbose and VerboseMode.Details in self.verbose_mode
-
+
@property
def verbose_mode_detailed(self):
- ''' true if verbose mode is in detail mode '''
+ """true if verbose mode is in detail mode"""
return self.verbose and VerboseMode.Details in self.verbose_mode
-
+
@property
def verbose_mode_device(self):
- ''' true if verbose mode is in device mode '''
+ """true if verbose mode is in device mode"""
return self.verbose and VerboseMode.Device in self.verbose_mode
@property
def verbose_mode_simconnect(self):
- ''' true if verbose mode is in simconnect mode '''
+ """true if verbose mode is in simconnect mode"""
return self.verbose and VerboseMode.SimConnect in self.verbose_mode
-
+
@property
def verbose_mode_condition(self):
- ''' true if verbose mode is in condition/execution mode '''
+ """true if verbose mode is in condition/execution mode"""
return self.verbose and VerboseMode.Condition in self.verbose_mode
-
+
@property
def verbose_mode_execution(self):
- ''' true if verbose mode is in condition/execution mode '''
+ """true if verbose mode is in condition/execution mode"""
return self.verbose and VerboseMode.Condition in self.verbose_mode
-
+
@property
def verbose_mode_osc(self):
- ''' true if verbose mode is in OSC mode '''
+ """true if verbose mode is in OSC mode"""
return self.verbose and VerboseMode.OSC in self.verbose_mode
-
+
@property
def verbose_mode_midi(self):
- ''' true if verbose mode is in MIDI mode '''
+ """true if verbose mode is in MIDI mode"""
return self.verbose and VerboseMode.Midi in self.verbose_mode
-
+
@property
def verbose_mode_process(self):
- ''' true if verbose mode is in process mode '''
+ """true if verbose mode is in process mode"""
return self.verbose and VerboseMode.Process in self.verbose_mode
-
+
@property
def verbose_mode_exec(self):
- ''' true if verbose mode is in Exec(ute) mode '''
+ """true if verbose mode is in Exec(ute) mode"""
return self.verbose and VerboseMode.Exec in self.verbose_mode
-
+
@property
def verbose_mode_macro(self):
- ''' true if verbose mode is in macro mode '''
+ """true if verbose mode is in macro mode"""
return self.verbose and VerboseMode.Macro in self.verbose_mode
-
+
@property
def verbose_mode_gate(self):
- ''' true if verbose mode is in gated axis mode '''
+ """true if verbose mode is in gated axis mode"""
return self.verbose and VerboseMode.Gate in self.verbose_mode
-
+
@property
def verbose_mode_outputs(self):
- ''' true if verbose mode is in output mode '''
+ """true if verbose mode is in output mode"""
return self.verbose and VerboseMode.Outputs in self.verbose_mode
-
+
@property
def verbose_mode_output(self):
return self.verbose_mode_outputs
-
+
@property
def midi_enabled(self):
- ''' true if MIDI module is enabled '''
+ """true if MIDI module is enabled"""
return self._data.get("midi_enabled", True)
-
+
@midi_enabled.setter
def midi_enabled(self, value):
self._data["midi_enabled"] = value
self.save()
-
@property
def osc_enabled(self):
- ''' true if osc module is enabled '''
+ """true if osc module is enabled"""
return self._data.get("osc_enabled", True)
-
+
@osc_enabled.setter
def osc_enabled(self, value):
self._data["osc_enabled"] = value
@@ -1198,9 +1137,10 @@ def osc_enabled(self, value):
@property
def osc_input_port(self):
- ''' OSC listen port '''
+ """OSC listen port"""
port = self._data.get("osc_port", 8000)
return port
+
@osc_input_port.setter
def osc_input_port(self, value):
self._data["osc_port"] = value
@@ -1208,30 +1148,31 @@ def osc_input_port(self, value):
@property
def osc_output_port(self):
- ''' OSC listen port '''
+ """OSC listen port"""
port = self._data.get("osc_output_port", 8001)
return port
+
@osc_output_port.setter
def osc_output_port(self, value):
self._data["osc_output_port"] = value
self.save()
-
+
@property
def osc_host(self):
- ''' OSC client host (this is the IP the machine GremlinEx sends OSC data to)'''
+ """OSC client host (this is the IP the machine GremlinEx sends OSC data to)"""
host = self._data.get("osc_host", "127.0.0.1")
return host
-
+
@osc_host.setter
- def osc_host(self, value : str):
+ def osc_host(self, value: str):
self._data["osc_host"] = value
self.save()
@property
def show_scancodes(self):
- ''' hide/show scan codes for keyboard related inputs '''
+ """hide/show scan codes for keyboard related inputs"""
return self._data.get("show_scancodes", False)
-
+
@show_scancodes.setter
def show_scancodes(self, value):
self._data["show_scancodes"] = value
@@ -1239,21 +1180,19 @@ def show_scancodes(self, value):
@property
def show_input_axis(self):
- ''' shows input axis values for axis inputs '''
- return self._data.get("show_input_axis",True)
-
+ """shows input axis values for axis inputs"""
+ return self._data.get("show_input_axis", True)
+
@show_input_axis.setter
def show_input_axis(self, value):
self._data["show_input_axis"] = value
self.save()
-
-
-
@property
def tab_list(self):
- ''' tab order for the UI devices as set by the user '''
+ """tab order for the UI devices as set by the user"""
return self._data.get("tab_order", None)
+
@tab_list.setter
def tab_list(self, value):
self._data["tab_order"] = value
@@ -1261,8 +1200,9 @@ def tab_list(self, value):
@property
def show_output_vjoy(self):
- ''' determines if VJOY output devices are displayed on the device tabs '''
+ """determines if VJOY output devices are displayed on the device tabs"""
return self._data.get("show_vjoy_ouput", False)
+
@show_output_vjoy.setter
def show_output_vjoy(self, value):
self._data["show_vjoy_output"] = value
@@ -1270,38 +1210,39 @@ def show_output_vjoy(self, value):
@property
def last_plugin_folder(self):
- ''' last folder used for plugins '''
- return self._data.get("last_plugin_folder",None)
+ """last folder used for plugins"""
+ return self._data.get("last_plugin_folder", None)
+
@last_plugin_folder.setter
def last_plugin_folder(self, value):
- self._data["last_plugin_folder"]=value
+ self._data["last_plugin_folder"] = value
self.save()
@property
def last_sound_folder(self):
- ''' last folder used for sounds '''
- return self._data.get("last_sound_folder",None)
+ """last folder used for sounds"""
+ return self._data.get("last_sound_folder", None)
+
@last_sound_folder.setter
def last_sound_folder(self, value):
self._data["last_sound_folder"] = value
self.save()
-
+
@property
def partial_plugin_save(self):
- ''' true if partial plugin configuration saving is ok = false nothing will be saved - true partial values will be saved '''
- return self._data.get("partial_plugin_init_ok",True)
-
+ """true if partial plugin configuration saving is ok = false nothing will be saved - true partial values will be saved"""
+ return self._data.get("partial_plugin_init_ok", True)
+
@partial_plugin_save.setter
def partial_plugin_save(self, value):
self._data["partial_plugin_init_ok"] = value
self.save()
-
@property
def runtime_ui_active(self):
- ''' keep UI enabled at runtime '''
- return self._data.get("runtime_ui_active",False)
-
+ """keep UI enabled at runtime"""
+ return self._data.get("runtime_ui_active", False)
+
@runtime_ui_active.setter
def runtime_ui_active(self, value):
self._data["runtime_ui_active"] = value
@@ -1309,27 +1250,27 @@ def runtime_ui_active(self, value):
@property
def sync_last_selection(self):
- ''' synchronizes the actions and container drop downs when enabled '''
- return self._data.get("sync_last_selection",True)
-
+ """synchronizes the actions and container drop downs when enabled"""
+ return self._data.get("sync_last_selection", True)
+
@sync_last_selection.setter
def sync_last_selection(self, value):
self._data["sync_last_selection"] = value
self.save()
-
@property
def last_keyboard_mapper_pulse_value(self):
return self._data.get("last_keyboard_mapper_pulse_value", 250)
+
@last_keyboard_mapper_pulse_value.setter
def last_keyboard_mapper_pulse_value(self, value):
self._data["last_keyboard_mapper_pulse_value"] = value
self.save()
-
@property
def last_keyboard_mapper_interval_value(self):
return self._data.get("last_keyboard_mapper_interval_value", 250)
+
@last_keyboard_mapper_interval_value.setter
def last_keyboard_mapper_interval_value(self, value):
self._data["last_keyboard_mapper_interval_value"] = value
@@ -1337,18 +1278,19 @@ def last_keyboard_mapper_interval_value(self, value):
@property
def runtime_ignore_device_change(self):
- ''' ignore device changes at runtime '''
+ """ignore device changes at runtime"""
return self._data.get("runtime_ignore_device_change", True)
+
@runtime_ignore_device_change.setter
def runtime_ignore_device_change(self, value):
self._data["runtime_ignore_device_change"] = value
self.save()
-
@property
def vigem_device_count(self):
- ''' count of gamepads to create for VIGEM - default is 1 '''
- return self._data.get("vigem_device_count",1)
+ """count of gamepads to create for VIGEM - default is 1"""
+ return self._data.get("vigem_device_count", 1)
+
@vigem_device_count.setter
def vigem_device_count(self, value):
if value < 0:
@@ -1360,73 +1302,75 @@ def vigem_device_count(self, value):
@property
def import_level(self):
- ''' import mapping expansion level 0 to 4 '''
- return self._data.get("import_level",1)
+ """import mapping expansion level 0 to 4"""
+ return self._data.get("import_level", 1)
@import_level.setter
def import_level(self, value):
self._data["import_level"] = value
self.save()
-
@property
def import_window_location(self):
- """Returns the position of the import profile """
+ """Returns the position of the import profile"""
return self._data.get("import_window_location", None)
@import_window_location.setter
def import_window_location(self, value):
- """Sets the position of the import profile window """
+ """Sets the position of the import profile window"""
self._data["import_window_location"] = value
self.save()
@property
def last_device_guid(self):
- ''' gets the last selected device guid'''
- device_guid = self._profile_data.get("last_device_guid", None) # try the profile specific config first
+ """gets the last selected device guid"""
+ device_guid = self._profile_data.get(
+ "last_device_guid", None
+ ) # try the profile specific config first
if not device_guid:
device_guid = self._data.get("last_device_guid", None)
return device_guid
-
+
@property
def last_dinput_device_guid(self):
- ''' last device guid as a dinput GUID instead of a string '''
+ """last device guid as a dinput GUID instead of a string"""
guid = self.last_device_guid
if guid:
return gremlin.util.parse_guid(guid)
return None
-
+
@last_device_guid.setter
def last_device_guid(self, device_guid):
- ''' stores the last device GUID '''
+ """stores the last device GUID"""
device_guid = str(device_guid)
- self._data["last_device_guid"] = device_guid # general config
- self._profile_data["last_device_guid"] = device_guid # profile specific config
+ self._data["last_device_guid"] = device_guid # general config
+ self._profile_data["last_device_guid"] = device_guid # profile specific config
self.save()
self.save_profile()
- def set_last_input(self, device_guid, input_type : gremlin.input_types.InputType, input_id ):
- ''' stores the last input '''
+ def set_last_input(
+ self, device_guid, input_type: gremlin.input_types.InputType, input_id
+ ):
+ """stores the last input"""
if gremlin.shared_state.is_tab_loading:
# don't save while tabs are loading
- return
-
+ return
+
el = gremlin.event_handler.EventListener()
if el.input_selection_suspended:
# ignore selection requests if selection is suspended
return
-
if not isinstance(device_guid, str):
device_guid = str(device_guid)
-
+ data: dict = self._profile_data.get("last_input", {})
- data : dict = self._profile_data.get("last_input", {})
-
verbose = self.verbose
-
- if input_type != gremlin.input_types.InputType.ModeControl and isinstance(input_id, gremlin.base_classes.AbstractInputItem):
+
+ if input_type != gremlin.input_types.InputType.ModeControl and isinstance(
+ input_id, gremlin.base_classes.AbstractInputItem
+ ):
# convert to an ID we can use
input_id = input_id.guid
elif input_id is None:
@@ -1442,10 +1386,14 @@ def set_last_input(self, device_guid, input_type : gremlin.input_types.InputType
if guid is not None:
input_id = str(guid)
else:
- syslog.warning(f"CONFIG: SetLastInput(): Don't know how to handle input_id [{input_id}] type: {type(input_id).__name__}")
+ syslog.warning(
+ f"CONFIG: SetLastInput(): Don't know how to handle input_id [{input_id}] type: {type(input_id).__name__}"
+ )
input_id = None
else:
- syslog.warning(f"CONFIG: SetLastInput(): Don't know how to handle input_id [{input_id}] type: {type(input_id).__name__}")
+ syslog.warning(
+ f"CONFIG: SetLastInput(): Don't know how to handle input_id [{input_id}] type: {type(input_id).__name__}"
+ )
input_id = None
input_type = gremlin.input_types.InputType.convert(input_type)
@@ -1456,52 +1404,55 @@ def set_last_input(self, device_guid, input_type : gremlin.input_types.InputType
self._profile_data["last_device_guid"] = device_guid
self._data["last_device_guid"] = device_guid
- self._data["last_input_type"] = gremlin.input_types.InputType.to_string(input_type)
+ self._data["last_input_type"] = gremlin.input_types.InputType.to_string(
+ input_type
+ )
self._data["last_input_id"] = input_id
- if verbose:
- device_name = gremlin.joystick_handling.device_name_from_guid(device_guid)
- syslog.info(f"CONFIG: save last input {device_name} {gremlin.input_types.InputType.to_string(input_type)} {input_id}")
-
+ if verbose:
+ device_name = gremlin.joystick_handling.device_name_from_guid(
+ device_guid
+ )
+ syslog.info(
+ f"CONFIG: save last input {device_name} {gremlin.input_types.InputType.to_string(input_type)} {input_id}"
+ )
+
self.save_profile()
self.save()
-
-
-
-
-
-
def get_last_device_guid(self):
- ''' gets the last selected device in the profile '''
- device_guid = self._profile_data.get("last_device_guid",None)
+ """gets the last selected device in the profile"""
+ device_guid = self._profile_data.get("last_device_guid", None)
if device_guid is None:
- device_guid = self._data.get("last_device_guid",None)
+ device_guid = self._data.get("last_device_guid", None)
return device_guid
-
- def _get_input_id(self, dinput_device_guid, input_id) -> tuple:
- ''' converts input ID from the storage data to the actual input ID that isn't stored in the config
+ def _get_input_id(self, dinput_device_guid, input_id) -> tuple:
+ """converts input ID from the storage data to the actual input ID that isn't stored in the config
:returns:
(input_type, save_input_id, input_id) # input type, the save input ID is a configuration "save" value for saving data, input_id is the object
-
- '''
+
+ """
save_input_id = input_id
input_type = None
# syslog = logging.getLogger("system")
- device_name = gremlin.joystick_handling.device_name_from_guid(dinput_device_guid)
- if not dinput_device_guid in gremlin.shared_state.device_type_map:
+ device_name = gremlin.joystick_handling.device_name_from_guid(
+ dinput_device_guid
+ )
+ if dinput_device_guid not in gremlin.shared_state.device_type_map:
# input is missing
- #syslog.warning(f"Config: get last input: Unable to determine input type: Unable to find device {dinput_device_guid} {device_name} in device map")
+ # syslog.warning(f"Config: get last input: Unable to determine input type: Unable to find device {dinput_device_guid} {device_name} in device map")
return (None, None, None)
-
+
# if input_id is None:
# syslog.warning(f"Config: get last input: Unable to determine input type: NULL input id")
# return (None, None, None)
device_type = gremlin.shared_state.device_type_map[dinput_device_guid]
if device_type == gremlin.types.DeviceType.Joystick:
- device_info = gremlin.joystick_handling.device_info_from_guid(dinput_device_guid)
+ device_info = gremlin.joystick_handling.device_info_from_guid(
+ dinput_device_guid
+ )
if device_info:
if device_info.axis_count > 0:
input_type = gremlin.input_types.InputType.JoystickAxis
@@ -1516,7 +1467,11 @@ def _get_input_id(self, dinput_device_guid, input_id) -> tuple:
if input_id is None or input_id > device_info.hat_count:
input_id = 1
- elif device_type in (gremlin.types.DeviceType.Keyboard, gremlin.types.DeviceType.Midi, gremlin.types.DeviceType.Osc):
+ elif device_type in (
+ gremlin.types.DeviceType.Keyboard,
+ gremlin.types.DeviceType.Midi,
+ gremlin.types.DeviceType.Osc,
+ ):
# grab the tab widget
if device_type == gremlin.types.DeviceType.Keyboard:
input_type = gremlin.input_types.InputType.KeyboardLatched
@@ -1556,28 +1511,29 @@ def _get_input_id(self, dinput_device_guid, input_id) -> tuple:
input_type = gremlin.input_types.InputType.NotSet
save_input_id = None
input_id = None
-
-
+
else:
assert False, f"Config: GetInputId() Don't know how to handle device type: {device_type} {device_name}"
if input_type is None or input_id is None:
- syslog.warning(f"Config: get last input: Unable to determine input type for device {dinput_device_guid} {device_name}")
+ syslog.warning(
+ f"Config: get last input: Unable to determine input type for device {dinput_device_guid} {device_name}"
+ )
return (None, None, None)
return (input_type, save_input_id, input_id)
+ def get_last_input(
+ self, device_guid=None
+ ) -> tuple: # (device_guid, input_type, input_id)
+ """gets the last input for a given device
- def get_last_input(self, device_guid = None) -> tuple: # (device_guid, input_type, input_id)
- ''' gets the last input for a given device
-
:param device_guid: (optional) the device to look for - if None - uses the last known device
:returns tuple: (device_guid, input_type, input_id)
-
- '''
- # syslog = logging.getLogger("system")
+ """
+ # syslog = logging.getLogger("system")
if device_guid is None:
# get the last profile device guid saved to config
@@ -1588,7 +1544,11 @@ def get_last_input(self, device_guid = None) -> tuple: # (device_guid, input_typ
input_type = gremlin.input_types.InputType.to_enum(input_type)
input_id = self._data.get("last_input_id", None)
- if device_guid is not None and input_type is not None and input_id is not None:
+ if (
+ device_guid is not None
+ and input_type is not None
+ and input_id is not None
+ ):
return (device_guid, input_type, input_id)
if device_guid is None:
@@ -1608,83 +1568,90 @@ def get_last_input(self, device_guid = None) -> tuple: # (device_guid, input_typ
device_guid = str(device_guid)
else:
dinput_device_guid = gremlin.util.parse_guid(device_guid)
- data : dict = self._profile_data.get("last_input", {})
+ data: dict = self._profile_data.get("last_input", {})
if device_guid in data:
input_type, input_id = data[device_guid]
try:
input_type = gremlin.input_types.InputType.to_enum(input_type)
except:
- syslog.error(f"GetLastInput(): unable to convert input type {input_type} to a known type")
+ syslog.error(
+ f"GetLastInput(): unable to convert input type {input_type} to a known type"
+ )
input_type = gremlin.input_types.InputType.NotSet
if input_id is not None and isinstance(input_id, int):
return (device_guid, input_type, input_id)
-
+
if input_id is not None:
- input_type, save_input_id, input_id = self._get_input_id(dinput_device_guid, input_id)
+ input_type, save_input_id, input_id = self._get_input_id(
+ dinput_device_guid, input_id
+ )
if input_type is None:
if verbose:
- syslog.info(f"Loading input selection: nothing found for {device_guid} {device_name}")
+ syslog.info(
+ f"Loading input selection: nothing found for {device_guid} {device_name}"
+ )
return (None, None, None)
if verbose:
- syslog.info(f"Loading saved input selection: {device_guid} {device_name} {input_type} {input_id}")
+ syslog.info(
+ f"Loading saved input selection: {device_guid} {device_name} {input_type} {input_id}"
+ )
try:
input_type = gremlin.input_types.InputType.to_enum(input_type)
except:
- syslog.error(f"GetLastInput(): unable to convert input type {input_type} to a known type")
+ syslog.error(
+ f"GetLastInput(): unable to convert input type {input_type} to a known type"
+ )
input_type = gremlin.input_types.InputType.NotSet
return (device_guid, input_type, input_id)
# provide a suitable default for the input
input_id = None
if dinput_device_guid in gremlin.shared_state.device_type_map:
- input_type, save_input_id, input_id = self._get_input_id(dinput_device_guid, input_id)
+ input_type, save_input_id, input_id = self._get_input_id(
+ dinput_device_guid, input_id
+ )
if input_type is not None and input_id is not None:
# save the new defaults
data[device_guid] = (input_type, save_input_id)
self._profile_data["last_input"] = data
self.save_profile()
if verbose:
- syslog.info(f"Loading default input selection: {device_guid} {device_name} {input_type} {input_id}")
+ syslog.info(
+ f"Loading default input selection: {device_guid} {device_name} {input_type} {input_id}"
+ )
return (device_guid, input_type, input_id)
-
if verbose:
- syslog.info(f"Loading input selection: nothing found for {device_guid} {device_name}")
+ syslog.info(
+ f"Loading input selection: nothing found for {device_guid} {device_name}"
+ )
return (None, None, None)
-
-
-
-
def ensure_profile(self, profile):
- ''' called when a new profile is created '''
+ """called when a new profile is created"""
if not profile or not profile.profile_file:
- return # nothing to do - no profile
-
+ return # nothing to do - no profile
+
if self._profile_fname == profile.profile_file:
- return # nothing to do - same profile
-
+ return # nothing to do - same profile
+
self._profile_fname = profile.profile_file
fname = self.get_profile_config()
-
-
-
+
self._profile_config_fname = fname
- self.reload_profile()
+ self.reload_profile()
# hook profile load
if not self._profile_loaded:
eh = gremlin.event_handler.EventListener()
eh.profile_changed.connect(self._profile_changed_cb)
self._profile_loaded = True
-
if self._profile_fname and not os.path.isfile(self._profile_fname):
self.save_profile()
-
@QtCore.Slot()
def _profile_changed_cb(self):
self._last_profile_reload = None
@@ -1692,7 +1659,8 @@ def _profile_changed_cb(self):
@property
def debug_ui(self):
- return self._data.get("debug_ui",False)
+ return self._data.get("debug_ui", False)
+
@debug_ui.setter
def debug_ui(self, value):
self._data["debug_ui"] = value
@@ -1700,94 +1668,93 @@ def debug_ui(self, value):
@property
def button_grid_visible(self) -> bool:
- ''' default state of the button grid in vjoy remap '''
+ """default state of the button grid in vjoy remap"""
return self._data.get("button_grid_visible", True)
+
@button_grid_visible.setter
- def button_grid_visible(self, value : bool):
+ def button_grid_visible(self, value: bool):
self._data["button_grid_visible"] = value
self.save()
@property
def midi_enabled(self) -> bool:
- ''' true if MIDI support is enabled '''
+ """true if MIDI support is enabled"""
if self._midi_enabled is None:
from gremlin.ui.midi_device import MidiInterface
+
midi = MidiInterface()
- self._midi_enabled = midi.midi_enabled and self._data.get("midi_enabled", True)
+ self._midi_enabled = midi.midi_enabled and self._data.get(
+ "midi_enabled", True
+ )
return self._midi_enabled
-
+
@midi_enabled.setter
- def midi_enabled(self, value : bool):
+ def midi_enabled(self, value: bool):
self._data["midi_enabled"] = value
- self._midi_enabled = None # force a re-read
-
-
+ self._midi_enabled = None # force a re-read
@property
def osc_enabled(self) -> bool:
- ''' True if OSC support is enabled'''
+ """True if OSC support is enabled"""
if self._osc_enabled is None:
self._osc_enabled = self._data.get("osc_enabled", True)
return self._osc_enabled
-
-
+
@osc_enabled.setter
- def osc_enabled(self, value : bool):
+ def osc_enabled(self, value: bool):
self._data["osc_enabled"] = value
- self._osc_enabled = None # force a re-read
-
+ self._osc_enabled = None # force a re-read
# @property
# def splitter_pos(self) -> int:
# ''' splitter config '''
# return self._data.get("splitter_config", 250)
-
+
# @splitter_pos.setter
# def splitter_pos(self, data : int):
# self._data["splitter_config"] = data
- @property
+ @property
def mapping_rollover_mode(self):
- return self._data.get("mapping_rollover_mode",1)
-
+ return self._data.get("mapping_rollover_mode", 1)
+
@mapping_rollover_mode.setter
- def mapping_rollover_mode(self, mode : int):
+ def mapping_rollover_mode(self, mode: int):
self._data["mapping_rollover_mode"] = mode
self.save()
- @property
+ @property
def mapping_vjoy_id(self):
- return self._data.get("mapping_vjoy_id",1)
+ return self._data.get("mapping_vjoy_id", 1)
@mapping_vjoy_id.setter
- def mapping_vjoy_id(self, id : int):
+ def mapping_vjoy_id(self, id: int):
self._data["mapping_vjoy_id"] = id
- self.save()
+ self.save()
- @property
+ @property
def convert_response_curve(self):
return self._data.get("convert_response_curve", True)
@convert_response_curve.setter
- def convert_response_curve(self, value : bool):
+ def convert_response_curve(self, value: bool):
self._data["convert_response_curve"] = value
- self.save()
+ self.save()
- @property
+ @property
def convert_vjoy_remap(self):
return self._data.get("convert_vjoy_remap", False)
@convert_vjoy_remap.setter
def convert_vjoy_remap(self, value: bool):
self._data["convert_vjoy_remap"] = value
- self.save()
-
+ self.save()
@property
def numlock_off(self) -> bool:
- ''' force numlock off - global setting '''
+ """force numlock off - global setting"""
return self._data.get("numlock_off", True)
-
+
@numlock_off.setter
def numlock_off(self, value: bool):
self._data["numlock_off"] = value
@@ -1795,20 +1762,19 @@ def numlock_off(self, value: bool):
@property
def condition_selector(self) -> str:
- ''' last selected condition selector '''
- return self._data.get("condition_selector","Joystick Condition")
-
+ """last selected condition selector"""
+ return self._data.get("condition_selector", "Joystick Condition")
+
@condition_selector.setter
def condition_selector(self, value: str):
self._data["condition_selector"] = value
self.save()
-
@property
def experimental(self) -> bool:
- ''' true if internal dev mode '''
- return self._data.get("experimental",False)
-
+ """true if internal dev mode"""
+ return self._data.get("experimental", False)
+
@experimental.setter
def experimental(self, value: bool):
self._data["experimental"] = value
@@ -1816,116 +1782,114 @@ def experimental(self, value: bool):
@property
def show_input_enable(self) -> bool:
- return self._data.get("show_input_enable",False)
+ return self._data.get("show_input_enable", False)
+
@show_input_enable.setter
def show_input_enable(self, value: bool):
self._data["show_input_enable"] = value
self.save()
-
-
- def getWindowSize(self, key : str):
- ''' gets window geometry size'''
- data = self._data.get("window_geo_size",{})
+ def getWindowSize(self, key: str):
+ """gets window geometry size"""
+ data = self._data.get("window_geo_size", {})
if key in data:
return data[key]
- return None
+ return None
- def setWindowSize(self, key : str, width : int, height : int):
- ''' sets window geometry size '''
- data = self._data.get("window_geo_size",{})
+ def setWindowSize(self, key: str, width: int, height: int):
+ """sets window geometry size"""
+ data = self._data.get("window_geo_size", {})
data[key] = [width, height]
self._data["window_geo_size"] = data
self.save()
-
- def getWindowLocation(self, key : str):
- ''' gets window geometry location '''
- data = self._data.get("window_geo_loc",{})
+ def getWindowLocation(self, key: str):
+ """gets window geometry location"""
+ data = self._data.get("window_geo_loc", {})
if key in data:
return data[key]
- return None
+ return None
- def setWindowLocation(self, key : str, x : int, y : int):
- ''' sets window geometry location '''
- data = self._data.get("window_geo_loc",{})
+ def setWindowLocation(self, key: str, x: int, y: int):
+ """sets window geometry location"""
+ data = self._data.get("window_geo_loc", {})
data[key] = [x, y]
self._data["window_geo_loc"] = data
self.save()
-
def clearWindowData(self):
- ''' resets saved window data '''
+ """resets saved window data"""
self._data["window_geo_loc"] = {}
self._data["window_geo_size"] = {}
self.save()
-
- @property
+ @property
def backup_count(self) -> int:
- return self._data.get("backup_count", 5)
+ return self._data.get("backup_count", 5)
+
@backup_count.setter
def backup_count(self, value: int):
if value < 0:
value = 0
elif value > 50:
value = 50
- self._data["backup_count"]= value
+ self._data["backup_count"] = value
self.save()
-
@property
def start_on_f5(self) -> bool:
return self._data.get("start_on_f5", False)
+
@start_on_f5.setter
- def start_on_f5(self, value:bool):
+ def start_on_f5(self, value: bool):
self._data["start_on_f5"] = value
self.save()
-
@property
def keyboard_repeater_invert_display(self) -> bool:
return self._data.get("keyboard_repeater_invert_display", False)
-
+
@keyboard_repeater_invert_display.setter
- def keyboard_repeater_invert_display(self, value : bool):
+ def keyboard_repeater_invert_display(self, value: bool):
self._data["keyboard_repeater_invert_display"] = value
self.save()
@property
def keyboard_repeater_capture_mouse(self) -> bool:
return self._data.get("keyboard_repeater_capture_mouse", False)
-
+
@keyboard_repeater_capture_mouse.setter
- def keyboard_repeater_capture_mouse(self, value : bool):
+ def keyboard_repeater_capture_mouse(self, value: bool):
self._data["keyboard_repeater_capture_mouse"] = value
self.save()
@property
def keyboard_repeater_show(self) -> bool:
return self._data.get("keyboard_repeater_show", True)
-
+
@keyboard_repeater_show.setter
- def keyboard_repeater_show(self, value : bool):
+ def keyboard_repeater_show(self, value: bool):
self._data["keyboard_repeater_show"] = value
self.save()
@property
def theme(self) -> str:
- return self._data.get("theme","auto")
+ return self._data.get("theme", "auto")
+
@theme.setter
- def theme(self, value :str):
+ def theme(self, value: str):
if value:
value = value.casefold()
- if value in ("auto","light","dark"):
+ if value in ("auto", "light", "dark"):
self._data["theme"] = value
self.save()
@property
def keySize(self) -> int:
- ''' size of keys for the virtual keyboard'''
- return self._data.get("keySize",1)
+ """size of keys for the virtual keyboard"""
+ return self._data.get("keySize", 1)
+
@keySize.setter
- def keySize(self, value : int):
- self._data["keySize"] = value
- self.save()
+ def keySize(self, value: int):
+ self._data["keySize"] = value
+ self.save()
diff --git a/gremlin/control_action.py b/gremlin/control_action.py
index e989f46c..45ecb328 100644
--- a/gremlin/control_action.py
+++ b/gremlin/control_action.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -23,7 +23,6 @@
class ModeList:
-
"""Represents a list of modes to cycle through."""
def __init__(self, modes):
@@ -57,7 +56,7 @@ def switch_to_previous_mode():
eh.change_mode(eh.previous_mode)
-def cycle_modes(mode_list : list):
+def cycle_modes(mode_list: list):
"""Cycles to the next mode in the provided mode list.
If the currently active mode is not in the provided list of modes
@@ -74,7 +73,6 @@ def cycle_modes(mode_list : list):
if index == len(mode_list):
index = 0
next_mode = mode_list[index]
-
gremlin.event_handler.EventHandler().change_mode(next_mode)
diff --git a/gremlin/curve_handler.py b/gremlin/curve_handler.py
index 27bd9ada..46a6f374 100644
--- a/gremlin/curve_handler.py
+++ b/gremlin/curve_handler.py
@@ -1,8 +1,6 @@
-
-
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -20,14 +18,12 @@
from __future__ import annotations
import os
from lxml import etree as ElementTree
-from PySide6 import QtWidgets, QtCore, QtGui #QtWebEngineWidgets
+from PySide6 import QtWidgets, QtCore, QtGui # QtWebEngineWidgets
import gremlin.base_profile
import gremlin.config
-import gremlin.config
import gremlin.event_handler
import gremlin.execution_graph
-from gremlin.input_types import InputType
import gremlin.joystick_handling
import gremlin.shared_state
import gremlin.macro
@@ -35,33 +31,32 @@
import gremlin.ui.joystick_device
import gremlin.ui.input_item
import gremlin.ui.ui_common
-from gremlin.ui.qsliderwidget import QSliderWidget
import gremlin.util
from gremlin.util import *
from gremlin.types import *
import gremlin.clipboard
-from enum import Enum, auto
from gremlin.macro_handler import *
import gremlin.util
import gremlin.singleton_decorator
-from gremlin.util import InvokeUiMethod
import gremlin.util
-from itertools import pairwise
-from gremlin.ui.ui_common import DynamicDoubleSpinBox, DualSlider, get_text_width
+from gremlin.ui.ui_common import get_text_width
import enum
from lxml import etree
from gremlin.ui.deadzone import DeadzonePreset, DeadzoneWidget
import logging
+
syslog = logging.getLogger("system")
g_scene_size = 250.0
+
class CurvePreset(enum.IntEnum):
- ''' preset enums '''
+ """preset enums"""
+
Bezier1 = 1
Bezier2 = 2
Bezier3 = 3
@@ -69,23 +64,23 @@ class CurvePreset(enum.IntEnum):
Reset = 5
@staticmethod
- def to_display(value : CurvePreset) -> str:
+ def to_display(value: CurvePreset) -> str:
return _curve_preset_string_lookup[value]
+
_curve_preset_string_lookup = {
- CurvePreset.Bezier1 : "Bezier 1",
- CurvePreset.Bezier2 : "Bezier 2",
- CurvePreset.Bezier3 : "Bezier 3",
- CurvePreset.Bezier4 : "Bezier 4",
- CurvePreset.Reset : "Reset",
+ CurvePreset.Bezier1: "Bezier 1",
+ CurvePreset.Bezier2: "Bezier 2",
+ CurvePreset.Bezier3: "Bezier 3",
+ CurvePreset.Bezier4: "Bezier 4",
+ CurvePreset.Reset: "Reset",
}
class Point2D:
-
"""Represents a 2D point with support for addition and subtraction."""
- def __init__(self, x : float = 0.0, y : float = 0.0):
+ def __init__(self, x: float = 0.0, y: float = 0.0):
"""Creates a new instance.
:param x the x coordinate
@@ -95,18 +90,18 @@ def __init__(self, x : float = 0.0, y : float = 0.0):
self.x = float(x)
except:
self.x = 0.0
- try:
+ try:
self.y = float(y)
except:
self.y = 0.0
def __iter__(self):
- ''' iterator version '''
+ """iterator version"""
for value in [self.x, self.y]:
yield value
-
+
def __getitem__(self, item):
- ''' indexable version '''
+ """indexable version"""
if item == 0:
return self.x
if item == 1:
@@ -114,10 +109,9 @@ def __getitem__(self, item):
raise IndexError("Index out of range")
def __len__(self):
- ''' size of the iteration '''
+ """size of the iteration"""
return 2
-
def __add__(self, other):
return Point2D(self.x + other.x, self.y + other.y)
@@ -126,50 +120,52 @@ def __sub__(self, other):
def __str__(self):
return f"[{self.x:.3f}, {self.y:.3f}]"
-
def __eq__(self, other):
- return gremlin.util.is_close(self.x, other.x) and \
- gremlin.util.is_close(self.y, other.y)
-
+ return gremlin.util.is_close(self.x, other.x) and gremlin.util.is_close(
+ self.y, other.y
+ )
+
def __hash__(self):
return hash((self.x, self.y))
+
class CurveType(enum.Enum):
- ''' supported curve types '''
+ """supported curve types"""
+
Cubic = 0
Bezier = 1
@staticmethod
- def to_string(value : CurveType) -> str:
+ def to_string(value: CurveType) -> str:
return _curve_type_to_string_lookup[value]
-
+
@staticmethod
- def to_enum(value : str) -> CurveType:
+ def to_enum(value: str) -> CurveType:
return _curve_type_to_enum_lookup[value]
-
+
@staticmethod
- def to_display(value : CurveType) -> str:
+ def to_display(value: CurveType) -> str:
return _curve_type_to_display_name[value]
-
+
_curve_type_to_string_lookup = {
- CurveType.Cubic : "cubic-spline",
- CurveType.Bezier : "cubic-bezier-spline"
+ CurveType.Cubic: "cubic-spline",
+ CurveType.Bezier: "cubic-bezier-spline",
}
_curve_type_to_enum_lookup = {
- "cubic-spline" : CurveType.Cubic,
- "cubic-bezier-spline" : CurveType.Bezier
+ "cubic-spline": CurveType.Cubic,
+ "cubic-bezier-spline": CurveType.Bezier,
}
_curve_type_to_display_name = {
- CurveType.Cubic : "Cubic Spline",
- CurveType.Bezier : "Cubic Bezier Spline"
+ CurveType.Cubic: "Cubic Spline",
+ CurveType.Bezier: "Cubic Bezier Spline",
}
-class SymmetryMode(enum.Enum):
+class SymmetryMode(enum.Enum):
"""Symmetry modes for response curves."""
NoSymmetry = 1
@@ -178,25 +174,24 @@ class SymmetryMode(enum.Enum):
@staticmethod
def to_string(value):
return _symmetry_mode_to_string[value]
-
+
@staticmethod
def to_enum(value):
return _symmetry_mode_to_enum[value]
+
_symmetry_mode_to_string = {
SymmetryMode.NoSymmetry: "none",
- SymmetryMode.Diagonal: "diagonal"
+ SymmetryMode.Diagonal: "diagonal",
}
_symmetry_mode_to_enum = {
- "none" : SymmetryMode.NoSymmetry,
- "diagonal" : SymmetryMode.Diagonal
+ "none": SymmetryMode.NoSymmetry,
+ "diagonal": SymmetryMode.Diagonal,
}
-
class ControlPoint:
-
"""Represents a single control point in a response curve.
Each control point has at least a center point but can possibly have
@@ -220,7 +215,7 @@ def __init__(self, model, center, handles=()):
self._handles = [hdl for hdl in handles]
self._identifier = ControlPoint.next_id
self._last_modified = time.time()
- self._last_modified_handle_index = 0 # index of the last modified handle
+ self._last_modified_handle_index = 0 # index of the last modified handle
ControlPoint.next_id += 1
@property
@@ -269,12 +264,12 @@ def set_center(self, point, emit_model_update=True):
self._model.model_updated()
eh.message.emit(None)
else:
-
- eh.message.emit(f"Invalid point")
+ eh.message.emit("Invalid point")
@property
def x(self) -> float:
return self._center.x
+
@property
def y(self) -> float:
return self._center.y
@@ -287,7 +282,7 @@ def identifier(self):
def handles(self):
return self._handles
- def set_handle(self, index : int, point : Point2D):
+ def set_handle(self, index: int, point: Point2D):
"""Sets the location of the specified handle.
:param index the id of the handle to modify
@@ -296,9 +291,11 @@ def set_handle(self, index : int, point : Point2D):
if len(self.handles) > index:
self._last_modified = time.time()
self.handles[index] = point
- if len(self.handles) == 2 and \
- isinstance(self._model, CubicBezierSplineModel) and \
- self._model.handle_symmetry_enabled:
+ if (
+ len(self.handles) == 2
+ and isinstance(self._model, CubicBezierSplineModel)
+ and self._model.handle_symmetry_enabled
+ ):
alt_point = self._center + (self._center - point)
alt_index = 1 if index == 0 else 0
self.handles[alt_index] = alt_point
@@ -316,28 +313,38 @@ def __eq__(self, other):
:param other the control point to compare with for identity
:return True of the control points are the same, False otherwise
"""
- #return self.identifier == other.identifier
- return gremlin.util.is_close(self.x, other.x) and \
- gremlin.util.is_close(self.y, other.y)
+ # return self.identifier == other.identifier
+ return gremlin.util.is_close(self.x, other.x) and gremlin.util.is_close(
+ self.y, other.y
+ )
def __hash__(self):
return hash(self.identifier)
+
@gremlin.singleton_decorator.SingletonDecorator
class CurveEventHandler(QtCore.QObject):
- ''' handler of events related to the curve handler '''
- message = QtCore.Signal(str) # displays an informational message
- selected_item = QtCore.Signal(object, object, int) # (point_editor, item selected, index of item select : int) - the graphics item selected
- next_point = QtCore.Signal(object) # navigate to the next control point (point_editor)
- prev_point = QtCore.Signal(object) # navigate to the previous control point (point_editor)
- handle_match_x = QtCore.Signal(object) # match control point x value (point_editor)
- handle_match_y = QtCore.Signal(object) # match control point y value (point_editor)
- delete_point = QtCore.Signal(object) # delete the control point (point_editor)
- value_changed = QtCore.Signal(float) # output value changed (point_editor,value:float)
+ """handler of events related to the curve handler"""
+
+ message = QtCore.Signal(str) # displays an informational message
+ selected_item = QtCore.Signal(
+ object, object, int
+ ) # (point_editor, item selected, index of item select : int) - the graphics item selected
+ next_point = QtCore.Signal(
+ object
+ ) # navigate to the next control point (point_editor)
+ prev_point = QtCore.Signal(
+ object
+ ) # navigate to the previous control point (point_editor)
+ handle_match_x = QtCore.Signal(object) # match control point x value (point_editor)
+ handle_match_y = QtCore.Signal(object) # match control point y value (point_editor)
+ delete_point = QtCore.Signal(object) # delete the control point (point_editor)
+ value_changed = QtCore.Signal(
+ float
+ ) # output value changed (point_editor,value:float)
class AbstractCurveModel(QtCore.QObject):
-
"""Abstract base class for all curve models."""
# Signal emitted when model data changes
@@ -347,7 +354,7 @@ class AbstractCurveModel(QtCore.QObject):
def __init__(self, action_data, parent=None):
"""Initializes an empty model.
-
+
:param profile_data the data of this response curve
"""
super().__init__(parent)
@@ -372,7 +379,6 @@ def model_updated(self):
self._enforce_symmetry()
self.save_to_profile()
self.content_modified.emit()
-
def get_curve_function(self):
"""Returns the curve function corresponding to the model.
@@ -402,10 +408,9 @@ def add_control_point(self, point, handles=()):
self._control_points.append(cp)
if self.symmetry_mode == SymmetryMode.Diagonal:
- self._control_points.append(self._create_control_point(
- Point2D(-point.x, -point.y),
- handles
- ))
+ self._control_points.append(
+ self._create_control_point(Point2D(-point.x, -point.y), handles)
+ )
self.save_to_profile()
self.content_added.emit()
@@ -458,7 +463,7 @@ def save_to_profile(self):
)
def _enforce_symmetry(self):
- ''' enforces symmetry of all points '''
+ """enforces symmetry of all points"""
count = len(self._control_points)
ordered_cp = sorted(self._control_points, key=lambda x: x.center.x)
@@ -496,9 +501,7 @@ def _enforce_symmetry(self):
h2 = 1
if index == 0:
h1, h2 = h2, h1
- cp.handles[h1] = cp.center - cp.handles[h2]
-
-
+ cp.handles[h1] = cp.center - cp.handles[h2]
def set_symmetry_mode(self, mode):
"""Sets the symmetry mode of the curve model.
@@ -512,10 +515,9 @@ def set_symmetry_mode(self, mode):
self.add_control_point(Point2D(0.0, 0.0))
self.content_added.emit()
self._enforce_symmetry()
-
-class CubicSplineModel(AbstractCurveModel):
+class CubicSplineModel(AbstractCurveModel):
"""Represents a simple cubic spline model."""
def __init__(self, profile_data):
@@ -562,9 +564,7 @@ def is_valid_point(self, point, identifier=None):
def _init_from_profile_data(self):
"""Initializes the control points based on profile data."""
for coord in self._action_data.control_points:
- self._control_points.append(
- ControlPoint(self, Point2D(coord[0], coord[1]))
- )
+ self._control_points.append(ControlPoint(self, Point2D(coord[0], coord[1])))
def save_to_profile(self):
"""Ensures that the control point data is properly recorded in
@@ -576,7 +576,6 @@ def save_to_profile(self):
class CubicBezierSplineModel(AbstractCurveModel):
-
"""Represents a cubic bezier spline model."""
def __init__(self, profile_data):
@@ -590,9 +589,7 @@ def get_curve_function(self):
:return curve function corresponding to the model
"""
points = []
- sorted_control_points = sorted(
- self._control_points, key=lambda e: e.center.x
- )
+ sorted_control_points = sorted(self._control_points, key=lambda e: e.center.x)
for i, pt in enumerate(sorted_control_points):
if i == 0:
points.append((pt.center.x, pt.center.y))
@@ -626,7 +623,7 @@ def _create_control_point(self, point, handles=()):
if len(handles) == 0:
handles = (
Point2D(point.x - 0.05, point.y),
- Point2D(point.x + 0.05, point.y)
+ Point2D(point.x + 0.05, point.y),
)
return ControlPoint(self, point, handles)
@@ -647,7 +644,7 @@ def is_valid_point(self, point, identifier=None):
def _init_from_profile_data(self):
"""Initializes the spline with profile data.
-
+
expecting 3 points and 3 handles - the points must be left, center and right with x = -1, x = 0, and x = 1
the second value in the series is the handle coordinate
@@ -659,43 +656,40 @@ def _init_from_profile_data(self):
point_center point_handle_1 point_handle_2
...
point_last handle_last (x must be + 1)
-
+
"""
# If the data appears to be invalid insert a valid default
if len(self._action_data.control_points) < 4:
self._action_data.control_points = []
- self._action_data.control_points.extend([
- (-1, -1),
- (-0.9, -0.9),
- (0.9, 0.9),
- (1, 1)
- ])
+ self._action_data.control_points.extend(
+ [(-1, -1), (-0.9, -0.9), (0.9, 0.9), (1, 1)]
+ )
coordinates = self._action_data.control_points
self._control_points.append(
ControlPoint(
self,
Point2D(coordinates[0][0], coordinates[0][1]),
- [Point2D(coordinates[1][0], coordinates[1][1])]
+ [Point2D(coordinates[1][0], coordinates[1][1])],
)
)
- for i in range(3, len(coordinates)-3, 3):
+ for i in range(3, len(coordinates) - 3, 3):
self._control_points.append(
ControlPoint(
self,
Point2D(coordinates[i][0], coordinates[i][1]),
[
- Point2D(coordinates[i-1][0], coordinates[i-1][1]),
- Point2D(coordinates[i+1][0], coordinates[i+1][1])
- ]
+ Point2D(coordinates[i - 1][0], coordinates[i - 1][1]),
+ Point2D(coordinates[i + 1][0], coordinates[i + 1][1]),
+ ],
)
)
self._control_points.append(
ControlPoint(
self,
Point2D(coordinates[-1][0], coordinates[-1][1]),
- [Point2D(coordinates[-2][0], coordinates[-2][1])]
+ [Point2D(coordinates[-2][0], coordinates[-2][1])],
)
)
@@ -704,44 +698,31 @@ def save_to_profile(self):
self._action_data.mapping_type = CurveType.Bezier
- control_points = sorted(
- self._control_points,
- key=lambda entry: entry.center.x
- )
+ control_points = sorted(self._control_points, key=lambda entry: entry.center.x)
self._action_data.control_points = []
for cp in control_points:
- if cp.center.x == -1: # left point
- self._action_data.control_points.append(
- [cp.center.x, cp.center.y]
- )
+ if cp.center.x == -1: # left point
+ self._action_data.control_points.append([cp.center.x, cp.center.y])
self._action_data.control_points.append(
[cp.handles[0].x, cp.handles[0].y]
)
- elif cp.center.x == 1: # right point
+ elif cp.center.x == 1: # right point
self._action_data.control_points.append(
[cp.handles[0].x, cp.handles[0].y]
)
- self._action_data.control_points.append(
- [cp.center.x, cp.center.y]
- )
- else: # other points in the middle
+ self._action_data.control_points.append([cp.center.x, cp.center.y])
+ else: # other points in the middle
self._action_data.control_points.append(
[cp.handles[0].x, cp.handles[0].y]
)
- self._action_data.control_points.append(
- [cp.center.x, cp.center.y]
- )
+ self._action_data.control_points.append([cp.center.x, cp.center.y])
self._action_data.control_points.append(
[cp.handles[1].x, cp.handles[1].y]
)
-
-
-
class DataPointGraphicsItem(QtWidgets.QGraphicsEllipseItem):
-
"""UI Item representing a data point center of a control point."""
def __init__(self, x, y, parent=None):
@@ -756,25 +737,23 @@ def __init__(self, x, y, parent=None):
self.y = y
self.setPos(x, y)
-
+
self.setZValue(3)
color = QtGui.QColor("#f27e0a")
color.setAlpha(128)
self.setBrush(QtGui.QBrush(color))
-
def update(self, x, y):
self.x = x
self.y = y
self.redraw()
-
def redraw(self):
"""Forces a position update of the ui element."""
self.setPos(self.x, self.y)
-class ControlPointGraphicsItem(QtWidgets.QGraphicsEllipseItem):
+class ControlPointGraphicsItem(QtWidgets.QGraphicsEllipseItem):
"""UI Item representing the center of a control point."""
def __init__(self, control_point, parent=None):
@@ -784,15 +763,14 @@ def __init__(self, control_point, parent=None):
:param parent the parent of this widget
"""
super().__init__(-4, -4, 8, 8, parent)
- assert(isinstance(control_point, ControlPoint))
+ assert isinstance(control_point, ControlPoint)
self.control_point = control_point
self.parent = parent
-
self.setPos(
g_scene_size * self.control_point.center.x,
- -g_scene_size * self.control_point.center.y
+ -g_scene_size * self.control_point.center.y,
)
self.setZValue(2)
self.setBrush(QtGui.QBrush(QtCore.Qt.gray))
@@ -811,7 +789,7 @@ def redraw(self):
"""Forces a position update of the ui element."""
self.setPos(
g_scene_size * self.control_point.center.x,
- -g_scene_size * self.control_point.center.y
+ -g_scene_size * self.control_point.center.y,
)
def set_active(self, is_active):
@@ -830,8 +808,6 @@ def set_active(self, is_active):
if scene.mouseGrabberItem() == self:
self.ungrabMouse()
-
-
def mouseReleaseEvent(self, evt):
"""Releases the mouse grab when the mouse is released.
@@ -850,8 +826,6 @@ def mouseMoveEvent(self, evt):
x = gremlin.util.clamp(evt.scenePos().x() / g_scene_size, -1.0, 1.0)
y = gremlin.util.clamp(-evt.scenePos().y() / g_scene_size, -1.0, 1.0)
-
-
# snap to grid if shift key is down
if self.parent:
center = self.parent.control_point.center
@@ -860,17 +834,17 @@ def mouseMoveEvent(self, evt):
if self.eh.get_control_state():
# coarse snap
if center:
- x,y = gremlin.util.snap_to_grid(x, y, 25, center.x, center.y)
+ x, y = gremlin.util.snap_to_grid(x, y, 25, center.x, center.y)
else:
- x,y = gremlin.util.snap_to_grid(x, y, 25)
+ x, y = gremlin.util.snap_to_grid(x, y, 25)
elif self.eh.get_shifted_state():
# fine snap
if center:
- x,y = gremlin.util.snap_to_grid(x, y, 50, center.x, center.y)
+ x, y = gremlin.util.snap_to_grid(x, y, 50, center.x, center.y)
else:
- x,y = gremlin.util.snap_to_grid(x, y, 50)
-
- new_point = Point2D(x,y)
+ x, y = gremlin.util.snap_to_grid(x, y, 50)
+
+ new_point = Point2D(x, y)
# Only allow movement along the y axis if the point is on either
# end of the area
@@ -881,10 +855,9 @@ def mouseMoveEvent(self, evt):
class CurveHandleGraphicsItem(QtWidgets.QGraphicsRectItem):
-
"""UI Item representing a handle of a control point."""
- def __init__(self, index : int, point : Point2D, parent : ControlPointGraphicsItem):
+ def __init__(self, index: int, point: Point2D, parent: ControlPointGraphicsItem):
"""Creates a new control point handle UI element.
:param index the id of the handle
@@ -894,13 +867,11 @@ def __init__(self, index : int, point : Point2D, parent : ControlPointGraphicsIt
super().__init__(-4, -4, 8, 8, parent)
self.setPos(point.x, point.y)
self.setBrush(QtGui.QBrush(QtCore.Qt.gray))
- self.parent : ControlPointGraphicsItem = parent
+ self.parent: ControlPointGraphicsItem = parent
self.index = index
self.line = QtWidgets.QGraphicsLineItem(point.x, point.y, 0, 0, parent)
self.line.setZValue(0)
self.setZValue(1)
-
-
self.eh = gremlin.event_handler.EventListener()
@@ -910,8 +881,8 @@ def redraw(self):
point = self.parent.control_point.handles[self.index]
delta = point - center
- self.setPos(delta.x*g_scene_size, -delta.y*g_scene_size)
- self.line.setLine(delta.x*g_scene_size, -delta.y*g_scene_size, 0, 0)
+ self.setPos(delta.x * g_scene_size, -delta.y * g_scene_size)
+ self.line.setLine(delta.x * g_scene_size, -delta.y * g_scene_size, 0, 0)
def set_active(self, is_active):
"""Handles changing the selected state of an item
@@ -946,7 +917,7 @@ def mouseMoveEvent(self, evt):
# Create desired point
x = gremlin.util.clamp(evt.scenePos().x() / g_scene_size, -1.0, 1.0)
y = gremlin.util.clamp(-evt.scenePos().y() / g_scene_size, -1.0, 1.0)
-
+
# snap to grid if shift key is down
if self.parent:
center = self.parent.control_point.center
@@ -955,27 +926,25 @@ def mouseMoveEvent(self, evt):
if self.eh.get_control_state():
# coarse snap
if center:
- x,y = gremlin.util.snap_to_grid(x, y, 25, center.x, center.y)
+ x, y = gremlin.util.snap_to_grid(x, y, 25, center.x, center.y)
else:
- x,y = gremlin.util.snap_to_grid(x, y, 25)
+ x, y = gremlin.util.snap_to_grid(x, y, 25)
elif self.eh.get_shifted_state():
# fine snap
if center:
- x,y = gremlin.util.snap_to_grid(x, y, 50, center.x, center.y)
+ x, y = gremlin.util.snap_to_grid(x, y, 50, center.x, center.y)
else:
- x,y = gremlin.util.snap_to_grid(x, y, 50)
-
-
- new_point = Point2D(x,y)
+ x, y = gremlin.util.snap_to_grid(x, y, 50)
+
+ new_point = Point2D(x, y)
self.parent.control_point.set_handle(self.index, new_point)
class CurveView(QtWidgets.QGraphicsScene):
-
"""Visualization of the entire curve editor UI element."""
- def __init__(self, curve_model, point_editor, show_input_axis = False, parent=None):
+ def __init__(self, curve_model, point_editor, show_input_axis=False, parent=None):
"""Creates a new instance.
:param curve_model the model to visualize
@@ -992,7 +961,7 @@ def __init__(self, curve_model, point_editor, show_input_axis = False, parent=No
self.model.content_added.connect(self._populate_from_model)
self.point_editor = point_editor
from gremlin.util import load_image
-
+
is_dark = gremlin.shared_state.is_dark_theme
curve_image = "dark_curve_grid_ex.svg" if is_dark else "curve_grid_ex.svg"
self.background_image = load_image(curve_image)
@@ -1005,8 +974,8 @@ def __init__(self, curve_model, point_editor, show_input_axis = False, parent=No
self.current_item = None
self.tracker = None
self.value = 0.0
- self.item_list = [] # list of items by index
- self.item_list_map = {} # map of points to items
+ self.item_list = [] # list of items by index
+ self.item_list_map = {} # map of points to items
self._populate_from_model()
eh = CurveEventHandler()
@@ -1024,14 +993,11 @@ def _model_changed(self):
self.redraw_scene()
eh.value_changed.emit(self.value)
-
-
def _dist(self, a, b):
- return ((b.x - a.x)**2 + (b.y - a.y)**2) ** 0.5
-
+ return ((b.x - a.x) ** 2 + (b.y - a.y) ** 2) ** 0.5
def _shortest_path(self, points):
- ''' sorts the points by the shortest distance '''
+ """sorts the points by the shortest distance"""
start = points[0]
pass_by = points
path = [start]
@@ -1048,14 +1014,11 @@ def _populate_from_model(self):
self.item_list = []
self.item_list_map = {}
for item in self.items():
- if type(item) in [
- ControlPointGraphicsItem,
- CurveHandleGraphicsItem
- ]:
+ if type(item) in [ControlPointGraphicsItem, CurveHandleGraphicsItem]:
self.removeItem(item)
points = [cp for cp in self.model.get_control_points()]
- points.sort(key = lambda p: (p.x, p.y)) # do a pre-sort by x value then y value
+ points.sort(key=lambda p: (p.x, p.y)) # do a pre-sort by x value then y value
points = self._shortest_path(points)
for cp in points:
@@ -1065,21 +1028,16 @@ def _populate_from_model(self):
self.item_list_map[cp] = item
if self.show_input_axis and self.tracker is None:
- self.tracker = DataPointGraphicsItem(0,0)
+ self.tracker = DataPointGraphicsItem(0, 0)
self.addItem(self.tracker)
self.redraw_scene()
-
def pointExists(self, point) -> bool:
for cp in self.model.get_control_points():
if point == cp:
return True
return False
-
-
-
-
def add_control_point(self, point, handles=()):
"""Adds a new control point to the model and scene.
@@ -1091,16 +1049,14 @@ def add_control_point(self, point, handles=()):
if self.pointExists(point):
# point already exists, ignore
return
-
+
cp = self.model.add_control_point(point, handles)
self._populate_from_model()
- item = self.item_list_map[cp]
+ item = self.item_list_map[cp]
self._select_item(item)
-
-
def _editor_update(self, value):
"""Callback for changes in the point editor UI.
@@ -1110,28 +1066,25 @@ def _editor_update(self, value):
if self.current_item is None:
return
new_point = Point2D(
- self.point_editor.x_input.value(),
- self.point_editor.y_input.value()
- )
+ self.point_editor.x_input.value(), self.point_editor.y_input.value()
+ )
if isinstance(self.current_item, CurveHandleGraphicsItem):
# move a handle
- item : CurveHandleGraphicsItem = self.current_item
+ item: CurveHandleGraphicsItem = self.current_item
item.parent.control_point.set_handle(item.index, new_point)
elif isinstance(self.current_item, ControlPointGraphicsItem):
if abs(self.current_item.control_point.center.x) == 1.0:
new_point.x = self.current_item.control_point.center.x
self.current_item.control_point.set_center(new_point)
self.model.save_to_profile()
-
@QtCore.Slot()
def _next_item(self, point_editor):
- ''' selects the next item '''
+ """selects the next item"""
if self.point_editor != point_editor:
return
item = self.current_item
if item and isinstance(item, CurveHandleGraphicsItem):
-
handle_list = item.parent.handles
index = item.index
index -= 1
@@ -1139,27 +1092,26 @@ def _next_item(self, point_editor):
return
index = 0
-
- if not self.current_item is None:
+
+ if self.current_item is not None:
count = len(self.item_list)
if self.current_item in self.item_list:
index = self.item_list.index(self.current_item)
index += 1
if index >= count:
index = 0
-
+
self._select_item(self.item_list[index])
@QtCore.Slot()
def _prev_item(self, point_editor):
- ''' selects the next item '''
+ """selects the next item"""
if self.point_editor != point_editor:
return
index = None
item = self.current_item
if item and isinstance(item, CurveHandleGraphicsItem):
-
handle_list = item.parent.handles
index = item.index
index += 1
@@ -1167,35 +1119,34 @@ def _prev_item(self, point_editor):
index = 0
self._select_item(handle_list[index])
return
-
+
count = len(self.item_list)
if count == 0:
return
-
+
if self.current_item is None:
- index = count - 1
+ index = count - 1
else:
if self.current_item in self.item_list:
index = self.item_list.index(self.current_item)
index -= 1
if index < 0:
- index = count-1
+ index = count - 1
if index is None:
index = 0
item = self.item_list[index]
- self._select_item(item)
-
+ self._select_item(item)
@QtCore.Slot(object)
- def _delete_point(self, point_editor):
+ def _delete_point(self, point_editor):
if self.point_editor != point_editor:
return
item = self.current_item
if item and isinstance(item, ControlPointGraphicsItem):
if self.current_item is None:
- return # nothin selected
-
+ return # nothin selected
+
count = len(self.item_list)
if count < 3:
# must have at least two points
@@ -1206,49 +1157,48 @@ def _delete_point(self, point_editor):
if abs(self.current_item.control_point.center.x) == 1.0:
ui_common.MessageBox(prompt="Enpoints cannot be removed.")
return
-
+
message_box = QtWidgets.QMessageBox()
message_box.setIcon(QtWidgets.QMessageBox.Icon.Warning)
message_box.setText("Delete this control point?")
- message_box.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok | QtWidgets.QMessageBox.StandardButton.Cancel)
+ message_box.setStandardButtons(
+ QtWidgets.QMessageBox.StandardButton.Ok
+ | QtWidgets.QMessageBox.StandardButton.Cancel
+ )
gremlin.util.centerDialog(message_box)
result = message_box.exec()
if result == QtWidgets.QMessageBox.StandardButton.Ok:
self._delete_point_confirmed()
@QtCore.Slot()
- def _delete_point_confirmed(self):
- ''' deletes the current control point '''
-
+ def _delete_point_confirmed(self):
+ """deletes the current control point"""
+
self.model.remove_control_point(self.current_item.control_point)
self._populate_from_model()
self.current_item = None
self._next_item(self.point_editor)
-
-
-
@QtCore.Slot()
def _handle_match_x(self, point_editor):
if self.point_editor != point_editor:
return
- ''' handles the x '''
+ """ handles the x """
item = self.current_item
if not item or not isinstance(item, CurveHandleGraphicsItem):
return
y = gremlin.util.clamp(item.y() / g_scene_size, -1.0, 1.0)
point = Point2D(item.parent.control_point.x, y)
-
+
# if point == item.parent.control_point.center:
# point = item.parent.control_point.center + Point2D(0.001,0.0)
item.parent.control_point.set_handle(item.index, point)
-
-
+
@QtCore.Slot()
def _handle_match_y(self, point_editor):
- ''' handles the y '''
+ """handles the y"""
if self.point_editor != point_editor:
return
@@ -1260,7 +1210,6 @@ def _handle_match_y(self, point_editor):
# if point == item.parent.control_point.center:
# point = item.parent.control_point.center + Point2D(0.0,0.001)
item.parent.control_point.set_handle(item.index, point)
-
def _select_item(self, item):
"""Handles drawing of an item being selected.
@@ -1268,8 +1217,9 @@ def _select_item(self, item):
"""
# Ensure we want / can select the provided item
- if isinstance(item, ControlPointGraphicsItem) or \
- isinstance(item, CurveHandleGraphicsItem):
+ if isinstance(item, ControlPointGraphicsItem) or isinstance(
+ item, CurveHandleGraphicsItem
+ ):
if self.current_item and item != self.current_item:
self.current_item.set_active(False)
self.current_item = item
@@ -1280,7 +1230,6 @@ def _select_item(self, item):
else:
index = -1
eh.selected_item.emit(self.point_editor, item, index)
-
def redraw_scene(self):
"""Updates the scene
@@ -1305,26 +1254,26 @@ def redraw_scene(self):
curve_fn = self.model.get_curve_function()
if curve_fn:
path = QtGui.QPainterPath(
- QtCore.QPointF(int(-g_scene_size),int(-g_scene_size*curve_fn(-1)))
+ QtCore.QPointF(int(-g_scene_size), int(-g_scene_size * curve_fn(-1)))
)
- for x in range(-int(g_scene_size), int(g_scene_size+1), 2):
+ for x in range(-int(g_scene_size), int(g_scene_size + 1), 2):
path.lineTo(x, -g_scene_size * curve_fn(x / g_scene_size))
- self.addPath(path, QtGui.QPen(QtGui.QColor(gremlin.ui.ui_common.Color.selectColor()), 4))
+ self.addPath(
+ path,
+ QtGui.QPen(QtGui.QColor(gremlin.ui.ui_common.Color.selectColor()), 4),
+ )
# update the tracking item
if self.show_input_axis:
x = self.value
y = -g_scene_size * curve_fn(x / g_scene_size)
- self.tracker.update(x,y)
- #self.tracker.redraw()
-
+ self.tracker.update(x, y)
+ # self.tracker.redraw()
# Update editor widget fields
if self.current_item:
if isinstance(self.current_item, ControlPointGraphicsItem):
- self.point_editor.set_values(
- self.current_item.control_point.center
- )
+ self.point_editor.set_values(self.current_item.control_point.center)
self._redraw = False
@@ -1346,10 +1295,12 @@ def mouseDoubleClickEvent(self, evt):
if evt.button() == QtCore.Qt.LeftButton:
item = self.itemAt(evt.scenePos(), QtGui.QTransform())
if not isinstance(item, ControlPointGraphicsItem):
- self.add_control_point(Point2D(
- evt.scenePos().x() / g_scene_size,
- evt.scenePos().y() / -g_scene_size
- ))
+ self.add_control_point(
+ Point2D(
+ evt.scenePos().x() / g_scene_size,
+ evt.scenePos().y() / -g_scene_size,
+ )
+ )
def keyPressEvent(self, evt):
"""Removes the currently selected control point if the Del
@@ -1379,11 +1330,12 @@ def drawBackground(self, painter, rect):
:param painter the painter object
:param rect the drawing rectangle
"""
- painter.drawImage(QtCore.QPoint(int(-g_scene_size), int(-g_scene_size)),self.background_image)
+ painter.drawImage(
+ QtCore.QPoint(int(-g_scene_size), int(-g_scene_size)), self.background_image
+ )
class ControlPointEditorWidget(QtWidgets.QWidget):
-
"""Widgets allowing the control point coordinates to be changed
via text fields."""
@@ -1396,13 +1348,13 @@ def __init__(self, parent=None):
# Generate controls
self.main_layout = QtWidgets.QHBoxLayout(self)
- self.main_layout.setContentsMargins(0,0,0,0)
+ self.main_layout.setContentsMargins(0, 0, 0, 0)
self.point_label = QtWidgets.QLabel("Control Point")
self.x_label = QtWidgets.QLabel("X")
self.y_label = QtWidgets.QLabel("Y")
self.message = QtWidgets.QLabel("")
- self.x_input = ui_common.QFloatLineEdit()
+ self.x_input = ui_common.QFloatLineEdit()
self.x_input.setRange(-1, 1)
self.x_input.setDecimals(3)
self.x_input.setSingleStep(0.1)
@@ -1467,11 +1419,10 @@ def __init__(self, parent=None):
self._selected_item_changed(self, None, 0)
-
@QtCore.Slot(object, object, int)
def _selected_item_changed(self, point_editor, item, index):
if self != point_editor:
- return # not ours
+ return # not ours
msg = ""
handle_visible = False
point = None
@@ -1487,69 +1438,64 @@ def _selected_item_changed(self, point_editor, item, index):
self.point_label.setText("Control Point")
msg = f"{[index+1]}"
point = item.control_point.center
-
else:
self.point_label.setText("???")
-
+
self.selected_label.setText(msg)
self.handle_match_x.setVisible(handle_visible)
self.handle_match_y.setVisible(handle_visible)
- # update the data
+ # update the data
if point is not None:
self.set_values(point)
-
-
-
@QtCore.Slot()
def _next_control_point(self):
- ''' switches to the enxt selected control point'''
+ """switches to the enxt selected control point"""
eh = CurveEventHandler()
eh.next_point.emit(self)
@QtCore.Slot()
def _prev_control_point(self):
- ''' switches to the enxt selected control point'''
+ """switches to the enxt selected control point"""
eh = CurveEventHandler()
eh.prev_point.emit(self)
@QtCore.Slot()
def _handle_match_x(self):
- ''' called when match x button called'''
+ """called when match x button called"""
eh = CurveEventHandler()
eh.handle_match_x.emit(self)
@QtCore.Slot()
def _handle_match_y(self):
- ''' called when match x button called'''
+ """called when match x button called"""
eh = CurveEventHandler()
- eh.handle_match_y.emit(self)
+ eh.handle_match_y.emit(self)
@QtCore.Slot(str)
def _update_message(self, message):
if message is None:
- self.message.setText("")
+ self.message.setText("")
else:
self.message.setText(message)
@QtCore.Slot()
def _delete_control_point(self):
- ''' deletes the current control point '''
-
+ """deletes the current control point"""
+
eh = CurveEventHandler()
eh.delete_point.emit(self)
@QtCore.Slot(str)
def _update_selected_message(self, message):
if message is None:
- self.selected_label.setText("")
+ self.selected_label.setText("")
else:
self.selected_label.setText(message)
-
def set_values(self, point):
"""Sets the values in the input fields to those of the provided point.
@@ -1561,19 +1507,20 @@ def set_values(self, point):
self.y_input.setValue(point.y)
self.active_point = point
+
class AxisCurveWidget(QtWidgets.QWidget):
- ''' response curve standalone widget '''
+ """response curve standalone widget"""
- def __init__(self, curve_data : AxisCurveData, parent=None):
+ def __init__(self, curve_data: AxisCurveData, parent=None):
"""Creates a new instance.
- :param curve_data: the curve configuration data
+ :param curve_data: the curve configuration data
:param parent: the parent widget
"""
super().__init__(parent=parent)
self.main_layout = QtWidgets.QVBoxLayout(self)
- self.action_data : AxisCurveData = curve_data
+ self.action_data: AxisCurveData = curve_data
self.is_inverted = False
self.last_value = 0
self.curve_model = None
@@ -1586,30 +1533,32 @@ def __init__(self, curve_data : AxisCurveData, parent=None):
@QtCore.Slot(float)
def update_value(self, value):
- ''' updates dot on the curve based on the value -1 to +1 '''
+ """updates dot on the curve based on the value -1 to +1"""
if value < -1 or value > 1:
syslog = logging.getLogger("system")
- syslog.warning(f"CurveInput: Error: value {value:0.3f} is our of range -1 to +1 - check input")
- value = gremlin.util.clamp(value,-1,1)
+ syslog.warning(
+ f"CurveInput: Error: value {value:0.3f} is our of range -1 to +1 - check input"
+ )
+ value = gremlin.util.clamp(value, -1, 1)
if self.action_data.show_input_axis:
-
-
- ''' draw the current value on the curve '''
+ """ draw the current value on the curve """
curve_fn = self.curve_model.get_curve_function()
if curve_fn:
# get the position of the marker
- curve_value = gremlin.joystick_handling.scale_to_range(value, target_min = -g_scene_size, target_max = g_scene_size) # value on the curve by pixel x
+ curve_value = gremlin.joystick_handling.scale_to_range(
+ value, target_min=-g_scene_size, target_max=g_scene_size
+ ) # value on the curve by pixel x
x = curve_value
y = -g_scene_size * curve_fn(x / g_scene_size)
- #print (f"value: {value} cv: {curve_value} x: {x} y: {y}")
+ # print (f"value: {value} cv: {curve_value} x: {x} y: {y}")
# tracker only exists when input repeater mode is enabled
- self.curve_scene.tracker.update(x,y)
+ self.curve_scene.tracker.update(x, y)
self.input_raw_widget.setText(f"{value:0.3f}")
- curved = gremlin.util.clamp(curve_fn(value),-1.0, +1.0)
+ curved = gremlin.util.clamp(curve_fn(value), -1.0, +1.0)
self.input_curved_widget.setText(f"{curved:0.3f}")
self.reapeater_widget.setValue(curved)
@@ -1617,21 +1566,19 @@ def update_value(self, value):
self.last_value = value
self.curve_scene.value = value
-
-
def _cleanup_ui(self):
- ''' cleanup operations '''
- self.action_data = None
-
+ """cleanup operations"""
+ self.action_data = None
def _create_ui(self):
"""Creates the required UI elements."""
-
self.container_options_widget = QtWidgets.QWidget()
- self.container_options_widget.setContentsMargins(0,0,0,0)
- self.container_options_layout = QtWidgets.QHBoxLayout(self.container_options_widget)
- self.container_options_layout.setContentsMargins(0,0,0,0)
+ self.container_options_widget.setContentsMargins(0, 0, 0, 0)
+ self.container_options_layout = QtWidgets.QHBoxLayout(
+ self.container_options_widget
+ )
+ self.container_options_layout.setContentsMargins(0, 0, 0, 0)
# Dropdown menu for the different curve types
self.curve_type_selection = gremlin.ui.ui_common.QComboBox()
@@ -1648,16 +1595,14 @@ def _create_ui(self):
help_button.setFlat(True)
help_button.setStyleSheet("QPushButton { background-color: transparent }")
help_button.setMaximumWidth(32)
-
- help_button.clicked.connect(self._show_help)
+
+ help_button.clicked.connect(self._show_help)
# Curve manipulation options
-
+
self.container_options_layout.addWidget(QtWidgets.QLabel("Curve Type:"))
self.container_options_layout.addWidget(self.curve_type_selection)
-
-
# Curve inversion
self.curve_inversion = QtWidgets.QPushButton("Invert")
self.curve_inversion.clicked.connect(self._invert_curve)
@@ -1665,26 +1610,29 @@ def _create_ui(self):
# Curve symmetry
self.curve_symmetry = QtWidgets.QCheckBox("Diagonal Symmetry")
- self.curve_symmetry.setChecked(self.action_data.symmetry_mode == SymmetryMode.Diagonal)
+ self.curve_symmetry.setChecked(
+ self.action_data.symmetry_mode == SymmetryMode.Diagonal
+ )
self.curve_symmetry.clicked.connect(self._curve_symmetry_cb)
self.container_options_layout.addWidget(self.curve_symmetry)
# Handle symmetry
self.handle_symmetry_widget = QtWidgets.QCheckBox("Force smooth curves")
-
+
if self.action_data.mapping_type == "cubic-bezier-spline":
- self.handle_symmetry_widget.setChecked(self.curve_model.handle_symmetry_enabled)
+ self.handle_symmetry_widget.setChecked(
+ self.curve_model.handle_symmetry_enabled
+ )
self.handle_symmetry_widget.stateChanged.connect(self._handle_symmetry_cb)
else:
self.handle_symmetry_widget.setVisible(False)
- self.container_options_layout.addWidget(self.handle_symmetry_widget)
+ self.container_options_layout.addWidget(self.handle_symmetry_widget)
self.centered_widget = QtWidgets.QCheckBox("Centered")
self.centered_widget.clicked.connect(self._centered_changed_cb)
-
- self.container_options_layout.addWidget(self.centered_widget)
+ self.container_options_layout.addWidget(self.centered_widget)
self.container_options_layout.addStretch()
self.copy_button_widget = QtWidgets.QPushButton()
@@ -1696,7 +1644,7 @@ def _create_ui(self):
self.copy_button_widget.setMaximumWidth(24)
self.copy_button_widget.setToolTip("Copy curve")
self.copy_button_widget.clicked.connect(self._copy_curve_cb)
-
+
self.paste_button_widget = QtWidgets.QPushButton()
paste_icon = "gfx/dark_button_paste.svg" if is_dark else "gfx/button_paste.svg"
@@ -1707,30 +1655,27 @@ def _create_ui(self):
self._update_clipboard()
self.paste_button_widget.clicked.connect(self._paste_curve_cb)
-
self.container_options_layout.addWidget(self.copy_button_widget)
self.container_options_layout.addWidget(self.paste_button_widget)
self.container_options_layout.addWidget(help_button)
-
self.container_presets_widget = QtWidgets.QWidget()
- self.container_presets_widget.setContentsMargins(0,0,0,0)
- self.container_presets_layout = QtWidgets.QHBoxLayout(self.container_presets_widget)
- self.container_presets_layout.setContentsMargins(0,0,0,0)
+ self.container_presets_widget.setContentsMargins(0, 0, 0, 0)
+ self.container_presets_layout = QtWidgets.QHBoxLayout(
+ self.container_presets_widget
+ )
+ self.container_presets_layout.setContentsMargins(0, 0, 0, 0)
self.container_presets_layout.addWidget(QtWidgets.QLabel("Presets:"))
-
-
self.preset_save_button_widget = QtWidgets.QPushButton("Save preset")
self.preset_save_button_widget.setToolTip("Saves a preset to a file")
self.preset_save_button_widget.clicked.connect(self._save_preset_cb)
self.preset_load_button_widget = QtWidgets.QPushButton("Load preset")
- self.preset_load_button_widget.setToolTip("Load preset from a previously saved preset")
+ self.preset_load_button_widget.setToolTip(
+ "Load preset from a previously saved preset"
+ )
self.preset_load_button_widget.clicked.connect(self._load_preset_cb)
-
-
-
self.container_presets_layout.addWidget(self.preset_save_button_widget)
self.container_presets_layout.addWidget(self.preset_load_button_widget)
@@ -1741,19 +1686,18 @@ def _create_ui(self):
self.container_presets_layout.addWidget(button)
self.container_presets_layout.addStretch()
-
-
self.container_control_widget = QtWidgets.QWidget()
- self.container_control_widget.setContentsMargins(0,0,0,0)
- self.container_control_layout = QtWidgets.QHBoxLayout(self.container_control_widget)
- self.container_control_layout.setContentsMargins(0,0,0,0)
+ self.container_control_widget.setContentsMargins(0, 0, 0, 0)
+ self.container_control_layout = QtWidgets.QHBoxLayout(
+ self.container_control_widget
+ )
+ self.container_control_layout.setContentsMargins(0, 0, 0, 0)
# Create all objects required for the response curve UI
self.control_point_editor = ControlPointEditorWidget()
-
- self.control_point_editor.setContentsMargins(0,0,0,0)
- self.container_control_layout.addWidget(self.control_point_editor)
+ self.control_point_editor.setContentsMargins(0, 0, 0, 0)
+ self.container_control_layout.addWidget(self.control_point_editor)
width = get_text_width("M") * 8
@@ -1771,42 +1715,39 @@ def _create_ui(self):
self.curve_model = CubicBezierSplineModel(self.action_data)
else:
raise gremlin.error.ProfileError("Invalid curve type")
-
-
+
# mode
self.curve_model.set_symmetry_mode(self.action_data.symmetry_mode)
self.container_curve_widget = QtWidgets.QFrame()
background_color = gremlin.ui.ui_common.Color.actionBackgroundColor()
- self.container_curve_widget.setStyleSheet(f'.QFrame{{background-color: {background_color}; border-radius: 10px;}}')
+ self.container_curve_widget.setStyleSheet(
+ f".QFrame{{background-color: {background_color}; border-radius: 10px;}}"
+ )
self.container_curve_layout = QtWidgets.QHBoxLayout(self.container_curve_widget)
# Graphical curve editor
self.curve_scene = CurveView(
self.curve_model,
self.control_point_editor,
- self.action_data.show_input_axis
+ self.action_data.show_input_axis,
)
-
-
# Create view displaying the curve scene
-
+
self.curve_view = QtWidgets.QGraphicsView(self.curve_scene)
self._configure_response_curve_view()
-
self.container_repeater_widget = QtWidgets.QWidget()
- self.container_repeater_widget.setContentsMargins(0,0,0,0)
- self.container_repeater_layout = QtWidgets.QHBoxLayout(self.container_repeater_widget)
- self.container_repeater_layout.setContentsMargins(0,0,0,0)
- self.reapeater_widget = ui_common.AxisStateWidget(orientation=QtCore.Qt.Orientation.Horizontal)
-
-
-
-
-
+ self.container_repeater_widget.setContentsMargins(0, 0, 0, 0)
+ self.container_repeater_layout = QtWidgets.QHBoxLayout(
+ self.container_repeater_widget
+ )
+ self.container_repeater_layout.setContentsMargins(0, 0, 0, 0)
+ self.reapeater_widget = ui_common.AxisStateWidget(
+ orientation=QtCore.Qt.Orientation.Horizontal
+ )
self.container_repeater_layout.addWidget(QtWidgets.QLabel("Input:"))
self.container_repeater_layout.addWidget(self.input_raw_widget)
@@ -1815,7 +1756,6 @@ def _create_ui(self):
self.container_repeater_layout.addWidget(self.reapeater_widget)
self.container_repeater_layout.addStretch()
-
self.deadzone_widget = DeadzoneWidget(self.action_data)
self.deadzone_widget.changed.connect(self._deadzone_modified_cb)
@@ -1838,7 +1778,7 @@ def _update_ui(self):
with QtCore.QSignalBlocker(self.curve_type_selection):
self.curve_type_selection.setCurrentIndex(index)
-
+
self.curve_scene.redraw_scene()
with QtCore.QSignalBlocker(self.centered_widget):
@@ -1855,142 +1795,136 @@ def _centered_changed_cb(self, checked):
@QtCore.Slot()
def _show_help(self):
-
-
-
dialog = ui_common.MarkdownDialog("Axis Response Curve Instructions")
w = 600
h = 400
geom = self.geometry()
dialog.setGeometry(
- int(geom.x() + geom.width() / 2 - w/2),
- int(geom.y() + geom.height() / 2 - h/2),
+ int(geom.x() + geom.width() / 2 - w / 2),
+ int(geom.y() + geom.height() / 2 - h / 2),
w,
- h
+ h,
)
if dialog.load("curve_handler_instructions.md"):
- gremlin.util.centerDialog(dialog,w,h)
+ gremlin.util.centerDialog(dialog, w, h)
dialog.exec()
return
else:
- ui_common.MessageBox(prompt ="Unable to locate help file")
-
-
-
+ ui_common.MessageBox(prompt="Unable to locate help file")
@QtCore.Slot()
def _save_preset_cb(self):
- ''' save the current curve information to a preset '''
+ """save the current curve information to a preset"""
xml_source, _ = QtWidgets.QFileDialog.getSaveFileName(
- None,
- "Save Preset",
- gremlin.util.userprofile_path(),
- "XML files (*.xml)"
+ None, "Save Preset", gremlin.util.userprofile_path(), "XML files (*.xml)"
)
if xml_source != "":
try:
-
if os.path.isfile(xml_source):
# blitz it
os.unlink(xml_source)
root = etree.Element("curve_preset")
- #self.curve_model.save_to_profile() # sync the coords
+ # self.curve_model.save_to_profile() # sync the coords
node = self.action_data._generate_xml()
root.append(node)
tree = etree.ElementTree(root)
- tree.write(xml_source, pretty_print=True,xml_declaration=True,encoding="utf-8")
+ tree.write(
+ xml_source,
+ pretty_print=True,
+ xml_declaration=True,
+ encoding="utf-8",
+ )
base_name = os.path.basename(xml_source)
- gremlin.ui.ui_common.MessageBox(prompt = f"Preset saved to {base_name}", is_warning=False)
+ gremlin.ui.ui_common.MessageBox(
+ prompt=f"Preset saved to {base_name}", is_warning=False
+ )
except Exception as err:
- gremlin.ui.ui_common.MessageBox(prompt = f"Error saving preset: {err}")
-
+ gremlin.ui.ui_common.MessageBox(prompt=f"Error saving preset: {err}")
@QtCore.Slot()
def _load_preset_cb(self):
- ''' save the current curve information to a preset '''
+ """save the current curve information to a preset"""
xml_source, _ = QtWidgets.QFileDialog.getOpenFileName(
- None,
- "Load Preset",
- gremlin.util.userprofile_path(),
- "XML files (*.xml)"
+ None, "Load Preset", gremlin.util.userprofile_path(), "XML files (*.xml)"
)
if xml_source != "":
try:
base_name = os.path.basename(xml_source)
parser = etree.XMLParser(remove_blank_text=True)
- tree = etree.parse(xml_source, parser)
+ tree = etree.parse(xml_source, parser)
root = tree.getroot()
if root is None or root.tag != "curve_preset":
- gremlin.ui.ui_common.MessageBox(prompt = f"File {base_name} does not appear to be a valid preset file.")
+ gremlin.ui.ui_common.MessageBox(
+ prompt=f"File {base_name} does not appear to be a valid preset file."
+ )
return
- node = gremlin.util.get_xml_child(root,"curve-data")
+ node = gremlin.util.get_xml_child(root, "curve-data")
if node is None:
- gremlin.ui.ui_common.MessageBox(prompt = f"File {base_name} does not appear to be a valid preset file.")
+ gremlin.ui.ui_common.MessageBox(
+ prompt=f"File {base_name} does not appear to be a valid preset file."
+ )
return
-
+
self.action_data._parse_xml(node)
- self._change_curve_type(self.action_data.mapping_type, self.action_data.control_points)
+ self._change_curve_type(
+ self.action_data.mapping_type, self.action_data.control_points
+ )
self.action_data.curve_update()
self._update_ui()
self.update_value(self.last_value)
except Exception as err:
- gremlin.ui.ui_common.MessageBox(prompt = f"Error loading preset: {err}")
+ gremlin.ui.ui_common.MessageBox(prompt=f"Error loading preset: {err}")
def _clipboard_valid(self, clipboard) -> bool:
- ''' true if the clipboard data is valid '''
+ """true if the clipboard data is valid"""
data = clipboard.data
if gremlin.util.is_binary_string(data):
data = data.decode("utf-8")
return isinstance(data, str) and "" in data
-
@QtCore.Slot()
def _copy_curve_cb(self):
- ''' copies current curve data to the clipboard '''
+ """copies current curve data to the clipboard"""
node = self.action_data._generate_xml()
xml = etree.tostring(node)
clipboard = gremlin.clipboard.Clipboard()
clipboard.data = xml
self._update_clipboard()
-
@QtCore.Slot()
def _paste_curve_cb(self):
- ''' paste curve data from clipboard '''
+ """paste curve data from clipboard"""
clipboard = gremlin.clipboard.Clipboard()
if self._clipboard_valid(clipboard):
try:
xml = clipboard.data
node = etree.fromstring(xml)
self.action_data._parse_xml(node)
- self._change_curve_type(self.action_data.mapping_type, self.action_data.control_points)
+ self._change_curve_type(
+ self.action_data.mapping_type, self.action_data.control_points
+ )
self.action_data.curve_update()
self._update_ui()
self.update_value(self.last_value)
except:
# invalid
return
-
-
- def _update_clipboard(self, clipboard = None):
- ''' updates the state of the clipboard buttons '''
+ def _update_clipboard(self, clipboard=None):
+ """updates the state of the clipboard buttons"""
if clipboard is None:
clipboard = gremlin.clipboard.Clipboard()
self.paste_button_widget.setEnabled(self._clipboard_valid(clipboard))
-
@QtCore.Slot(int)
def _curve_type_changed(self):
curve_type = self.curve_type_selection.currentData()
self._change_curve_type(curve_type)
-
-
- def _change_curve_type(self, curve_type : CurveType, control_points = None):
+ def _change_curve_type(self, curve_type: CurveType, control_points=None):
"""Changes the type of curve used.
:param curve_type the name of the new curve type
@@ -2001,14 +1935,19 @@ def _change_curve_type(self, curve_type : CurveType, control_points = None):
if curve_type == CurveType.Cubic:
self.action_data.control_points = [(-1.0, -1.0), (1.0, 1.0)]
elif curve_type == CurveType.Bezier:
- self.action_data.control_points = [(-1.0, -1.0), (-1.0, 0),
- (-0.08, 0.0), (0.0, 0.0), (0.08, 0.0),
- (1.0, 0.0), (1.0, 1.0),
- ]
-
+ self.action_data.control_points = [
+ (-1.0, -1.0),
+ (-1.0, 0),
+ (-0.08, 0.0),
+ (0.0, 0.0),
+ (0.08, 0.0),
+ (1.0, 0.0),
+ (1.0, 1.0),
+ ]
+
else:
self.action_data.control_points = control_points
-
+
self.action_data.mapping_type = curve_type
self.curve_model = AxisCurveData.model_map[curve_type](self.action_data)
@@ -2017,17 +1956,15 @@ def _change_curve_type(self, curve_type : CurveType, control_points = None):
self.handle_symmetry_widget.setVisible(False)
elif self.action_data.mapping_type == CurveType.Bezier:
self.handle_symmetry_widget.setVisible(True)
- self.handle_symmetry_widget.stateChanged.connect(
- self._handle_symmetry_cb
- )
-
+ self.handle_symmetry_widget.stateChanged.connect(self._handle_symmetry_cb)
+
self.curve_symmetry.setChecked(False)
# Recreate the UI components
self.curve_scene = CurveView(
self.curve_model,
self.control_point_editor,
- self.action_data.show_input_axis
+ self.action_data.show_input_axis,
)
self.curve_view = QtWidgets.QGraphicsView(self.curve_scene)
self._configure_response_curve_view()
@@ -2049,7 +1986,7 @@ def _curve_symmetry_cb(self, checked):
@QtCore.Slot()
def _curve_set_preset_cb(self):
- ''' sets the curve points to max bezier '''
+ """sets the curve points to max bezier"""
# point_first handle_first (x must be -1)
# ...
@@ -2060,38 +1997,58 @@ def _curve_set_preset_cb(self):
# handle_last point_last (x must be + 1)
widget = self.sender()
- preset : CurvePreset = widget.data
+ preset: CurvePreset = widget.data
curve_type = CurveType.Bezier
- is_centered = True # assume centered
+ is_centered = True # assume centered
match preset:
case CurvePreset.Bezier1:
- # max 10%
- control_points = [(-1.0, -1.0), (-1.0, 0),
- (-0.1, 0.0), (0.0, 0.0), (0.1, 0.0),
- (1.0, 0.0), (1.0, 1.0),
- ]
+ # max 10%
+ control_points = [
+ (-1.0, -1.0),
+ (-1.0, 0),
+ (-0.1, 0.0),
+ (0.0, 0.0),
+ (0.1, 0.0),
+ (1.0, 0.0),
+ (1.0, 1.0),
+ ]
case CurvePreset.Bezier2:
- # max 20%
- control_points = [(-1.0, -1.0), (-1.0, 0),
- (-0.2, 0.0), (0.0, 0.0), (0.2, 0.0),
- (1.0, 0.0), (1.0, 1.0),
- ]
+ # max 20%
+ control_points = [
+ (-1.0, -1.0),
+ (-1.0, 0),
+ (-0.2, 0.0),
+ (0.0, 0.0),
+ (0.2, 0.0),
+ (1.0, 0.0),
+ (1.0, 1.0),
+ ]
case CurvePreset.Bezier3:
# 5% start 50%
- control_points = [(-1.0, -1.0), (-0.5, 0),
- (-0.05, 0.0), (0.0, 0.0), (0.05, 0.0),
- (0.5, 0.0), (1.0, 1.0),
- ]
+ control_points = [
+ (-1.0, -1.0),
+ (-0.5, 0),
+ (-0.05, 0.0),
+ (0.0, 0.0),
+ (0.05, 0.0),
+ (0.5, 0.0),
+ (1.0, 1.0),
+ ]
case CurvePreset.Bezier4:
- # 10% start 50%
- control_points = [(-1.0, -1.0), (-0.5, 0),
- (-0.1, 0.0), (0.0, 0.0), (0.1, 0.0),
- (0.5, 0.0), (1.0, 1.0),
- ]
+ # 10% start 50%
+ control_points = [
+ (-1.0, -1.0),
+ (-0.5, 0),
+ (-0.1, 0.0),
+ (0.0, 0.0),
+ (0.1, 0.0),
+ (0.5, 0.0),
+ (1.0, 1.0),
+ ]
case CurvePreset.Reset:
# reset to cubic linear
curve_type = CurveType.Cubic
- control_points = [(-1.0, -1.0), (1.0, 1.0)]
+ control_points = [(-1.0, -1.0), (1.0, 1.0)]
is_centered = False
case _:
@@ -2105,18 +2062,15 @@ def _curve_set_preset_cb(self):
self._update_ui()
self.update_value(self.last_value)
- @QtCore.Slot()
+ @QtCore.Slot()
def _deadzone_preset_cb(self):
- ''' handles deadzone presets '''
+ """handles deadzone presets"""
widget = self.sender()
- preset : DeadzonePreset = widget.data
-
-
-
+ preset: DeadzonePreset = widget.data
dd = self.deadzone_widget
d_start, d_left, d_right, d_end = dd.values()
-
+
if d_start is None:
d_start = -1
if d_end is None:
@@ -2125,60 +2079,54 @@ def _deadzone_preset_cb(self):
d_left = 0
if d_right is None:
d_right = 0
-
+
match preset:
- case DeadzonePreset.center_zero :
+ case DeadzonePreset.center_zero:
d_left = 0.0
d_right = 0.0
- case DeadzonePreset.center_two :
+ case DeadzonePreset.center_two:
d_left = -0.02 * 2
d_right = 0.02 * 2
- case DeadzonePreset.center_five :
+ case DeadzonePreset.center_five:
d_left = -0.05 * 2
d_right = 0.05 * 2
- case DeadzonePreset.center_ten :
+ case DeadzonePreset.center_ten:
d_left = -0.1 * 2
d_right = 0.1 * 2
- case DeadzonePreset.end_two :
+ case DeadzonePreset.end_two:
d_start = -1 + 0.02 * 2
d_end = 1 - 0.02 * 2
- case DeadzonePreset.end_five :
+ case DeadzonePreset.end_five:
d_start = -1 + 0.05 * 2
d_end = 1 - 0.05 * 2
- case DeadzonePreset.end_ten :
+ case DeadzonePreset.end_ten:
d_start = -1 + 0.1 * 2
d_end = 1 - 0.1 * 2
- case DeadzonePreset.reset :
+ case DeadzonePreset.reset:
d_start = -1
d_left = 0
d_right = 0
d_end = 1
-
-
dd._update_deadzone([d_start, d_left, d_right, d_end])
- #dd.set_values([d_start, d_left, d_right, d_end])
-
+ # dd.set_values([d_start, d_left, d_right, d_end])
- @QtCore.Slot()
+ @QtCore.Slot()
def _deadzone_modified_cb(self):
- ''' called when deadzones are modified '''
+ """called when deadzones are modified"""
self.action_data.curve_update()
values = self.deadzone_widget.values()
- #print (f"deadzone: {values}")
+ # print (f"deadzone: {values}")
for index, value in enumerate(values):
self.action_data.deadzone[index] = value
self._update_ui()
self.update_value(self.last_value)
-
def _handle_symmetry_cb(self, state):
if not isinstance(self.curve_model, CubicBezierSplineModel):
- syslog.error(
- "Handle symmetry callback in non bezier curve attempted."
- )
+ syslog.error("Handle symmetry callback in non bezier curve attempted.")
return
self.curve_model.set_handle_symmetry(state == QtCore.Qt.Checked.value)
@@ -2188,12 +2136,11 @@ def _configure_response_curve_view(self):
self.curve_view = QtWidgets.QGraphicsView(self.curve_scene)
self.curve_view.setFixedSize(QtCore.QSize(510, 510))
self.curve_view.setFrameShape(QtWidgets.QFrame.NoFrame)
- self.curve_view.setSceneRect(QtCore.QRectF(
- -g_scene_size,
- -g_scene_size,
- 2*g_scene_size,
- 2*g_scene_size
- ))
+ self.curve_view.setSceneRect(
+ QtCore.QRectF(
+ -g_scene_size, -g_scene_size, 2 * g_scene_size, 2 * g_scene_size
+ )
+ )
gremlin.ui.ui_common.clear_layout(self.container_curve_layout)
self.container_curve_layout.addStretch()
self.container_curve_layout.addWidget(self.curve_view)
@@ -2203,14 +2150,14 @@ def _invert_curve(self):
self.curve_model.invert()
-class AxisCurveData():
- ''' holds the data for a curved axis '''
+class AxisCurveData:
+ """holds the data for a curved axis"""
# map of curve types to curve models
model_map = {
- CurveType.Cubic : CubicSplineModel,
- CurveType.Bezier : CubicBezierSplineModel
- }
+ CurveType.Cubic: CubicSplineModel,
+ CurveType.Bezier: CubicBezierSplineModel,
+ }
def __init__(self):
self.deadzone = [-1, 0, 0, 1]
@@ -2221,7 +2168,7 @@ def __init__(self):
self.show_input_axis = gremlin.config.Configuration().show_input_axis
self.deadzone_fn = None
self.response_fn = None
- self.isCentered = False
+ self.isCentered = False
el = gremlin.event_handler.EventListener()
el.profile_start.connect(self.profile_start)
@@ -2229,17 +2176,18 @@ def __init__(self):
@property
def mapping_type(self) -> CurveType:
return self._mapping_type
+
@mapping_type.setter
- def mapping_type(self, value : CurveType):
+ def mapping_type(self, value: CurveType):
self._mapping_type = value
@QtCore.Slot()
def profile_start(self):
- ''' called on profile start '''
+ """called on profile start"""
# setup the curve function for the output
- self.curve_update()
+ self.curve_update()
- def _parse_xml(self, node, data = None):
+ def _parse_xml(self, node, data=None):
"""Parses the XML corresponding to a response curve.
:param node the XML node to parse
@@ -2253,7 +2201,7 @@ def _parse_xml(self, node, data = None):
self.symmetry_mode = SymmetryMode.to_enum(mode)
if "centered" in node.attrib:
- self.isCentered = safe_read(node,"centered", bool, False)
+ self.isCentered = safe_read(node, "centered", bool, False)
self.control_points = []
for child in node:
@@ -2262,22 +2210,19 @@ def _parse_xml(self, node, data = None):
float(child.get("low")),
float(child.get("center-low")),
float(child.get("center-high")),
- float(child.get("high"))
+ float(child.get("high")),
]
elif child.tag == "mapping":
curve_type = child.get("type")
self.mapping_type = CurveType.to_enum(curve_type)
self.control_points = []
for point in child.iter("control-point"):
- self.control_points.append((
- float(point.get("x")),
- float(point.get("y"))
- ))
-
+ self.control_points.append(
+ (float(point.get("x")), float(point.get("y")))
+ )
self.curve_update()
-
def _generate_xml(self):
"""Generates a XML node corresponding to this object.
@@ -2302,18 +2247,22 @@ def _generate_xml(self):
# Deadzone settings
deadzone_node = ElementTree.Element("deadzone")
- # provide suitable defaults
+ # provide suitable defaults
if self.deadzone:
if len(self.deadzone) == 2:
# enpoints only
- v1, v4 = self.deadzone
+ v1, v4 = self.deadzone
v2 = v3 = 0
- else: # 4 members
+ else: # 4 members
v1, v2, v3, v4 = self.deadzone
- if v1 is None: v1 = -1
- if v2 is None: v2 = 0
- if v3 is None: v3 = 0
- if v4 is None: v4 = 1
+ if v1 is None:
+ v1 = -1
+ if v2 is None:
+ v2 = 0
+ if v3 is None:
+ v3 = 0
+ if v4 is None:
+ v4 = 1
else:
v1 = -1
v2 = 0
@@ -2328,44 +2277,41 @@ def _generate_xml(self):
return node
-
def curve_update(self):
- ''' updates the curve params '''
+ """updates the curve params"""
self.deadzone_fn = lambda value: gremlin.input_devices.deadzone(
value,
self.deadzone[0],
self.deadzone[1],
self.deadzone[2],
- self.deadzone[3]
+ self.deadzone[3],
)
if self.mapping_type == CurveType.Cubic:
self.response_fn = gremlin.spline.CubicSpline(self.control_points)
elif self.mapping_type == CurveType.Bezier:
- self.response_fn = \
- gremlin.spline.CubicBezierSpline(self.control_points)
+ self.response_fn = gremlin.spline.CubicBezierSpline(self.control_points)
else:
raise gremlin.error.GremlinError("Invalid curve type")
- def curve_value(self, value : float, update : bool = False):
- ''' processes an input value -1 to +1 and outputs the curved value based on the current curve model '''
+ def curve_value(self, value: float, update: bool = False):
+ """processes an input value -1 to +1 and outputs the curved value based on the current curve model"""
if update or self.deadzone_fn is None or self.response_fn is None:
self.curve_update()
if self.deadzone_fn is not None:
value = self.deadzone_fn(value)
if self.response_fn is not None:
value = self.response_fn(value)
-
+
return value
-
class AxisCurveDialog(gremlin.ui.ui_common.QRememberDialog):
- ''' dialog box for curve configuration '''
+ """dialog box for curve configuration"""
def __init__(self, curve_data, parent=None):
"""Creates a new instance.
- :param curve_data: the curve configuration data
+ :param curve_data: the curve configuration data
:param parent: the parent widget
"""
super().__init__(self.__class__.__name__, parent=parent)
@@ -2376,8 +2322,7 @@ def __init__(self, curve_data, parent=None):
self.scroll_widget = QtWidgets.QWidget()
self.scroll_layout = QtWidgets.QVBoxLayout(self.scroll_widget)
self.scroll_widget.setSizePolicy(
- QtWidgets.QSizePolicy.Expanding,
- QtWidgets.QSizePolicy.Expanding
+ QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
)
self.scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
self.scroll_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
@@ -2397,20 +2342,13 @@ def __init__(self, curve_data, parent=None):
@property
def curve_update_handler(self):
return self.widget.update_value
-
def keyPressEvent(self, event):
- ''' disable escape key to prevent conflict with handle deselect'''
+ """disable escape key to prevent conflict with handle deselect"""
if event.key() == QtCore.Qt.Key_Escape:
pass
else:
super().keyPressEvent(event)
-
def closeEvent(self, arg__1):
return super().closeEvent(arg__1)
-
-
-
-
-
diff --git a/gremlin/error.py b/gremlin/error.py
index e71f1f63..c09329bf 100644
--- a/gremlin/error.py
+++ b/gremlin/error.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,11 +17,11 @@
import logging
+
syslog = logging.getLogger("system")
class GremlinError(Exception):
-
"""Generic exception raised for gremlin related errors.
This class also functions as the base class for all other
@@ -37,7 +37,6 @@ def __str__(self):
class ProfileError(GremlinError):
-
"""Exception raised when an error occurs with a profile related
operation.
"""
@@ -47,7 +46,6 @@ def __init__(self, value):
class KeyboardError(GremlinError):
-
"""Exception raised when an error occurs related to keyboard inputs."""
def __init__(self, value):
@@ -55,7 +53,6 @@ def __init__(self, value):
class MouseError(GremlinError):
-
"""Exception raised when an error occurs related to mouse inputs."""
def __init__(self, value):
@@ -63,7 +60,6 @@ def __init__(self, value):
class MissingImplementationError(GremlinError):
-
"""Exception raised when a method is not implemented."""
def __init__(self, value):
@@ -71,7 +67,6 @@ def __init__(self, value):
class VJoyError(GremlinError):
-
"""Exception raised when an error occurs within the vJoy module."""
def __init__(self, value):
@@ -79,7 +74,6 @@ def __init__(self, value):
class HidGuardianError(GremlinError):
-
"""Exception raised when an error related to HidGuardian."""
def __init__(self, value):
@@ -87,8 +81,7 @@ def __init__(self, value):
class PluginError(GremlinError):
-
"""Exception raised when an error occurs withing a user plugin."""
def __init__(self, value):
- super().__init__(value)
\ No newline at end of file
+ super().__init__(value)
diff --git a/gremlin/event_handler.py b/gremlin/event_handler.py
index 263cf8dd..df4a509f 100644
--- a/gremlin/event_handler.py
+++ b/gremlin/event_handler.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,12 +21,7 @@
import time
import queue
import threading
-import anytree
from threading import Thread, Timer
-from typing import Callable
-
-
-
import gremlin.base_classes
@@ -54,2130 +49,2255 @@
class Event:
+ """Represents a single event captured by the system.
+
+ An event can originate from the keyboard or joystick which is
+ indicated by the EventType value. The value of the event has to
+ be interpreted based on the type of the event.
+
+ Keyboard and JoystickButton events have a simple True / False
+ value stored in is_pressed indicating whether or not the key has
+ been pressed. For JoystickAxis the value indicates the axis value
+ in the range [-1, 1] stored in the value field. JoystickHat events
+ represent the hat position as a unit tuple (x, y) representing
+ deflection in cartesian coordinates in the value field.
+
+ The extended field is used for Keyboard events only to indicate
+ whether or not the key's scan code is extended one.
+ """
+
+ def __init__(
+ self,
+ event_type,
+ identifier,
+ device_guid,
+ value=None, # normal calibrated value that comes in
+ virtual_code=0,
+ is_pressed=False,
+ raw_value=None, # raw value that comes in from dinput
+ curved_value=None, # value if curved
+ force_remote=False,
+ action_id=None,
+ data=None,
+ is_axis=False, # true if the input should be considered an axis (variable) input
+ is_virtual=False, # true if the input is a virtual input (vjoy),
+ mode=None, # mode to fire the event on - leave null for current mode,
+ override_input_type=None,
+ ):
+ """Creates a new Event object.
+
+ :param event_type the type of the event, one of the EventType
+ values
+ :param identifier the identifier of the event source
+ :param device_guid Device GUID identifying the device causing this event
+ :param value the value of a joystick axis or hat
+ :param is_pressed boolean flag indicating if a button or key
+ :param raw_value the raw SDL value of the axis
+ :param force_remote flag that indicates if the action should be executed on the remote only
+ :param action_id the ID of the action to execute or that generated the event
+ is pressed
+ """
+ self._id = gremlin.util.get_guid() # unique ID for this event
+ self.event_type = event_type
+ self._identifier = identifier
+ self.device_guid = device_guid
+ # self._is_pressed = is_pressed
+ self.is_pressed = is_pressed
+ self.value = value
+ self.raw_value = raw_value
+ self.curve_value = curved_value
+ self.force_remote = force_remote
+ self.action_id = action_id # the current action id to load
+ self.data = data # extra data passed along with the event
+ self.is_axis = is_axis
+ self.virtual_code = virtual_code # vk if a keyboard event (the identifier will be the key_id (scancode, extended))
+ self.is_virtual = is_virtual # true if the item is a vjoy device input
+ self.is_virtual_button = False # true if a virtual button
+ self.is_custom = False # true if a custom event (should be processed)
+ self.mode = mode # mode to act on, should be null for default
+ self.is_repeater = False # True if the event is a repeater generated event
+ self.override_input_type = override_input_type # override input type - used as the input type for actions
+
+ def clone(self):
+ """Returns a clone of the event.
+
+ :return cloned copy of this event
+ """
+ import copy
+
+ if not isinstance(self.identifier, int):
+ self.identifier = gremlin.base_classes.PickleTarget(self.identifier)
+ dup = copy.deepcopy(self)
+ dup._id = gremlin.util.get_guid() # unique ID for this event
+ return dup
+
+ @property
+ def device_id(self) -> str:
+ """id as a string"""
+ return str(self.device_guid)
+
+ @property
+ def identifier(self):
+ if isinstance(self._identifier, gremlin.base_classes.PickleTarget):
+ self._identifier = self._identifier.item
+ return self._identifier
+
+ @identifier.setter
+ def identifier(self, value):
+ self._identifier = value
+
+ def getInputType(self):
+ if self.override_input_type:
+ return self.override_input_type
+ return self.event_type
+
+ # @property
+ # def is_pressed(self):
+ # return self._is_pressed
+
+ # @is_pressed.setter
+ # def is_pressed(self, value):
+ # if value:
+ # pass
+ # self._is_pressed = value
+
+ def fake_button(self, is_pressed=True, clone=False):
+ """converts the event to a fake button"""
+ e = self.clone() if clone else self
+ e.event_type = InputType.JoystickButton
+ e.identifier = 1
+ e.is_axis = False # range exit is a button type event
+ e.value = is_pressed
+ e.is_pressed = is_pressed
+ return e
+
+ def __eq__(self, other):
+ return self.__hash__() == other.__hash__()
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ @property
+ def callbackKey(self):
+ """unique key to use to identify the specific callback"""
+ if self.event_type == InputType.Keyboard:
+ data = (
+ (self.identifier.scan_code, self.identifier.is_extended)
+ if isinstance(self.identifier, gremlin.keyboard.Key)
+ else self.identifier
+ )
+ return (self.device_guid, self.event_type.value, data, 1 if data[1] else 0)
+ else:
+ return (self.device_guid, self.event_type.value, self.identifier, 0)
+
+ def __hash__(self):
+ """Computes the hash value of this event.
+ new in m58: use the unique ID of this event to uniquely identify it
+
+ :return integer hash value of this event
+ """
+
+ return hash(self._id)
+
+ @property
+ def hardwareKey(self):
+ """unique key for the input"""
+ return (self.device_guid, self.event_type, self.identifier)
+
+ @staticmethod
+ def from_key(key):
+ """Creates an event object corresponding to the provided key.
+
+ :param key the Key object from which to create the Event
+ :return Event object corresponding to the provided key
+ """
+ if hasattr(key, "scan_code") and hasattr(key, "is_extended"):
+ return Event(
+ event_type=InputType.Keyboard,
+ identifier=(key.scan_code, key.is_extended),
+ virtual_code=key.virtual_code,
+ device_guid=dinput.GUID_Keyboard,
+ )
+
+ raise ValueError(f"Unable to handle parameter - not a valid key: {key}")
+
+ def __str__(self):
+ if self.event_type == InputType.Mouse:
+ return f"Event: Mouse - button {self.identifier} pressed: {self.is_pressed}"
+ elif self.event_type in (InputType.Keyboard, InputType.KeyboardLatched):
+ return f"Event: Keyboard - scan code, extended : {self.identifier} vk: {self.virtual_code} (0x{self.virtual_code:X}) pressed: {self.is_pressed}"
+ elif self.event_type == InputType.JoystickAxis or self.is_axis:
+ return f"Event: Axis : {self.identifier} raw value: {self.raw_value} value: {self.value}"
+ elif self.event_type == InputType.JoystickButton:
+ return f"Event: Button : {self.identifier} pressed: {self.is_pressed} value: {self.value}"
+ elif self.event_type == InputType.ModeControl:
+ return f"Event: Mode Control : {self.identifier} pressed: {self.is_pressed} value: {self.value} mode: {self.mode}"
+ elif self.event_type == InputType.JoystickHat:
+ return f"Event: Hat : {self.identifier} pressed: {self.is_pressed} value: {self.value}"
+ elif self.event_type == InputType.Midi:
+ return f"Event: Midi : {self.identifier} value: {self.value}"
+ elif self.event_type == InputType.OpenSoundControl:
+ return f"Event: OSC : {self.identifier} value: {self.value}"
+
+ return f"Event: {self.event_type} identifier {self.identifier}"
- """Represents a single event captured by the system.
-
- An event can originate from the keyboard or joystick which is
- indicated by the EventType value. The value of the event has to
- be interpreted based on the type of the event.
-
- Keyboard and JoystickButton events have a simple True / False
- value stored in is_pressed indicating whether or not the key has
- been pressed. For JoystickAxis the value indicates the axis value
- in the range [-1, 1] stored in the value field. JoystickHat events
- represent the hat position as a unit tuple (x, y) representing
- deflection in cartesian coordinates in the value field.
-
- The extended field is used for Keyboard events only to indicate
- whether or not the key's scan code is extended one.
- """
-
- def __init__(
- self,
- event_type,
- identifier,
- device_guid,
- value=None, # normal calibrated value that comes in
- virtual_code = 0,
- is_pressed=False,
- raw_value=None, # raw value that comes in from dinput
- curved_value = None, # value if curved
- force_remote = False,
- action_id = None,
- data = None,
- is_axis = False, # true if the input should be considered an axis (variable) input
- is_virtual = False, # true if the input is a virtual input (vjoy),
- mode = None, # mode to fire the event on - leave null for current mode,
- override_input_type = None,
- ):
- """Creates a new Event object.
-
- :param event_type the type of the event, one of the EventType
- values
- :param identifier the identifier of the event source
- :param device_guid Device GUID identifying the device causing this event
- :param value the value of a joystick axis or hat
- :param is_pressed boolean flag indicating if a button or key
- :param raw_value the raw SDL value of the axis
- :param force_remote flag that indicates if the action should be executed on the remote only
- :param action_id the ID of the action to execute or that generated the event
- is pressed
- """
- self._id = gremlin.util.get_guid() # unique ID for this event
- self.event_type = event_type
- self._identifier = identifier
- self.device_guid = device_guid
- #self._is_pressed = is_pressed
- self.is_pressed = is_pressed
- self.value = value
- self.raw_value = raw_value
- self.curve_value = curved_value
- self.force_remote = force_remote
- self.action_id = action_id # the current action id to load
- self.data = data # extra data passed along with the event
- self.is_axis = is_axis
- self.virtual_code = virtual_code # vk if a keyboard event (the identifier will be the key_id (scancode, extended))
- self.is_virtual = is_virtual # true if the item is a vjoy device input
- self.is_virtual_button = False # true if a virtual button
- self.is_custom = False # true if a custom event (should be processed)
- self.mode = mode # mode to act on, should be null for default
- self.is_repeater = False # True if the event is a repeater generated event
- self.override_input_type = override_input_type # override input type - used as the input type for actions
-
-
-
- def clone(self):
- """Returns a clone of the event.
-
- :return cloned copy of this event
- """
- import copy
- if not isinstance(self.identifier, int):
- self.identifier = gremlin.base_classes.PickleTarget(self.identifier)
- dup = copy.deepcopy(self)
- dup._id = gremlin.util.get_guid() # unique ID for this event
- return dup
-
- @property
- def device_id(self) -> str:
- ''' id as a string '''
- return str(self.device_guid)
-
- @property
- def identifier(self):
- if isinstance(self._identifier, gremlin.base_classes.PickleTarget):
- self._identifier = self._identifier.item
- return self._identifier
-
- @identifier.setter
- def identifier(self, value):
- self._identifier = value
-
- def getInputType(self):
- if self.override_input_type:
- return self.override_input_type
- return self.event_type
-
- # @property
- # def is_pressed(self):
- # return self._is_pressed
-
- # @is_pressed.setter
- # def is_pressed(self, value):
- # if value:
- # pass
- # self._is_pressed = value
-
- def fake_button(self, is_pressed = True, clone = False):
- ''' converts the event to a fake button '''
- e = self.clone() if clone else self
- e.event_type = InputType.JoystickButton
- e.identifier = 1
- e.is_axis = False # range exit is a button type event
- e.value = is_pressed
- e.is_pressed = is_pressed
- return e
-
-
-
- def __eq__(self, other):
- return self.__hash__() == other.__hash__()
-
- def __ne__(self, other):
- return not (self == other)
-
- @property
- def callbackKey(self):
- ''' unique key to use to identify the specific callback '''
- if self.event_type == InputType.Keyboard:
- data = (self.identifier.scan_code, self.identifier.is_extended) if isinstance(self.identifier, gremlin.keyboard.Key) else self.identifier
- return (
- self.device_guid,
- self.event_type.value,
- data,
- 1 if data[1] else 0
- )
- else:
- return (
- self.device_guid,
- self.event_type.value,
- self.identifier,
- 0
- )
-
-
-
- def __hash__(self):
- """Computes the hash value of this event.
- new in m58: use the unique ID of this event to uniquely identify it
-
- :return integer hash value of this event
- """
-
-
- return hash(self._id)
-
- @property
- def hardwareKey(self):
- ''' unique key for the input'''
- return ((self.device_guid, self.event_type, self.identifier))
-
-
- @staticmethod
- def from_key(key):
- """Creates an event object corresponding to the provided key.
-
- :param key the Key object from which to create the Event
- :return Event object corresponding to the provided key
- """
- if hasattr(key,"scan_code") and hasattr(key,"is_extended"):
- return Event(
- event_type = InputType.Keyboard,
- identifier = (key.scan_code, key.is_extended),
- virtual_code = key.virtual_code,
- device_guid = dinput.GUID_Keyboard
- )
-
- raise ValueError(f"Unable to handle parameter - not a valid key: {key}")
-
- def __str__(self):
- if self.event_type == InputType.Mouse:
- return f"Event: Mouse - button {self.identifier} pressed: {self.is_pressed}"
- elif self.event_type in (InputType.Keyboard, InputType.KeyboardLatched):
- return f"Event: Keyboard - scan code, extended : {self.identifier} vk: {self.virtual_code} (0x{self.virtual_code:X}) pressed: {self.is_pressed}"
- elif self.event_type == InputType.JoystickAxis or self.is_axis:
- return f"Event: Axis : {self.identifier} raw value: {self.raw_value} value: {self.value}"
- elif self.event_type == InputType.JoystickButton:
- return f"Event: Button : {self.identifier} pressed: {self.is_pressed} value: {self.value}"
- elif self.event_type == InputType.ModeControl:
- return f"Event: Mode Control : {self.identifier} pressed: {self.is_pressed} value: {self.value} mode: {self.mode}"
- elif self.event_type == InputType.JoystickHat:
- return f"Event: Hat : {self.identifier} pressed: {self.is_pressed} value: {self.value}"
- elif self.event_type == InputType.Midi:
- return f"Event: Midi : {self.identifier} value: {self.value}"
- elif self.event_type == InputType.OpenSoundControl:
- return f"Event: OSC : {self.identifier} value: {self.value}"
-
- return f"Event: {self.event_type} identifier {self.identifier}"
class DeviceChangeEvent:
- ''' sent when a new device is selected '''
- def __init__(self):
- self.device_guid = None
- self.device_name = None
- self.device_input_id = 0
- self.device_input_type = 0
- self.input_type = 0
- self.vjoy_device_id = 0
- self.vjoy_input_id = 0
- self.source = None # object source responsible for the change, for example, the action
+ """sent when a new device is selected"""
+
+ def __init__(self):
+ self.device_guid = None
+ self.device_name = None
+ self.device_input_id = 0
+ self.device_input_type = 0
+ self.input_type = 0
+ self.vjoy_device_id = 0
+ self.vjoy_input_id = 0
+ self.source = (
+ None # object source responsible for the change, for example, the action
+ )
+
class StateChangeEvent:
- ''' sent when the state changes '''
- def __init__(self, is_local = False, is_remote = False, is_broadcast_enabled = False):
- self.is_local = is_local
- self.is_remote = is_remote
- self.is_broadcast_enabled = is_broadcast_enabled
+ """sent when the state changes"""
-class VjoyEvent:
- def __init__(self, vjoy_id, input_type : InputType, input_id : int, value):
- self.vjoy_id = vjoy_id
- self.input_type = input_type
- self.input_id = input_id
- self.value = value
+ def __init__(self, is_local=False, is_remote=False, is_broadcast_enabled=False):
+ self.is_local = is_local
+ self.is_remote = is_remote
+ self.is_broadcast_enabled = is_broadcast_enabled
+class VjoyEvent:
+ def __init__(self, vjoy_id, input_type: InputType, input_id: int, value):
+ self.vjoy_id = vjoy_id
+ self.input_type = input_type
+ self.input_id = input_id
+ self.value = value
@gremlin.singleton_decorator.SingletonDecorator
class EventListener(QtCore.QObject):
+ """Listens for keyboard and joystick events and publishes them
+ via QT's signal/slot interface.
+ """
- """Listens for keyboard and joystick events and publishes them
- via QT's signal/slot interface.
- """
-
- ui_ready = QtCore.Signal() # tell the UI all is ready
-
- # Signal emitted when joystick events are received
- joystick_event = QtCore.Signal(Event)
-
- hardware_input_event = QtCore.Signal(object, object, object) # called for any input event (device_guid, input_type, input_id)
-
- vjoy_event = QtCore.Signal(VjoyEvent)
-
- # custom joystick event - this is a code based joystick event
- custom_joystick_event = QtCore.Signal(Event)
-
- # Signal emitted when keyboard events are received
- keyboard_event = QtCore.Signal(Event)
- # Signal emitted when mouse events are received
- mouse_event = QtCore.Signal(Event)
- # Signal emitted when virtual button events are received
- virtual_event = QtCore.Signal(Event)
-
- # signal emmitted when a MIDI input is received
- midi_event = QtCore.Signal(Event)
-
- # signal emitted when an OSC input is received
- osc_event = QtCore.Signal(Event)
-
- # Signal emitted when a joystick is attached or removed
- device_change_event = QtCore.Signal()
-
- # fires when the number of gamepad devices changes
- gamepad_change_event = QtCore.Signal()
-
- # called when a process device change should be handled
- _process_device_change = QtCore.Signal()
-
- # Signal emitted when the icon needs to be refreshed
- icon_changed = QtCore.Signal(DeviceChangeEvent)
-
- # Signal emitted when a profile is changed (to refresh UI)
- profile_changed = QtCore.Signal()
-
- # profile loaded event
- profile_loaded = QtCore.Signal()
-
- # profile unloaded - trigger when a profile is being unloaded
- profile_unloaded = QtCore.Signal()
-
- # signal emitted when the selected hardware device changes
- profile_device_changed = QtCore.Signal(DeviceChangeEvent)
-
- # signal emitted when the selected hardware device changes
- profile_device_mapping_changed = QtCore.Signal(DeviceChangeEvent)
-
- # signal emitted when the UI tabs are loaded and profiles are loaded - some widgets use this for post-UI initialization update that needs to occur after the UI data is completely loaded
- tabs_loaded = QtCore.Signal()
-
- refresh_devices = QtCore.Signal() # used to refresh the device list going into GremlinEx
-
- profile_reset = QtCore.Signal() # profile reset signal (when runtime for a profile needs to reset)
- profile_start = QtCore.Signal() # profile start signal (when a profile starts)
- profile_started = QtCore.Signal() # profile started signal (after a profile starts)
- profile_stop = QtCore.Signal() # profile stop signal (when a profile stops)
- profile_stop_toolbar = QtCore.Signal() # profile stop signal (when a profile stops because the toolbar is pressed)
- profile_unload = QtCore.Signal() # profile unload signal (when a profile is unloaded and a new profile loaded)
- request_profile_stop = QtCore.Signal(str) # request the profile to stop (reason: str)
-
- process_monitor_changed = QtCore.Signal() # process monitor options changed
-
-
- config_changed = QtCore.Signal() # occurs on broadcast configuration change
- config_option_changed = QtCore.Signal() # occurs on broadcast configuration change
-
- options_changed = QtCore.Signal() # occurs when the options dialog closes to have components check for any changes
+ ui_ready = QtCore.Signal() # tell the UI all is ready
- # occurs on broadcast mode change
- broadcast_changed = QtCore.Signal(StateChangeEvent)
+ # Signal emitted when joystick events are received
+ joystick_event = QtCore.Signal(Event)
- # occurs on mode edit/update/delete of modes (edit time only)
- edit_mode_changed = QtCore.Signal(str) # param: the mode that was changed to
+ hardware_input_event = QtCore.Signal(
+ object, object, object
+ ) # called for any input event (device_guid, input_type, input_id)
- mode_name_changed = QtCore.Signal(str, str) # runs when a mode name change occurs for the UI to update - param (old name, new name)
- mode_list_update = QtCore.Signal() # runs when mode lists changes
- profile_modes_changed = QtCore.Signal() # occurs when the hierarchy, or list of modes changed for a given profile (mode added, removed, changed or renamed)
- execution_context_changed = QtCore.Signal() # occurs when execution context changes
+ vjoy_event = QtCore.Signal(VjoyEvent)
- runtime_mode_changed = QtCore.Signal(str) # runs when the runtime profile mode changes (runtime mode only, when a profile has been started) - param - the mode changed to
+ # custom joystick event - this is a code based joystick event
+ custom_joystick_event = QtCore.Signal(Event)
- # functor enable flag changed
- action_created = QtCore.Signal(object) # runs when an action is created - object = the object that triggered the event
+ # Signal emitted when keyboard events are received
+ keyboard_event = QtCore.Signal(Event)
+ # Signal emitted when mouse events are received
+ mouse_event = QtCore.Signal(Event)
+ # Signal emitted when virtual button events are received
+ virtual_event = QtCore.Signal(Event)
- # remove action
- action_delete = QtCore.Signal(object, object, object) # fires when an action is about to be deleted, passes the inputItem, container, action as a parameters
+ # signal emmitted when a MIDI input is received
+ midi_event = QtCore.Signal(Event)
+
+ # signal emitted when an OSC input is received
+ osc_event = QtCore.Signal(Event)
+
+ # Signal emitted when a joystick is attached or removed
+ device_change_event = QtCore.Signal()
+
+ # fires when the number of gamepad devices changes
+ gamepad_change_event = QtCore.Signal()
+
+ # called when a process device change should be handled
+ _process_device_change = QtCore.Signal()
+
+ # Signal emitted when the icon needs to be refreshed
+ icon_changed = QtCore.Signal(DeviceChangeEvent)
+
+ # Signal emitted when a profile is changed (to refresh UI)
+ profile_changed = QtCore.Signal()
+
+ # profile loaded event
+ profile_loaded = QtCore.Signal()
+
+ # profile unloaded - trigger when a profile is being unloaded
+ profile_unloaded = QtCore.Signal()
+
+ # signal emitted when the selected hardware device changes
+ profile_device_changed = QtCore.Signal(DeviceChangeEvent)
+
+ # signal emitted when the selected hardware device changes
+ profile_device_mapping_changed = QtCore.Signal(DeviceChangeEvent)
+
+ # signal emitted when the UI tabs are loaded and profiles are loaded - some widgets use this for post-UI initialization update that needs to occur after the UI data is completely loaded
+ tabs_loaded = QtCore.Signal()
+
+ refresh_devices = (
+ QtCore.Signal()
+ ) # used to refresh the device list going into GremlinEx
+
+ profile_reset = (
+ QtCore.Signal()
+ ) # profile reset signal (when runtime for a profile needs to reset)
+ profile_start = QtCore.Signal() # profile start signal (when a profile starts)
+ profile_started = QtCore.Signal() # profile started signal (after a profile starts)
+ profile_stop = QtCore.Signal() # profile stop signal (when a profile stops)
+ profile_stop_toolbar = (
+ QtCore.Signal()
+ ) # profile stop signal (when a profile stops because the toolbar is pressed)
+ profile_unload = (
+ QtCore.Signal()
+ ) # profile unload signal (when a profile is unloaded and a new profile loaded)
+ request_profile_stop = QtCore.Signal(
+ str
+ ) # request the profile to stop (reason: str)
+
+ process_monitor_changed = QtCore.Signal() # process monitor options changed
+
+ config_changed = QtCore.Signal() # occurs on broadcast configuration change
+ config_option_changed = QtCore.Signal() # occurs on broadcast configuration change
+
+ options_changed = (
+ QtCore.Signal()
+ ) # occurs when the options dialog closes to have components check for any changes
+
+ # occurs on broadcast mode change
+ broadcast_changed = QtCore.Signal(StateChangeEvent)
+
+ # occurs on mode edit/update/delete of modes (edit time only)
+ edit_mode_changed = QtCore.Signal(str) # param: the mode that was changed to
+
+ mode_name_changed = QtCore.Signal(
+ str, str
+ ) # runs when a mode name change occurs for the UI to update - param (old name, new name)
+ mode_list_update = QtCore.Signal() # runs when mode lists changes
+ profile_modes_changed = QtCore.Signal() # occurs when the hierarchy, or list of modes changed for a given profile (mode added, removed, changed or renamed)
+ execution_context_changed = QtCore.Signal() # occurs when execution context changes
+
+ runtime_mode_changed = QtCore.Signal(
+ str
+ ) # runs when the runtime profile mode changes (runtime mode only, when a profile has been started) - param - the mode changed to
+
+ # functor enable flag changed
+ action_created = QtCore.Signal(
+ object
+ ) # runs when an action is created - object = the object that triggered the event
+
+ # remove action
+ action_delete = QtCore.Signal(
+ object, object, object
+ ) # fires when an action is about to be deleted, passes the inputItem, container, action as a parameters
+
+ # selection event - tells the UI to show a different input
+ select_input = QtCore.Signal(
+ object, object, object, bool, bool, bool
+ ) # selects a particular input (device_guid, input_type, input_id, force_update, force_switch, tab_changed)
+ select_input_completed = QtCore.Signal(
+ object, object, object
+ ) # indicates input selection is completed (device_guid, input_type, input_id)
+
+ input_selected = QtCore.Signal(
+ object
+ ) # widget item was selected, parameter = InputItemWidget
+ input_item_selected = QtCore.Signal(
+ object, int
+ ) # widget item was selected, parameter = InputItem, index of input item in the listview
+ input_unselected = QtCore.Signal(
+ object
+ ) # widget item was unselected selected, parameter = InputItemWidget
+
+ tab_selected = QtCore.Signal(
+ str
+ ) # tab selected, the device_guid (str) is passed as the parameter - this is triggered when a device tab is selected and made visible
+ tab_unselected = QtCore.Signal(
+ str
+ ) # tab unselected, the device_guid (str) is passed as the parameter - this is triggered when a device tab is selected and made visible
+
+ # mapping changed - either container or action added -
+ mapping_changed = QtCore.Signal(
+ object
+ ) # fires when a container or action changes on an InputItem - passes the InputItem as the parameter
+
+ # suspend keyboard input
+ suspend_keyboard_input = QtCore.Signal(
+ bool
+ ) # arg = state, true = suspend, false = resume
+
+ # called when vjoy button usage has changed in the profile so displays can update themselves
+ button_usage_changed = QtCore.Signal(
+ int
+ ) # (vjoy_device_id) the vjoy device that changed
+
+ # called when a condition state changes - used to update the UI
+ condition_redraw = QtCore.Signal(object) # fires when a condition is redrawing
+ condition_state_changed = QtCore.Signal(object) # passes along the container
+ condition_added = QtCore.Signal(
+ object, str, object
+ ) # fires when a condition is added - params (input_item, mode, condition)
+ condition_removed = QtCore.Signal(
+ object, str, object
+ ) # fires when a condition is removed - params (input_item, mode, condition)
+
+ # container deleted
+ container_delete = QtCore.Signal(
+ object, object
+ ) # fires when a container is about to be deleted, passes the input item, container as parameters
+
+ # update input curve icons
+ update_input_icons = (
+ QtCore.Signal()
+ ) # fires when the UI needs to refresh input calibration and curve icons
+ update_action_icons = (
+ QtCore.Signal()
+ ) # fires when the UI needs to update the action icons
+
+ # occurs when input enabled state changes
+ input_enabled_changed = QtCore.Signal(object) # param - InputItem
+
+ # occurs when calibration data changes
+ calibration_changed = QtCore.Signal(object) # param - CalibrationData object
+
+ # occurs when a macro step completes
+ macro_step_completed = QtCore.Signal(
+ int
+ ) # param - macro ID returned by the queue_macro function
+
+ # request profile activate/deactivate
+ request_activate = QtCore.Signal(
+ bool
+ ) # param - flag - true to activate, false to deactivate
+
+ # abort load
+ abort = QtCore.Signal() # tells loops/thread at active time to stop - called when a profile needs to stop due to a start error
+
+ # request OSC start/stop
+ request_osc = QtCore.Signal(bool) # param - flag - true to start, false to stop
+ osc_input_port_changed = QtCore.Signal() # occurs when OSC input port is changed
+ osc_output_port_changed = QtCore.Signal() # occurs when OSC output port is changed
+ osc_output_server_changed = (
+ QtCore.Signal()
+ ) # occurs when OSC server output IP is changed
+
+ # request MIDI start/stop
+ request_midi = QtCore.Signal(bool) # param - flag - true to start, false to stop
+
+ # # signals the need to register an OSC input item
+ # register_osc_input = QtCore.Signal(object) # param input_item being registered
+
+ # gremlin ex shutdown in progress
+ shutdown = QtCore.Signal()
+
+ # toggle highlighting mode state
+ toggle_highlight = QtCore.Signal(object, object, object) # param (axis,button)
+ enable_highlight_changed = QtCore.Signal(
+ bool
+ ) # fires when highlight enable is turned on param(enabled)
+
+ button_state_change = QtCore.Signal(
+ Event
+ ) # indicates a change in button state params: (device_guid, input_type, input_id, is_pressed)
+ axis_state_change = QtCore.Signal(
+ Event
+ ) # indicates a change in axis state params: (device_guid, input_type, input_id, is_pressed)
+
+ update_input_state = QtCore.Signal(
+ object
+ ) # request to update all axis and button input states in the UI for a given device: (device_guid)
+
+ # heartbeat
+ heartbeat = QtCore.Signal() # ticks every 30 seconds
+
+ # autorepeat abort flag
+ autorepeat_clear = (
+ QtCore.Signal()
+ ) # fire this to abort any keyboard autorepeat actions
+
+ # module status state notices
+ module_state_change = QtCore.Signal(
+ str, object
+ ) # send a module state update, (key, state)
+ module_state_register = QtCore.Signal(
+ str, str, object, object
+ ) # registers a module state (key, label, state, callback) - if callback is not None, sets up a button when clicked will execute the callback
+
+ # notify when an input is selected
+ input_selection_changed = QtCore.Signal(
+ object, object, object
+ ) # (device_guid, input_type, input_id)
+
+ def __init__(self):
+ """Creates a new instance."""
+ QtCore.QObject.__init__(self)
+ self.keyboard_hook = windows_event_hook.KeyboardHook()
+ self.keyboard_hook.register(self._keyboard_handler)
+
+ config = gremlin.config.Configuration()
+ self.mouse_hook = None
+ self.enable_mouse_hook = not config.is_debug
+ self.enableMouse()
+
+ # Calibration function for each axis of all devices
+ self._calibrations = {}
+
+ # map of axis input items that could be curved
+ self._joystick_input_item_map = {}
+
+ # Joystick device change update timeout timer
+ self._device_update_timer = None
+
+ self._running = True
+
+ self._process_device_change_lock = False
+
+ # keyboard input handling buffer
+ self._keyboard_state = {}
+ self._keyboard_queue = None
+ self._key_listener_started = False # true if the key listener is started
+ self.gremlin_active = False
+ self._keyboard_thread = None
+ self.keyboard_hook.start()
+
+ self.device_change_event.connect(self._device_changed_cb)
+
+ # internal event on process change
+ self._process_device_change.connect(self._process_device_change_cb)
+
+ # calibration data access
+ self._calibrationManager = None
+
+ self.profile_started.connect(self._profile_started_cb)
+
+ self._run_event = threading.Event()
+ self._run_thread = Thread(target=self._run)
+ self._run_thread.setName("EventListener run thread")
+ self._run_thread.start()
+
+ self._keep_alive_event = threading.Event()
+ self._keep_alive_thread = threading.Thread(target=self._keep_alive, daemon=True)
+ self._keep_alive_thread.setName("heartbeat")
+ self._keep_alive_thread.start()
+
+ self.shutdown.connect(self._shutdown_handler)
+
+ @QtCore.Slot()
+ def _shutdown_handler(self):
+ """terminate threads"""
+ if self._keep_alive_thread:
+ self._keep_alive_event.set()
+ self._keep_alive_thread.join()
+ self._keep_alive_thread = None
+
+ if self._run_thread:
+ self._run_event.set()
+ self._run_thread.join()
+ self._run_thread = None
+
+ @property
+ def calibrationManager(self):
+ from gremlin.ui.axis_calibration import CalibrationManager
+
+ if not self._calibrationManager:
+ self._calibrationManager = CalibrationManager()
+
+ return self._calibrationManager
+
+ def _profile_started_cb(self):
+ """occurs on profile start"""
+ device_guid = gremlin.shared_state.mode_tab_guid
+ mode_enter = gremlin.ui.mode_device.ModeInputModeType.ModeEnter
+ delay = 0.250 # delay in seconds between press/release events for mode control change
+ new_mode = gremlin.shared_state.runtime_mode
+
+ event_enter_pressed = Event(
+ InputType.ModeControl,
+ identifier=mode_enter,
+ device_guid=device_guid,
+ is_pressed=True,
+ mode=new_mode,
+ )
+ event_enter_released = Event(
+ InputType.ModeControl,
+ identifier=mode_enter,
+ device_guid=device_guid,
+ is_pressed=False,
+ mode=new_mode,
+ )
+
+ # fire mode change for mode enter (press + release)
+ eh = EventHandler()
+ m2_list, f2_list = eh.execute_event(event_enter_pressed)
+ enter_release = Timer(
+ delay, lambda: eh._execute_callbacks(event_enter_released, m2_list, f2_list)
+ )
+ enter_release.start()
+
+ def registerInput(self, item):
+ """registers an input item"""
+ if item.input_type == InputType.JoystickAxis:
+ key = (item.device_guid, item.input_id)
+ self._joystick_input_item_map[key] = item
+
+ def _device_changed_cb(self):
+ self._init_joysticks()
+
+ def mouseEnabled(self):
+ """returns mouse hook status"""
+ return self.mouse_hook is not None
+
+ def enableMouse(self):
+ if self.enable_mouse_hook:
+ if self.mouse_hook is None:
+ self.mouse_hook = windows_event_hook.MouseHook()
+ self.mouse_hook.register(self._mouse_handler)
+ self.mouse_hook.start()
+ else:
+ syslog.warning("************ DEBUG MODE - MOUSE HOOKS ARE DISABLED ")
+
+ def disableMouse(self):
+ if self.mouse_hook is not None:
+ self.mouse_hook.stop()
+ self.mouse_hook.unregister(self._mouse_handler)
+ self.mouse_hook = None
+
+ def push_joystick(self):
+ gremlin.shared_state.push_joystick()
+
+ def pop_joystick(self, reset=False):
+ gremlin.shared_state.pop_joystick(reset)
+
+ def push_input_selection(self):
+ gremlin.shared_state.push_input_selection()
+
+ def pop_input_selection(self, reset=False):
+ gremlin.shared_state.pop_input_selection(reset)
+
+ @property
+ def joystick_input_suspended(self) -> bool:
+ """true if joystick input suspended"""
+ return gremlin.shared_state.is_joystick_input_suspended
+
+ @property
+ def input_selection_suspended(self) -> bool:
+ """true if input selection is suspended"""
+ return gremlin.shared_state.is_input_selection_suspended
+
+ def _process_queue(self):
+ """processes an item the keyboard buffer queue"""
+ item, is_pressed = self._keyboard_queue.get()
+ verbose = gremlin.config.Configuration().verbose_mode_detailed
+ is_error = False
+ if verbose:
+ syslog.info(f"process_queue: found item: {item} is presseD: {is_pressed}")
+
+ if isinstance(item, int):
+ virtual_code = item
+ key = gremlin.keyboard.KeyMap.find_virtual(virtual_code)
+ self._keyboard_buffer[virtual_code] = is_pressed
+ key_id = key.index_tuple()
+ else:
+ key_id = item
+ scan_code, is_extended = item
+ key = gremlin.keyboard.KeyMap.find(scan_code, is_extended)
+
+ if key is None:
+ syslog.error(
+ f"DEQUEUE KEY: don't know how to handle scancode: {scan_code:x} extended: {is_extended}"
+ )
+ is_error = True
+ else:
+ virtual_code = key.virtual_code
+ self._keyboard_buffer[key_id] = is_pressed
+
+ if not is_error:
+ if verbose:
+ syslog.info(
+ f"DEQUEUE KEY {gremlin.keyboard.KeyMap.keyid_tostring(key_id)} id: {key_id} vk: {virtual_code} (0x{virtual_code:X}) name: {key.name} pressed: {is_pressed}"
+ )
+
+ self.keyboard_event.emit(
+ Event(
+ event_type=InputType.Keyboard,
+ device_guid=dinput.GUID_Keyboard,
+ identifier=key_id,
+ virtual_code=virtual_code,
+ is_pressed=is_pressed,
+ data=self._keyboard_buffer,
+ )
+ )
+
+ # process the events
+ QtWidgets.QApplication.processEvents()
+ self._keyboard_queue.task_done()
+
+ def _keyboard_processor(self):
+ """runs as a thread to process inbound keyboard events using a queue"""
+
+ syslog.info("KBD: processing start")
+ self._keyboard_buffer = {}
+ self._key_listener_started = True
+ threading.current_thread().reset()
+ while not self._keyboard_thread.stopped():
+ if self._keyboard_queue.empty():
+ time.sleep(0.01)
+ continue
+ self._process_queue()
+
+ # done
+ # process any straglers
+ while not self._keyboard_queue.empty():
+ self._process_queue()
+
+ syslog.info("KBD: processing stop")
+
+ def start_key_listener(self):
+ """starts the key listener"""
+ if not self._key_listener_started:
+ self._keyboard_queue = queue.Queue()
+
+ self._keyboard_thread = gremlin.threading.AbortableThread(
+ target=self._keyboard_processor
+ )
+ self._keyboard_thread.start()
+
+ def stop_key_listener(self):
+ """stops the key listener"""
+ if self._key_listener_started:
+ self._keyboard_thread.stop()
+ self._keyboard_thread.join()
+ # clear any remaining input queue items
+ while not self._keyboard_queue.empty():
+ self._keyboard_queue.get()
+ self._keyboard_queue.join()
+ self._key_listener_started = False
+
+ def start(self):
+ """starts the non regular listener"""
+ self.enableMouse()
+ self._key_listener_stop_requested = False
+ self.start_key_listener()
+
+ def stop(self):
+ self.disableMouse()
+ self.stop_key_listener()
+
+ def terminate(self):
+ """Stops the loop from running."""
+
+ # syslog = logging.getLogger("system")
+ syslog.info("EVENT: shutdown requested")
+ gremlin.shared_state.terminating = True # tell UI we're terminating to avoid uncessary updates if we're shutting down
+ self._running = False
+ self.keyboard_hook.stop()
+ self.disableMouse()
+
+ # send the shutdown trigger to all code parts
+ if self._run_thread is not None:
+ # terminate run thread
+ self._run_event.set()
+ self._run_thread.join()
+
+ # stop heart beat
+ self._keep_alive_event.set()
+ self._keep_alive_thread.join()
+
+ self.request_activate.emit(False)
+ self.shutdown.emit()
+
+ def reload_calibrations(self):
+ """Reloads the calibration data from the configuration file."""
+ from gremlin.util import create_calibration_function
+
+ cfg = config.Configuration()
+ for key in self._calibrations:
+ limits = cfg.get_calibration(key[0], key[1])
+ self._calibrations[key] = create_calibration_function(
+ limits[0], limits[1], limits[2]
+ )
+
+ def _run(self):
+ """Starts the event loop."""
+
+ if not dinput.DILL.initalized:
+ dinput.DILL.init()
+ syslog.info("DILL: start listen")
+ dinput.DILL.set_device_change_callback(self._joystick_device_handler)
+ dinput.DILL.set_input_event_callback(self._joystick_event_handler)
+ while self._running and not self._run_event.is_set():
+ # Keep this thread alive until we are done
+ time.sleep(0.05)
+ syslog.info("DILL: shutdown")
+ dinput.DILL.set_device_change_callback(None)
+ dinput.DILL.set_input_event_callback(None)
+
+ def _keep_alive(self):
+ """keep alive 30 second hearbeat"""
+ notify_time = time.time()
+ while not self._keep_alive_event.is_set():
+ if time.time() >= notify_time:
+ self.heartbeat.emit()
+ notify_time = time.time() + 30
+ time.sleep(1)
+
+ def _joystick_event_handler(self, data):
+ """Callback for joystick events.
+
+ The handler converts the event data into a signal which is then
+ emitted. IMPORTANT: Applies any calibration and curvature to the data before firing other events.
+
+ :param data the joystick event
+ """
+
+ if not gremlin.joystick_handling._joystick_initialized:
+ # not initialized yet
+ return
+
+ if gremlin.shared_state.is_joystick_suspended:
+ # ignore if joystick input is suspended
+ return
+
+ from gremlin.util import dill_hat_lookup
+
+ verbose = config.Configuration().verbose_mode_joystick
+
+ event = dinput.InputEvent(data)
+
+ # breakpoint()
+ device = gremlin.joystick_handling.device_info_from_guid(event.device_guid)
+ if device is None:
+ # device not initialized/not found = ignore
+ return
+
+ is_virtual = device.is_virtual if device is not None else False
+ if event.input_type == dinput.InputType.Axis:
+ if verbose:
+ syslog.info(event)
+
+ # get the curved input if the input is curved
+ raw_value = event.value
+ value = self._apply_calibration(event)
+ curved_value = self._apply_curve_ex(
+ event.device_guid, event.input_index, value
+ )
+ event = Event(
+ event_type=InputType.JoystickAxis,
+ device_guid=event.device_guid,
+ identifier=event.input_index,
+ value=value,
+ curved_value=curved_value,
+ raw_value=raw_value,
+ is_axis=True,
+ is_virtual=is_virtual,
+ )
+
+ # notify axis change for tab switches
+ if not gremlin.shared_state.is_running:
+ self.axis_state_change.emit(event)
+
+ self.joystick_event.emit(event)
+
+ elif event.input_type == dinput.InputType.Button:
+ event = Event(
+ event_type=InputType.JoystickButton,
+ device_guid=event.device_guid,
+ identifier=event.input_index,
+ is_pressed=event.value == 1,
+ is_virtual=is_virtual,
+ )
+
+ if not gremlin.shared_state.is_running:
+ gremlin.util.singleShot(lambda: self.button_state_change.emit(event))
+
+ gremlin.util.singleShot(lambda: self.joystick_event.emit(event))
+
+ elif event.input_type == dinput.InputType.Hat:
+ value = dill_hat_lookup[event.value]
+ event = Event(
+ event_type=InputType.JoystickHat,
+ device_guid=event.device_guid,
+ identifier=event.input_index,
+ is_pressed=value,
+ is_virtual=is_virtual,
+ value=value,
+ raw_value=value,
+ )
+
+ if not gremlin.shared_state.is_running:
+ gremlin.util.singleShot(lambda: self.button_state_change.emit(event))
+
+ gremlin.util.singleShot(lambda: self.joystick_event.emit(event))
+
+ def _joystick_device_handler(self, data, action):
+ """Callback for device change events.
+
+ This is called when a device is added or removed from the system. This
+ uses a timer to call the actual device update function to prevent
+ the addition or removal of a multiple devices at the same time to
+ cause repeat updates.
+
+ :param data information about the device changing state
+ :param action whether the device was added or removed
+ """
+
+ # ignore if a VIGEM device - these are handled, for the moment, directly by the action
+ if (
+ data.vendor_id == 0x045E
+ and data.product_id == 0x28E
+ and data.button_count == 10
+ and data.name == b"Controller (XBOX 360 For Windows)"
+ ):
+ return
+
+ if self._device_update_timer is not None:
+ self._device_update_timer.cancel()
+ self._device_update_timer = Timer(0.5, self._run_device_list_update)
+ self._device_update_timer.start()
+
+ def _run_device_list_update(self):
+ """Performs the update of the devices connected."""
+ self._process_device_change.emit()
+
+ def _process_device_change_cb(self):
+ if self._process_device_change_lock:
+ return
+
+ self._process_device_change_lock = True
+ # syslog = logging.getLogger("system")
+
+ try:
+ is_running = gremlin.shared_state.is_running
+ gremlin.shared_state.has_device_changes = True
+ if is_running:
+ if gremlin.config.Configuration().runtime_ignore_device_change:
+ syslog.warning(
+ "\tRuntime device change detected - ignoring due to options"
+ )
+ return
+ else:
+ syslog.warning("\tChange detected at runtime - stopping profile")
+ gremlin.shared_state.ui.activate(False)
+
+ # reset devices and fire off the device change event
+ joystick_handling.reset_devices()
+
+ finally:
+ self._process_device_change_lock = False
+
+ def _keyboard_handler(self, event):
+ """low level handler for callback for keyboard events.
+
+ The handler converts the event data into a signal which is then
+ emitted.
+
+ :param event the keyboard event
+ """
+ verbose = gremlin.config.Configuration().verbose_mode_keyboard
+
+ # verbose = True
+ virtual_code = event.virtual_code
+ key_id = (event.scan_code, event.is_extended)
+ is_pressed = event.is_pressed
+ if verbose:
+ syslog.info(
+ f"Recorded key: {key_id:} sc: {event.scan_code:X} ex: {event.is_extended} vk: {virtual_code} (0x{virtual_code:X}) pressed: {is_pressed}"
+ )
+
+ # deal with any code translations needed
+ key_id = gremlin.keyboard.KeyMap.translate_lookup(
+ key_id
+ ) # modify scan codes if needed
+ virtual_code = gremlin.keyboard.KeyMap.vk_lookup(key_id) # get virtual code
+ if verbose:
+ syslog.info(
+ f"Translated key: {key_id:} sc: {event.scan_code:X} ex: {event.is_extended} vk: {virtual_code} (0x{virtual_code:X}) pressed: {is_pressed}"
+ )
+
+ is_repeat = self._keyboard_state.get(key_id) and is_pressed
+
+ if is_repeat:
+ # ignore repeats
+ return True
+
+ self._keyboard_state[key_id] = is_pressed
+
+ if gremlin.shared_state.is_running:
+ # RUN mode - queue input events
+ if not self._key_listener_started:
+ return True
+
+ self._keyboard_queue.put((key_id, is_pressed))
+
+ # add to the processing queue
+ if verbose:
+ syslog.info(
+ f"QUEUE KEY {gremlin.keyboard.KeyMap.keyid_tostring(key_id)} vk 0x{virtual_code:X} pressed {is_pressed}"
+ )
+
+ else:
+ # DESIGN mode - straight
+ # print (f"FIRE KEY {key_id} pressed {is_pressed}")
+ self.keyboard_event.emit(
+ Event(
+ event_type=InputType.Keyboard,
+ device_guid=dinput.GUID_Keyboard,
+ identifier=key_id,
+ virtual_code=virtual_code,
+ is_pressed=is_pressed,
+ data=self._keyboard_state.copy(), # use a copy of the keyboard state at the time the key is sent
+ )
+ )
+
+ # Allow the windows event to propagate further
+ return True
+
+ def get_key_state(self, key: gremlin.keyboard.Key):
+ """returns the state of the given key"""
+ return self._keyboard_state.get(key.index_tuple(), False)
+
+ def get_shifted_state(self):
+ """returns true if either of the shift keys are down"""
+ lshift_key = gremlin.keyboard.Key(
+ scan_code=gremlin.keyboard.scan_codes.sc_shiftLeft
+ )
+ if self.get_key_state(lshift_key):
+ return True
+ rshift_key = gremlin.keyboard.Key(
+ scan_code=gremlin.keyboard.scan_codes.sc_shiftRight
+ )
+ if self.get_key_state(rshift_key):
+ return True
+ return False
+
+ def get_control_state(self):
+ """returns true if either of the control keys are down"""
+ lctrl_key = gremlin.keyboard.Key(
+ scan_code=gremlin.keyboard.scan_codes.sc_controlLeft
+ )
+ if self.get_key_state(lctrl_key):
+ return True
+ rctrl_key = gremlin.keyboard.Key(
+ scan_code=gremlin.keyboard.scan_codes.sc_controlRight
+ )
+ if self.get_key_state(rctrl_key):
+ return True
+ return False
+
+ def _mouse_handler(self, event):
+ """Callback for mouse events.
+
+ The handler converts the event data into a signal which is then
+ emitted.
+
+ :param event the mouse event
+ """
+
+ # Ignore events we created via the macro system
+ if not event.is_injected:
+ if not self._running:
+ return
+
+ # update keyboard state for that key
+ key_id = (event.button_id.value + 0x1000, False)
+ self._keyboard_state[key_id] = event.is_pressed
+
+ # if event.is_pressed:
+ # print(f"mouse pressed {event.button_id}")
+
+ self.mouse_event.emit(
+ Event(
+ event_type=InputType.Mouse,
+ device_guid=dinput.GUID_Keyboard,
+ identifier=event.button_id,
+ is_pressed=event.is_pressed,
+ data=self._keyboard_state,
+ )
+ )
+ # print (f"Mouse button state: {key_id} {event.is_pressed}")
+ # Allow the windows event to propagate further
+ return True
+
+ def _apply_calibration(self, event):
+ """applies calibration data to the vent"""
+ return self._apply_calibration_ex(
+ event.device_guid, event.input_index, event.value
+ )
+
+ def _apply_curve(self, event):
+ """applies input curves to the input"""
+ return self._apply_curve_ex(event.device_guid, event.input_index, event.value)
+
+ def _apply_calibration_ex(self, device_guid, input_id, value):
+ """applies calibration and deadzone data to the raw input - value -32768 to 32767, returns -1, +1 and optionally inverts the input"""
+ calibration = self.calibrationManager.getCalibration(device_guid, input_id)
+ calibrated_value = calibration.getValue(value)
+ return calibrated_value
+
+ # from gremlin.util import axis_calibration
+ # key = (device_guid, input_id)
+ # if key in self._calibrations:
+ # return self._calibrations[key](value)
+ # else:
+ # return axis_calibration(value, -32768, 0, 32767)
+
+ def _apply_curve_ex(self, device_guid, input_id, value: float):
+ """applies a curve to the input axis"""
+ key = (device_guid, input_id)
+ if key in self._joystick_input_item_map:
+ item = self._joystick_input_item_map[key]
+ if item.curve_data is not None:
+ curved_value = item.curve_data.curve_value(value)
+ # print(f"curved: {value:0.4f} -> {curved_value:0.4f}")
+ return curved_value
+ return value
+
+ def apply_transforms(self, device_guid, input_id, raw_value):
+ """applies raw transforms to the data - input is expected in dinput range (-32K to +32k)"""
+ calib_value = self._apply_calibration_ex(device_guid, input_id, raw_value)
+ curved_value = self._apply_curve_ex(device_guid, input_id, calib_value)
+ # print(f"Raw value: {raw_value:0.4f} filtered: {calib_value:0.4f} Curved value: {curved_value:0.4f}")
+ return curved_value
+
+ def _init_joysticks(self):
+ """Initializes joystick devices."""
+ for dev_info in joystick_handling.joystick_devices():
+ self._load_calibrations(dev_info)
+
+ def _load_calibrations(self, device_info):
+ """Loads the calibration data for the given joystick.
+
+ :param device_info information about the device
+ """
+
+ from gremlin.util import create_calibration_function
+
+ cfg = config.Configuration()
+ for entry in device_info.axis_map:
+ limits = cfg.get_calibration(device_info.device_guid, entry.axis_index)
+ self._calibrations[(device_info.device_guid, entry.axis_index)] = (
+ create_calibration_function(limits[0], limits[1], limits[2])
+ )
+
+
+class TTSNotifyData:
+ """holds TTS data notification"""
+
+ def __init__(self):
+ self.profile = None
+ self.mode = None
- # selection event - tells the UI to show a different input
- select_input = QtCore.Signal(object, object, object, bool, bool, bool) # selects a particular input (device_guid, input_type, input_id, force_update, force_switch, tab_changed)
- select_input_completed = QtCore.Signal(object, object, object) # indicates input selection is completed (device_guid, input_type, input_id)
-
- input_selected = QtCore.Signal(object) # widget item was selected, parameter = InputItemWidget
- input_item_selected = QtCore.Signal(object, int) # widget item was selected, parameter = InputItem, index of input item in the listview
- input_unselected = QtCore.Signal(object) # widget item was unselected selected, parameter = InputItemWidget
-
- tab_selected = QtCore.Signal(str) # tab selected, the device_guid (str) is passed as the parameter - this is triggered when a device tab is selected and made visible
- tab_unselected = QtCore.Signal(str) # tab unselected, the device_guid (str) is passed as the parameter - this is triggered when a device tab is selected and made visible
-
-
-
-
- # mapping changed - either container or action added -
- mapping_changed = QtCore.Signal(object) # fires when a container or action changes on an InputItem - passes the InputItem as the parameter
-
- # suspend keyboard input
- suspend_keyboard_input = QtCore.Signal(bool) # arg = state, true = suspend, false = resume
-
- # called when vjoy button usage has changed in the profile so displays can update themselves
- button_usage_changed = QtCore.Signal(int) # (vjoy_device_id) the vjoy device that changed
-
- # called when a condition state changes - used to update the UI
- condition_redraw = QtCore.Signal(object) # fires when a condition is redrawing
- condition_state_changed = QtCore.Signal(object) # passes along the container
- condition_added = QtCore.Signal(object, str, object) # fires when a condition is added - params (input_item, mode, condition)
- condition_removed = QtCore.Signal(object, str, object) # fires when a condition is removed - params (input_item, mode, condition)
-
- # container deleted
- container_delete = QtCore.Signal(object, object) # fires when a container is about to be deleted, passes the input item, container as parameters
-
- # update input curve icons
- update_input_icons = QtCore.Signal() # fires when the UI needs to refresh input calibration and curve icons
- update_action_icons = QtCore.Signal() # fires when the UI needs to update the action icons
-
- # occurs when input enabled state changes
- input_enabled_changed = QtCore.Signal(object) # param - InputItem
-
- # occurs when calibration data changes
- calibration_changed = QtCore.Signal(object) # param - CalibrationData object
-
- # occurs when a macro step completes
- macro_step_completed = QtCore.Signal(int) # param - macro ID returned by the queue_macro function
-
- # request profile activate/deactivate
- request_activate = QtCore.Signal(bool) # param - flag - true to activate, false to deactivate
-
- # abort load
- abort = QtCore.Signal() # tells loops/thread at active time to stop - called when a profile needs to stop due to a start error
-
- # request OSC start/stop
- request_osc = QtCore.Signal(bool) # param - flag - true to start, false to stop
- osc_input_port_changed = QtCore.Signal() # occurs when OSC input port is changed
- osc_output_port_changed = QtCore.Signal() # occurs when OSC output port is changed
- osc_output_server_changed = QtCore.Signal() # occurs when OSC server output IP is changed
-
- # request MIDI start/stop
- request_midi = QtCore.Signal(bool) # param - flag - true to start, false to stop
-
- # # signals the need to register an OSC input item
- # register_osc_input = QtCore.Signal(object) # param input_item being registered
-
-
- # gremlin ex shutdown in progress
- shutdown = QtCore.Signal()
-
- # toggle highlighting mode state
- toggle_highlight = QtCore.Signal(object, object, object) # param (axis,button)
- enable_highlight_changed = QtCore.Signal(bool) # fires when highlight enable is turned on param(enabled)
-
- button_state_change = QtCore.Signal(Event) # indicates a change in button state params: (device_guid, input_type, input_id, is_pressed)
- axis_state_change = QtCore.Signal(Event) # indicates a change in axis state params: (device_guid, input_type, input_id, is_pressed)
-
- update_input_state = QtCore.Signal(object) # request to update all axis and button input states in the UI for a given device: (device_guid)
-
- # heartbeat
- heartbeat = QtCore.Signal() # ticks every 30 seconds
-
- # autorepeat abort flag
- autorepeat_clear = QtCore.Signal() # fire this to abort any keyboard autorepeat actions
-
- # module status state notices
- module_state_change = QtCore.Signal(str, object) # send a module state update, (key, state)
- module_state_register = QtCore.Signal(str, str, object, object) # registers a module state (key, label, state, callback) - if callback is not None, sets up a button when clicked will execute the callback
-
- # notify when an input is selected
- input_selection_changed = QtCore.Signal(object, object, object) # (device_guid, input_type, input_id)
-
-
- def __init__(self):
- """Creates a new instance."""
- QtCore.QObject.__init__(self)
- self.keyboard_hook = windows_event_hook.KeyboardHook()
- self.keyboard_hook.register(self._keyboard_handler)
-
- config = gremlin.config.Configuration()
- self.mouse_hook = None
- self.enable_mouse_hook = not config.is_debug
- self.enableMouse()
-
-
- # Calibration function for each axis of all devices
- self._calibrations = {}
-
- # map of axis input items that could be curved
- self._joystick_input_item_map = {}
-
- # Joystick device change update timeout timer
- self._device_update_timer = None
-
- self._running = True
-
- self._process_device_change_lock = False
-
- # keyboard input handling buffer
- self._keyboard_state = {}
- self._keyboard_queue = None
- self._key_listener_started = False # true if the key listener is started
- self.gremlin_active = False
- self._keyboard_thread = None
- self.keyboard_hook.start()
-
- self.device_change_event.connect(self._device_changed_cb)
-
- # internal event on process change
- self._process_device_change.connect(self._process_device_change_cb)
-
- # calibration data access
- self._calibrationManager = None
-
- self.profile_started.connect(self._profile_started_cb)
-
- self._run_event = threading.Event()
- self._run_thread = Thread(target=self._run)
- self._run_thread.setName("EventListener run thread")
- self._run_thread.start()
-
- self._keep_alive_event = threading.Event()
- self._keep_alive_thread = threading.Thread(target = self._keep_alive, daemon=True)
- self._keep_alive_thread.setName("heartbeat")
- self._keep_alive_thread.start()
-
- self.shutdown.connect(self._shutdown_handler)
-
-
- @QtCore.Slot()
- def _shutdown_handler(self):
- ''' terminate threads '''
- if self._keep_alive_thread:
- self._keep_alive_event.set()
- self._keep_alive_thread.join()
- self._keep_alive_thread = None
-
- if self._run_thread:
- self._run_event.set()
- self._run_thread.join()
- self._run_thread = None
-
- @property
- def calibrationManager(self):
- from gremlin.ui.axis_calibration import CalibrationManager
-
- if not self._calibrationManager:
- self._calibrationManager = CalibrationManager()
-
- return self._calibrationManager
-
- def _profile_started_cb(self):
- ''' occurs on profile start '''
- device_guid = gremlin.shared_state.mode_tab_guid
- mode_enter = gremlin.ui.mode_device.ModeInputModeType.ModeEnter
- delay = 0.250 # delay in seconds between press/release events for mode control change
- new_mode = gremlin.shared_state.runtime_mode
-
-
- event_enter_pressed = Event(InputType.ModeControl,
- identifier = mode_enter,
- device_guid= device_guid,
- is_pressed=True,
- mode = new_mode)
- event_enter_released = Event(InputType.ModeControl,
- identifier = mode_enter,
- device_guid= device_guid,
- is_pressed=False,
- mode = new_mode)
-
-
- # fire mode change for mode enter (press + release)
- eh = EventHandler()
- m2_list, f2_list = eh.execute_event(event_enter_pressed)
- enter_release = Timer(delay, lambda : eh._execute_callbacks(event_enter_released, m2_list, f2_list))
- enter_release.start()
-
-
-
- def registerInput(self, item):
- ''' registers an input item '''
- if item.input_type == InputType.JoystickAxis:
- key = (item.device_guid, item.input_id)
- self._joystick_input_item_map[key] = item
-
-
-
-
- def _device_changed_cb(self):
- self._init_joysticks()
-
- def mouseEnabled(self):
- ''' returns mouse hook status '''
- return self.mouse_hook is not None
-
- def enableMouse(self):
- if self.enable_mouse_hook:
- if self.mouse_hook is None:
- self.mouse_hook = windows_event_hook.MouseHook()
- self.mouse_hook.register(self._mouse_handler)
- self.mouse_hook.start()
- else:
- syslog.warning("************ DEBUG MODE - MOUSE HOOKS ARE DISABLED ")
-
-
- def disableMouse(self):
- if self.mouse_hook is not None:
- self.mouse_hook.stop()
- self.mouse_hook.unregister(self._mouse_handler)
- self.mouse_hook = None
-
- def push_joystick(self):
- gremlin.shared_state.push_joystick()
-
- def pop_joystick(self, reset = False):
- gremlin.shared_state.pop_joystick(reset)
-
- def push_input_selection(self):
- gremlin.shared_state.push_input_selection()
-
- def pop_input_selection(self, reset = False):
- gremlin.shared_state.pop_input_selection(reset)
-
-
- @property
- def joystick_input_suspended(self) -> bool:
- ''' true if joystick input suspended '''
- return gremlin.shared_state.is_joystick_input_suspended
-
- @property
- def input_selection_suspended(self) -> bool:
- ''' true if input selection is suspended '''
- return gremlin.shared_state.is_input_selection_suspended
-
-
- def _process_queue(self):
- ''' processes an item the keyboard buffer queue '''
- item, is_pressed = self._keyboard_queue.get()
- verbose = gremlin.config.Configuration().verbose_mode_detailed
- is_error = False
- if verbose:
- syslog.info(f"process_queue: found item: {item} is presseD: {is_pressed}")
-
- if isinstance(item, int):
- virtual_code = item
- key = gremlin.keyboard.KeyMap.find_virtual(virtual_code)
- self._keyboard_buffer[virtual_code] = is_pressed
- key_id = key.index_tuple()
- else:
-
- key_id = item
- scan_code, is_extended = item
- key = gremlin.keyboard.KeyMap.find(scan_code, is_extended)
-
- if key is None:
- syslog.error(f"DEQUEUE KEY: don't know how to handle scancode: {scan_code:x} extended: {is_extended}")
- is_error = True
- else:
- virtual_code = key.virtual_code
- self._keyboard_buffer[key_id] = is_pressed
-
- if not is_error:
- if verbose:
- syslog.info(f"DEQUEUE KEY {gremlin.keyboard.KeyMap.keyid_tostring(key_id)} id: {key_id} vk: {virtual_code} (0x{virtual_code:X}) name: {key.name} pressed: {is_pressed}")
-
-
- self.keyboard_event.emit(Event(
- event_type= InputType.Keyboard,
- device_guid=dinput.GUID_Keyboard,
- identifier=key_id,
- virtual_code = virtual_code,
- is_pressed=is_pressed,
- data = self._keyboard_buffer
- ))
-
- # process the events
- QtWidgets.QApplication.processEvents()
- self._keyboard_queue.task_done()
-
-
- def _keyboard_processor(self):
- ''' runs as a thread to process inbound keyboard events using a queue '''
-
- syslog.info("KBD: processing start")
- self._keyboard_buffer = {}
- self._key_listener_started = True
- threading.current_thread().reset()
- while not self._keyboard_thread.stopped():
- if self._keyboard_queue.empty():
- time.sleep(0.01)
- continue
- self._process_queue()
-
-
- # done
- # process any straglers
- while not self._keyboard_queue.empty():
- self._process_queue()
-
- syslog.info("KBD: processing stop")
-
-
- def start_key_listener(self):
- ''' starts the key listener '''
- if not self._key_listener_started:
- self._keyboard_queue = queue.Queue()
-
- self._keyboard_thread = gremlin.threading.AbortableThread(target = self._keyboard_processor)
- self._keyboard_thread.start()
-
- def stop_key_listener(self):
- ''' stops the key listener '''
- if self._key_listener_started:
- self._keyboard_thread.stop()
- self._keyboard_thread.join()
- # clear any remaining input queue items
- while not self._keyboard_queue.empty():
- self._keyboard_queue.get()
- self._keyboard_queue.join()
- self._key_listener_started = False
-
-
-
- def start(self):
- ''' starts the non regular listener '''
- self.enableMouse()
- self._key_listener_stop_requested = False
- self.start_key_listener()
-
-
- def stop(self):
- self.disableMouse()
- self.stop_key_listener()
-
-
- def terminate(self):
- """Stops the loop from running."""
-
- # syslog = logging.getLogger("system")
- syslog.info("EVENT: shutdown requested")
- gremlin.shared_state.terminating = True # tell UI we're terminating to avoid uncessary updates if we're shutting down
- self._running = False
- self.keyboard_hook.stop()
- self.disableMouse()
-
- # send the shutdown trigger to all code parts
- if self._run_thread is not None:
- # terminate run thread
- self._run_event.set()
- self._run_thread.join()
-
- # stop heart beat
- self._keep_alive_event.set()
- self._keep_alive_thread.join()
-
- self.request_activate.emit(False)
- self.shutdown.emit()
-
-
- def reload_calibrations(self):
- """Reloads the calibration data from the configuration file."""
- from gremlin.util import create_calibration_function
- cfg = config.Configuration()
- for key in self._calibrations:
- limits = cfg.get_calibration(key[0], key[1])
- self._calibrations[key] = \
- create_calibration_function(
- limits[0],
- limits[1],
- limits[2]
- )
-
- def _run(self):
- """Starts the event loop."""
-
- if not dinput.DILL.initalized:
- dinput.DILL.init()
- syslog.info("DILL: start listen")
- dinput.DILL.set_device_change_callback(self._joystick_device_handler)
- dinput.DILL.set_input_event_callback(self._joystick_event_handler)
- while self._running and not self._run_event.is_set():
- # Keep this thread alive until we are done
- time.sleep(0.05)
- syslog.info("DILL: shutdown")
- dinput.DILL.set_device_change_callback(None)
- dinput.DILL.set_input_event_callback(None)
-
-
- def _keep_alive(self):
- ''' keep alive 30 second hearbeat '''
- notify_time = time.time()
- while not self._keep_alive_event.is_set():
- if time.time() >= notify_time:
- self.heartbeat.emit()
- notify_time = time.time() + 30
- time.sleep(1)
-
- def _joystick_event_handler(self, data):
- """Callback for joystick events.
-
- The handler converts the event data into a signal which is then
- emitted. IMPORTANT: Applies any calibration and curvature to the data before firing other events.
-
- :param data the joystick event
- """
-
- if not gremlin.joystick_handling._joystick_initialized:
- # not initialized yet
- return
-
- if gremlin.shared_state.is_joystick_suspended:
- # ignore if joystick input is suspended
- return
-
- from gremlin.util import dill_hat_lookup
- verbose = config.Configuration().verbose_mode_joystick
-
- event = dinput.InputEvent(data)
-
- #breakpoint()
- device = gremlin.joystick_handling.device_info_from_guid(event.device_guid)
- if device is None:
- # device not initialized/not found = ignore
- return
-
- is_virtual = device.is_virtual if device is not None else False
- if event.input_type == dinput.InputType.Axis:
- if verbose:
- syslog.info(event)
-
- # get the curved input if the input is curved
- raw_value = event.value
- value = self._apply_calibration(event)
- curved_value = self._apply_curve_ex(event.device_guid, event.input_index, value)
- event = Event(
- event_type= InputType.JoystickAxis,
- device_guid=event.device_guid,
- identifier=event.input_index,
- value = value,
- curved_value = curved_value,
- raw_value= raw_value,
- is_axis = True,
- is_virtual = is_virtual
- )
-
- # notify axis change for tab switches
- if not gremlin.shared_state.is_running:
- self.axis_state_change.emit(event)
-
- self.joystick_event.emit(event)
-
- elif event.input_type == dinput.InputType.Button:
- event = Event(
- event_type= InputType.JoystickButton,
- device_guid=event.device_guid,
- identifier=event.input_index,
- is_pressed=event.value == 1,
- is_virtual = is_virtual
- )
-
- if not gremlin.shared_state.is_running:
- gremlin.util.singleShot(lambda : self.button_state_change.emit(event))
-
- gremlin.util.singleShot(lambda: self.joystick_event.emit(event))
-
- elif event.input_type == dinput.InputType.Hat:
- value = dill_hat_lookup[event.value]
- event = Event(
- event_type= InputType.JoystickHat,
- device_guid=event.device_guid,
- identifier=event.input_index,
- is_pressed = value,
- is_virtual = is_virtual,
- value = value,
- raw_value= value
- )
-
- if not gremlin.shared_state.is_running:
- gremlin.util.singleShot(lambda : self.button_state_change.emit(event))
-
- gremlin.util.singleShot(lambda: self.joystick_event.emit(event))
-
-
- def _joystick_device_handler(self, data, action):
- """Callback for device change events.
-
- This is called when a device is added or removed from the system. This
- uses a timer to call the actual device update function to prevent
- the addition or removal of a multiple devices at the same time to
- cause repeat updates.
-
- :param data information about the device changing state
- :param action whether the device was added or removed
- """
-
- # ignore if a VIGEM device - these are handled, for the moment, directly by the action
- if data.vendor_id == 0x045E and data.product_id == 0x28E and data.button_count == 10 and data.name == b'Controller (XBOX 360 For Windows)':
- return
-
-
- if self._device_update_timer is not None:
- self._device_update_timer.cancel()
- self._device_update_timer = Timer(0.5, self._run_device_list_update)
- self._device_update_timer.start()
-
- def _run_device_list_update(self):
- """Performs the update of the devices connected."""
- self._process_device_change.emit()
-
-
- def _process_device_change_cb(self):
-
- if self._process_device_change_lock:
- return
-
- self._process_device_change_lock = True
- # syslog = logging.getLogger("system")
-
- try:
-
- is_running = gremlin.shared_state.is_running
- gremlin.shared_state.has_device_changes = True
- if is_running:
- if gremlin.config.Configuration().runtime_ignore_device_change:
- syslog.warning("\tRuntime device change detected - ignoring due to options")
- return
- else:
- syslog.warning("\tChange detected at runtime - stopping profile")
- gremlin.shared_state.ui.activate(False)
-
- # reset devices and fire off the device change event
- joystick_handling.reset_devices()
-
- finally:
- self._process_device_change_lock = False
-
-
-
-
-
- def _keyboard_handler(self, event):
- """ low level handler for callback for keyboard events.
-
- The handler converts the event data into a signal which is then
- emitted.
-
- :param event the keyboard event
- """
- verbose = gremlin.config.Configuration().verbose_mode_keyboard
-
- # verbose = True
- virtual_code = event.virtual_code
- key_id = (event.scan_code, event.is_extended)
- is_pressed = event.is_pressed
- if verbose:
- syslog.info(f"Recorded key: {key_id:} sc: {event.scan_code:X} ex: {event.is_extended} vk: {virtual_code} (0x{virtual_code:X}) pressed: {is_pressed}")
-
- # deal with any code translations needed
- key_id = gremlin.keyboard.KeyMap.translate_lookup(key_id) # modify scan codes if needed
- virtual_code = gremlin.keyboard.KeyMap.vk_lookup(key_id) # get virtual code
- if verbose:
- syslog.info(f"Translated key: {key_id:} sc: {event.scan_code:X} ex: {event.is_extended} vk: {virtual_code} (0x{virtual_code:X}) pressed: {is_pressed}")
-
-
- is_repeat = self._keyboard_state.get(key_id) and is_pressed
-
- if is_repeat:
- # ignore repeats
- return True
-
- self._keyboard_state[key_id] = is_pressed
-
- if gremlin.shared_state.is_running:
- # RUN mode - queue input events
- if not self._key_listener_started:
- return True
-
-
- self._keyboard_queue.put((key_id, is_pressed))
-
- # add to the processing queue
- if verbose:
- syslog.info(f"QUEUE KEY {gremlin.keyboard.KeyMap.keyid_tostring(key_id)} vk 0x{virtual_code:X} pressed {is_pressed}")
-
- else:
- # DESIGN mode - straight
- #print (f"FIRE KEY {key_id} pressed {is_pressed}")
- self.keyboard_event.emit(
- Event( event_type= InputType.Keyboard,
- device_guid=dinput.GUID_Keyboard,
- identifier= key_id,
- virtual_code = virtual_code,
- is_pressed=is_pressed,
- data = self._keyboard_state.copy() # use a copy of the keyboard state at the time the key is sent
- ))
-
-
-
- # Allow the windows event to propagate further
- return True
-
- def get_key_state(self, key: gremlin.keyboard.Key):
- ''' returns the state of the given key '''
- return self._keyboard_state.get(key.index_tuple(), False)
-
- def get_shifted_state(self):
- ''' returns true if either of the shift keys are down'''
- lshift_key = gremlin.keyboard.Key(scan_code = gremlin.keyboard.scan_codes.sc_shiftLeft)
- if self.get_key_state(lshift_key):
- return True
- rshift_key = gremlin.keyboard.Key(scan_code = gremlin.keyboard.scan_codes.sc_shiftRight)
- if self.get_key_state(rshift_key):
- return True
- return False
-
- def get_control_state(self):
- ''' returns true if either of the control keys are down '''
- lctrl_key = gremlin.keyboard.Key(scan_code = gremlin.keyboard.scan_codes.sc_controlLeft)
- if self.get_key_state(lctrl_key):
- return True
- rctrl_key = gremlin.keyboard.Key(scan_code = gremlin.keyboard.scan_codes.sc_controlRight)
- if self.get_key_state(rctrl_key):
- return True
- return False
-
-
- def _mouse_handler(self, event):
- """Callback for mouse events.
-
- The handler converts the event data into a signal which is then
- emitted.
-
- :param event the mouse event
- """
-
- # Ignore events we created via the macro system
- if not event.is_injected:
- if not self._running:
- return
-
- # update keyboard state for that key
- key_id = (event.button_id.value + 0x1000, False)
- self._keyboard_state[key_id] = event.is_pressed
-
- # if event.is_pressed:
- # print(f"mouse pressed {event.button_id}")
-
- self.mouse_event.emit(Event(
- event_type= InputType.Mouse,
- device_guid=dinput.GUID_Keyboard,
- identifier=event.button_id,
- is_pressed=event.is_pressed,
- data = self._keyboard_state
- ))
- # print (f"Mouse button state: {key_id} {event.is_pressed}")
- # Allow the windows event to propagate further
- return True
-
- def _apply_calibration(self, event):
- ''' applies calibration data to the vent'''
- return self._apply_calibration_ex(event.device_guid, event.input_index, event.value)
-
- def _apply_curve(self, event):
- ''' applies input curves to the input '''
- return self._apply_curve_ex(event.device_guid, event.input_index, event.value)
-
- def _apply_calibration_ex(self, device_guid, input_id, value):
- ''' applies calibration and deadzone data to the raw input - value -32768 to 32767, returns -1, +1 and optionally inverts the input '''
- calibration = self.calibrationManager.getCalibration(device_guid, input_id)
- calibrated_value = calibration.getValue(value)
- return calibrated_value
-
- # from gremlin.util import axis_calibration
- # key = (device_guid, input_id)
- # if key in self._calibrations:
- # return self._calibrations[key](value)
- # else:
- # return axis_calibration(value, -32768, 0, 32767)
-
- def _apply_curve_ex(self, device_guid, input_id, value : float):
- ''' applies a curve to the input axis '''
- key = (device_guid, input_id)
- if key in self._joystick_input_item_map:
- item = self._joystick_input_item_map[key]
- if item.curve_data is not None:
- curved_value = item.curve_data.curve_value(value)
- #print(f"curved: {value:0.4f} -> {curved_value:0.4f}")
- return curved_value
- return value
-
- def apply_transforms(self, device_guid, input_id, raw_value):
- ''' applies raw transforms to the data - input is expected in dinput range (-32K to +32k)'''
- calib_value = self._apply_calibration_ex(device_guid, input_id, raw_value)
- curved_value = self._apply_curve_ex(device_guid, input_id, calib_value)
- #print(f"Raw value: {raw_value:0.4f} filtered: {calib_value:0.4f} Curved value: {curved_value:0.4f}")
- return curved_value
-
- def _init_joysticks(self):
- """Initializes joystick devices."""
- for dev_info in joystick_handling.joystick_devices():
- self._load_calibrations(dev_info)
-
- def _load_calibrations(self, device_info):
- """Loads the calibration data for the given joystick.
-
- :param device_info information about the device
- """
-
- from gremlin.util import create_calibration_function
- cfg = config.Configuration()
- for entry in device_info.axis_map:
- limits = cfg.get_calibration(
- device_info.device_guid,
- entry.axis_index
- )
- self._calibrations[(device_info.device_guid, entry.axis_index)] = \
- create_calibration_function(
- limits[0],
- limits[1],
- limits[2]
- )
-
-
-class TTSNotifyData():
- ''' holds TTS data notification '''
- def __init__(self):
- self.profile = None
- self.mode = None
@gremlin.singleton_decorator.SingletonDecorator
class EventHandler(QtCore.QObject):
-
- """Listens to the inputs from multiple different input devices."""
-
-
- mode_status_update = QtCore.Signal() # tell the UI to update the mode status bar
-
- # signal emitted when the profile is changed
- profile_changed = QtCore.Signal(str)
-
- # Signal emitted when the application is pause / resumed
- is_active = QtCore.Signal(bool)
-
- last_action_changed = QtCore.Signal(object, str) # fires when the action changes in the selector (drop_down, name)
- last_container_changed = QtCore.Signal(object, str) # fires when the action changes in the selector (drop_down, name)
-
-
-
-
- def __init__(self):
- """Initializes the EventHandler instance."""
- QtCore.QObject.__init__(self)
- self.plugins = {}
- self._mode_validator_callbacks = {} # list of validators (callbacks) that return a boolean True if the mode change can occur - signature must be callable(str)->bool
- self._last_tts_data = TTSNotifyData() # last mode that triggered a TTS verbal notice
- el = gremlin.event_handler.EventListener()
- el.profile_start.connect(self._profile_start)
- el.profile_stop.connect(self._profile_stop)
- el.runtime_mode_changed.connect(self._update_mode_change)
-
-
- self.reset()
-
- @QtCore.Slot()
- def _profile_start(self):
- self._update_mode_change(gremlin.shared_state.runtime_mode)
-
- @QtCore.Slot()
- def _profile_stop(self):
- self._last_tts_notify = None
- self._last_tts_notify_time = None
-
- def registerModeValidator(self, callback):
- assert callable(callback)
- self._mode_validator_callbacks[callback] = callback
-
- def unregisterModeValidator(self, callback):
- if callback in self._mode_validator_callbacks:
- del self._mode_validator_callbacks[callback]
-
- def clearModeValidator(self):
- self._mode_validator_callbacks.clear()
-
- def runModeValidator(self, mode):
- ''' runs through all current validators to see if a mode change can occur '''
- result = True # assume we can
- for callback in self._mode_validator_callbacks:
- result = result and callback(mode)
- if not result:
- break
-
- return result
-
-
-
- def reset(self):
- config = gremlin.config.Configuration()
- verbose = config.verbose
- if verbose:
- syslog.info("EventHandler: reset()")
-
- self.process_callbacks = True
- self.callbacks = {}
- self.callback_key_map = {} # map of event callbackKey to event
- self.input_item_map = {} # map of input items keyed by device_guid, mode, input_type, input_id
- self.latched_events = {}
- self.latched_callbacks = {}
- self.midi_callbacks = {}
- self.osc_callbacks = {}
- self._event_lookup = {}
- self.latched_functors = {}
- self.experimental = config.experimental
-
-
-
- @property
- def runtime_mode(self):
- """Returns the currently active mode.
-
- :return name of the currently active mode
- """
- return gremlin.shared_state.runtime_mode
-
- @runtime_mode.setter
- def runtime_mode(self, value):
- gremlin.shared_state.runtime_mode = value
-
- @property
- def edit_mode(self):
- return gremlin.shared_state.edit_mode
-
- @edit_mode.setter
- def edit_mode(self, value):
- gremlin.shared_state.edit_mode = value
-
- @property
- def current_mode(self):
- ''' gets the current mode based on state '''
- return gremlin.shared_state.current_mode
-
- @property
- def previous_runtime_mode(self):
- ''' returns the previous mode '''
- return gremlin.shared_state.previous_runtime_mode
-
- @previous_runtime_mode.setter
- def previous_runtime_mode(self, value):
- ''' sets the active mode '''
- gremlin.shared_state.previous_runtime_mode = value
-
-
- def add_plugin(self, plugin):
- """Adds a new plugin to be attached to event callbacks.
-
- :param plugin the plugin to add
- """
- # Do not add the same type of plugin multiple times
- if plugin.keyword not in self.plugins:
- self.plugins[plugin.keyword] = plugin
-
- def dump_exectree(self, device_guid, mode, event):
- ''' outputs the execution tree to the log '''
- from types import FunctionType, MethodType
-
- verbose = gremlin.config.Configuration().verbose
- if not verbose:
- return
-
- get_device_name = gremlin.shared_state.get_device_name
- device_name = gremlin.shared_state.get_device_name(device_guid)
-
- for callbacks in self.callbacks[device_guid][mode][event.callbackKey]:
- for callback in callbacks:
- if not hasattr(callback,"execution_graph"):
- syslog.debug(f"\tDevice ID: {device_name} mode: {mode} event: {event} - skip callback - missing execution graph - don't know how to handle {type(callback)} *********")
- continue
-
- for callback_functor in callback.execution_graph.functors:
- if hasattr(callback_functor,"action_set"):
- for functor in callback_functor.action_set.functors:
- action_data = functor.action_data if hasattr(functor, "action_data") else None
- syslog.debug(f"\tDevice ID: {device_name} mode: {mode} event: {event} hash: {hash(event):X} type: {type(functor)}")
- if action_data:
- # dump member variables only
- syslog.debug("\t\tData block:")
- for attr in dir(action_data):
- if not attr.startswith("_"):
- item = getattr(action_data,attr)
-
- if not (isinstance(item, FunctionType) or isinstance(item, MethodType) or inspect.isabstract(item) or inspect.isclass(item)):
- syslog.debug(f"\t\t\t{attr}: {item}")
- else:
- syslog.debug(f"\tFunctor '{type(callback_functor).__name__} does not define an action set")
-
-
-
-
-
-
- def dump_callbacks(self):
- # dump latched events
- import gremlin.ui.keyboard_device
- import gremlin.shared_state
-
-
- get_device_name = gremlin.shared_state.get_device_name
-
- syslog.debug("------------ Latched Events ----------------")
- for device_guid in self.latched_events.keys():
- device_name = gremlin.shared_state.get_device_name(device_guid)
- for mode in self.latched_events[device_guid].keys():
- for key_pair in self.latched_events[device_guid][mode]:
- identifier = self.latched_events[device_guid][mode][key_pair]
- if isinstance(identifier, gremlin.ui.keyboard_device.KeyboardInputItem):
- if isinstance(key_pair, tuple):
- scan_code, is_extended = key_pair
- key_data = f"scan code: 0x{scan_code:X} extended: {is_extended}"
- else:
- key_data = str(key_pair)
- syslog.debug(f"\tDevice ID: {device_name} mode: {mode} pair: {key_data} data: {identifier.to_string()}")
-
- syslog.debug("------------ Execution callbacks ----------------")
- for device_guid in self.callbacks.keys():
- for mode in self.callbacks[device_guid].keys():
- for key in self.callbacks[device_guid][mode]:
- event = self.callback_key_map[key]
- self.dump_exectree(device_guid, mode, event)
-
-
- def add_latched_functor(self, device_guid, mode, event, functor):
- ''' registers an extra latched functor on inputs if a functor uses multiple inputs '''
- # regular event
- if device_guid not in self.latched_functors:
- self.latched_functors[device_guid] = {}
- if mode not in self.latched_functors[device_guid]:
- self.latched_functors[device_guid][mode] = {}
- key = event.callbackKey
- if not key in self.latched_functors[device_guid][mode]:
- self.latched_functors[device_guid][mode][key] = []
- self.latched_functors[device_guid][mode][key].append(functor)
- verbose = gremlin.config.Configuration().verbose
- if verbose:
- device_name = gremlin.joystick_handling.device_name_from_guid(device_guid)
- syslog.info(f"Added latched functor: {device_name} mode: {mode} type: {event.event_type.name} input: {event.identifier} key: {key}")
-
- def _matching_input_item(self, mode, event):
- ''' gets the matching input item from the event '''
-
- device_guid = event.device_guid
- input_type = event.event_type
- if input_type == InputType.Keyboard:
- input_type = InputType.KeyboardLatched
- if input_type == InputType.KeyboardLatched:
- magic = json.dumps(event.identifier)
- else:
- magic = event.identifier
-
- if not device_guid in self.input_item_map:
- return None
- if not mode in self.input_item_map[device_guid]:
- return None
- if not input_type in self.input_item_map[device_guid][mode]:
- return None
- if magic in self.input_item_map[device_guid][mode][input_type]:
- return self.input_item_map[device_guid][mode][input_type][magic]
-
- # syslog.info(f"No match: {input_type} {magic}")
- # for key in self.input_item_map[device_guid][mode][input_type].keys():
- # syslog.info(f"\t{key}")
- return None
-
-
- def registerInputItem(self, mode : str, input_item):
- ''' registers an input item with the event handler '''
- item: gremlin.base_profile.InputItem = input_item
- device_guid = item.device_guid
- input_type = item.input_type
- if input_type == InputType.Keyboard:
- input_type = InputType.KeyboardLatched
-
- if input_type == InputType.KeyboardLatched:
- # use the key sequence as the magic key
- magic = json.dumps(input_item.input_id.key_tuple)
- else:
- magic = item.input_id
-
- if not device_guid in self.input_item_map:
- self.input_item_map[device_guid] = {}
- if not mode in self.input_item_map[device_guid]:
- self.input_item_map[device_guid][mode] = {}
- if not input_type in self.input_item_map[device_guid][mode]:
- self.input_item_map[device_guid][mode][input_type] = {}
- self.input_item_map[device_guid][mode][input_type][magic] = input_item
-
- verbose = gremlin.config.Configuration().verbose_mode_inputs
- if verbose: syslog.info(f"Register InputItem: {input_item.display_name} mode {mode} {input_type} magic: {magic}")
-
-
- def add_callback(self, device_guid, mode, event, callback, permanent=False, node = None):
- """Installs the provided callback for the given event.
-
- :param device_guid the GUID of the device the callback is
- associated with
- :param mode the mode the callback belongs to
- :param event the event for which to install the callback
- :param callback the callback function to link to the provided
- event
- :param permanent if True the callback is always active even
- if the system is paused
- :node: the execution tree node
- """
- import gremlin.config
- import gremlin.ui.keyboard_device
- import gremlin.keyboard
-
- assert callable(callback)
-
- if event:
- if event.event_type in (InputType.Keyboard, InputType.KeyboardLatched):
- verbose = gremlin.config.Configuration().verbose_mode_keyboard
- # keyboard latched event
- identifier = event.identifier
- primary_key = identifier.key
-
-
-
-
- # verbose = True
-
- # if the key can latch with multiple primary keys, build the table of all combinations
- key_list = [primary_key]
- if primary_key.is_latched:
- # multiple keys
- key_list.extend(primary_key._latched_keys)
-
- for key in key_list:
- # the events will arrive as keyboard events - in any order - this makes sure latching is checked regardless of the order of key presses
-
-
- virtual_code = key.virtual_code
- keyid_source = key.index_tuple() # use the scan code for now
- #index = virtual_code if virtual_code > 0 else keyid
- keyid, _ = gremlin.keyboard.KeyMap.translate(keyid_source)
-
- if device_guid not in self.latched_events.keys():
- self.latched_events[device_guid] = {}
-
- if mode not in self.latched_events[device_guid].keys():
- self.latched_events[device_guid][mode] = {}
- if keyid not in self.latched_events[device_guid][mode].keys():
- self.latched_events[device_guid][mode][keyid] = []
- self.latched_events[device_guid][mode][keyid].append(identifier)
- if verbose:
- syslog.info(f"Key latch registered by guid {device_guid} mode: {mode} vk: {virtual_code} (0x{virtual_code:X}) source keyid: {gremlin.keyboard.KeyMap.keyid_tostring(keyid_source)} -> translated keyId: {gremlin.keyboard.KeyMap.keyid_tostring(keyid)} name: {key.name} -> {identifier.display_name}")
-
-
- if device_guid not in self.latched_callbacks.keys():
- self.latched_callbacks[device_guid] = {}
- if mode not in self.latched_callbacks[device_guid].keys():
- self.latched_callbacks[device_guid][mode] = {}
- if not key in self.latched_callbacks[device_guid][mode]:
- self.latched_callbacks[device_guid][mode][primary_key] = []
- data = self.latched_callbacks[device_guid][mode][primary_key]
- data.append((self._install_plugins(callback),permanent))
- return
-
-
- elif event.event_type == InputType.Midi:
- # MIDI event
- verbose = gremlin.config.Configuration().verbose_mode_midi
- midi_input = event.identifier
- key = midi_input.message_key
- if device_guid not in self.midi_callbacks.keys():
- self.midi_callbacks[device_guid] = {}
- if mode not in self.midi_callbacks[device_guid].keys():
- self.midi_callbacks[device_guid][mode] = {}
- if not key in self.midi_callbacks[device_guid][mode]:
- self.midi_callbacks[device_guid][mode][key] = []
- data = self.midi_callbacks[device_guid][mode][key]
- data.append((self._install_plugins(callback),permanent))
- if verbose: syslog.info(f"MIDI: register callback {mode} {key}")
-
- elif event.event_type == InputType.OpenSoundControl:
- # OSC event
- verbose = gremlin.config.Configuration().verbose
- osc_input = event.identifier
- key = osc_input.message_key
- if device_guid not in self.osc_callbacks.keys():
- self.osc_callbacks[device_guid] = {}
- if mode not in self.osc_callbacks[device_guid].keys():
- self.osc_callbacks[device_guid][mode] = {}
- if not key in self.osc_callbacks[device_guid][mode]:
- self.osc_callbacks[device_guid][mode][key] = []
- data = self.osc_callbacks[device_guid][mode][key]
- data.append((self._install_plugins(callback),permanent))
-
- else:
- # regular event - events are stored by the event key
- if device_guid not in self.callbacks:
- self.callbacks[device_guid] = {}
- if mode not in self.callbacks[device_guid]:
- self.callbacks[device_guid][mode] = {}
- key = event.callbackKey
- if key not in self.callbacks[device_guid][mode]:
- self.callbacks[device_guid][mode][key] = []
- self.callback_key_map[key] = event
- self.callbacks[device_guid][mode][key].append((
- self._install_plugins(callback),
- permanent
- ))
-
- def _matching_event_keys(self, event):
- ''' gets the list of latched keys for this event '''
- if not event.event_type in (InputType.Keyboard, InputType.KeyboardLatched, InputType.Mouse):
- # not a keyboard event
- return []
- import gremlin.config
- import gremlin.keyboard
-
- # convert mouse events to keyboard event
- if event.event_type == InputType.Mouse:
- from gremlin.ui.keyboard_device import KeyboardDeviceTabWidget
- device_guid = KeyboardDeviceTabWidget.device_guid
-
- mouse_button = event.identifier
- # convert the mouse button to the virtual scan code we use for mouse events
- index = (mouse_button.value + 0x1000, False)
- verbose = gremlin.config.Configuration().verbose_mode_mouse
- if verbose:
- syslog.info(f"matching mouse event {event.identifier} to {gremlin.keyboard.KeyMap.keyid_tostring(index)}")
- else:
- verbose = gremlin.config.Configuration().verbose_mode_keyboard
- device_guid = event.device_guid
- # index = event.virtual_code if event.virtual_code > 0 else event.identifier # this is (scan_code, is_extended)
- index, _ = gremlin.keyboard.KeyMap.translate(event.identifier)
- if verbose: syslog.info(f"matching key event {event.identifier} to {gremlin.keyboard.KeyMap.keyid_tostring(index)}")
-
- #event_key = Key(scan_code = identifier[0], is_extended = identifier[1], is_mouse = is_mouse, virtual_code= virtual_code)
- input_items = []
-
-
-
- if device_guid in self.latched_events:
-
- #print (f"found guid: {device_guid}")
- data = self.latched_events[event.device_guid]
- if self.runtime_mode in data.keys():
- data = data[self.runtime_mode]
- matching_keys = []
- if index in data.keys():
- #print ("found identifier")
- matching_keys = data[index]
- if not matching_keys:
- index_ex = (index[0], not index[1])
- if index_ex in data.keys():
- matching_keys = data[index_ex]
-
- for input_item in matching_keys:
- # key = input_item.key
- input_items.append(input_item)
-
- if verbose: syslog.info(f"KEY: found {len(input_items)} matching items")
- return input_items
-
- return []
-
-
- def build_event_lookup(self, inheritance_tree):
- """Builds the lookup table linking event to callback.
-
- This takes mode inheritance into account.
-
- :param inheritance_tree the tree of parent and children in the
- inheritance structure
- """
- # Propagate events from parent to children if the children lack
- # handlers for the available events
- callbacks_list = [self.callbacks, self.latched_callbacks, self.latched_events]
-
- # build the inheritance modes
- node = inheritance_tree
- if node.name:
- parent = node.name
- children = [n.name for n in node.children]
-
- # Each device is treated separately
- for callback_items in callbacks_list:
- for device_guid in callback_items:
- # Only attempt to copy handlers if we have any available in
- # the parent mode
- if parent in callback_items[device_guid]:
- device_cb = callback_items[device_guid]
- parent_cb = device_cb[parent]
- # Copy the handlers into each child mode, unless they
- # have their own handlers already defined
- for child in children:
- if child not in device_cb:
- device_cb[child] = {}
- for event, callbacks in parent_cb.items():
- if isinstance(event, gremlin.event_handler.Event):
- key = event.callbackKey
- else:
- key = event
- if key not in device_cb[child]:
- device_cb[child][key] = callbacks
-
- # Recurse until we've dealt with all modes
- for child in node.children:
- self.build_event_lookup(child)
-
- def change_profile(self, new_profile):
- ''' requests a profile load '''
- if new_profile != gremlin.shared_state.current_profile:
- self.profile_change.emit(new_profile)
-
-
- def set_mode(self, new_mode):
- ''' sets the edit or runtime mode based on the state '''
- assert new_mode,"Mode cannot be blank"
- if gremlin.shared_state.is_running:
- gremlin.shared_state.runtime_mode = new_mode
- else:
- gremlin.shared_state.edit_mode = new_mode
-
- def set_runtime_mode(self, new_mode):
- ''' sets the active runtime mode '''
- assert new_mode,"Mode cannot be blank"
- gremlin.shared_state.runtime_mode = new_mode
-
- def set_edit_mode(self, new_mode):
- ''' sets the active edit mode '''
- assert new_mode,"Mode cannot be blank"
- gremlin.shared_state.edit_mode = new_mode
-
- @QtCore.Slot(str)
- def _update_mode_change(self, mode):
- if gremlin.config.Configuration().initial_load_mode_tts:
- # output verbal notification if requested
- data = self._last_tts_data
- profile = gremlin.shared_state.current_profile
- if data.mode is None or data.profile is None or data.mode != mode or data.profile != profile:
- self._last_tts_data.mode = mode
- self._last_tts_data.profile = profile
- tts = gremlin.tts.TextToSpeech()
- rate = gremlin.config.Configuration().initial_load_rate_tts
- tts.speak(f"Mode change to {mode}", rate) # default rate is 100
-
- def TTSNotify(self, text):
- ''' outputs a notification only if TTS notifications are enabled and the profile/mode is different from the last message issued'''
- config = gremlin.config.Configuration()
- if config.initial_load_mode_tts:
- data = self._last_tts_data
- profile = gremlin.shared_state.current_profile
- mode = gremlin.shared_state.current_mode
- if data.mode is None or data.profile is None or data.mode != mode or data.profile != profile:
- self._last_tts_data.mode = mode
- self._last_tts_data.profile = profile
- rate = config.initial_load_rate_tts
- tts = gremlin.tts.TextToSpeech()
- tts.speak(text, rate) # default rate is 100
-
-
- def change_mode(self, new_mode, emit = True, force_update = False, tts = True, validate = True):
- """Changes the GremlinEx currently active mode.
-
- :param new_mode: the new mode to use
- :param emit: enables signal
- :param force_update: forces a mode change even if already in the mode
- :param validate: validates change mode, set to false to remove validation
- """
-
- import gremlin.ui.mode_device
-
-
- el = EventListener()
- try:
-
-
- gremlin.util.pushCursor()
-
- config = gremlin.config.Configuration()
- verbose = config.verbose
- current_profile = gremlin.shared_state.current_profile
- is_running = gremlin.shared_state.is_running
-
-
- if verbose:
- if is_running:
- syslog.debug(f"CHANGE MODE: (runtime) change mode to [{new_mode}] requested - active mode: [{gremlin.shared_state.runtime_mode}] current mode: [{gremlin.shared_state.current_mode}] profile '{current_profile.name}'")
- else:
- syslog.debug(f"CHANGE MODE: (edit time) change mode to [{new_mode}] requested - active mode: [{gremlin.shared_state.runtime_mode}] current mode: [{gremlin.shared_state.current_mode}] profile '{current_profile.name}'")
-
-
-
- if new_mode == self.current_mode and not force_update:
- # already in this mode
- return
-
- el.push_input_selection()
-
- profile_modes = current_profile.get_modes()
- mode_exists = new_mode in profile_modes
-
- if not mode_exists:
- for device in self.callbacks.values():
- if new_mode in device:
- mode_exists = True
-
- if not mode_exists:
- for device in self.osc_callbacks.values():
- if new_mode in device:
- mode_exists = True
-
- if not mode_exists:
- for device in self.midi_callbacks.values():
- if new_mode in device:
- mode_exists = True
-
- if not mode_exists:
- for device in self.latched_callbacks.values():
- if new_mode in device:
- mode_exists = True
-
- if not mode_exists:
- # import gremlin.config
- # verbose = gremlin.config.Configuration().verbose
- # if verbose:
- syslog.warning(
- f"CHANGE MODE: Mode Change Error: The mode \"{new_mode}\" does not exist or has no associated callbacks - profile '{current_profile.name}'"
- )
- return
-
- if is_running:
- # runtime event (prevents UI from reloading)
- # if verbose:
- # syslog.debug(f"EVENT: (runtime) change mode to [{new_mode}] requested - active mode: [{gremlin.shared_state.runtime_mode}] current mode: [{gremlin.shared_state.current_mode}] profile '{current_profile.name}'")
-
-
- if self.runtime_mode != new_mode or force_update:
- import gremlin.shared_state
- device_guid = gremlin.shared_state.mode_tab_guid
- mode_enter = gremlin.ui.mode_device.ModeInputModeType.ModeEnter
- mode_exit = gremlin.ui.mode_device.ModeInputModeType.ModeExit
- delay = 0.250 # delay in seconds between press/release events for mode control change
-
- # fire off any mode changes
- event_exit_pressed = Event(InputType.ModeControl,
- identifier = mode_exit,
- device_guid= device_guid,
- is_pressed=True,
- mode = self.runtime_mode)
- event_exit_released = Event(InputType.ModeControl,
- identifier = mode_exit,
- device_guid= device_guid,
- is_pressed=False,
- mode = self.runtime_mode)
-
- event_enter_pressed = Event(InputType.ModeControl,
- identifier = mode_enter,
- device_guid= device_guid,
- is_pressed=True,
- mode = new_mode)
- event_enter_released = Event(InputType.ModeControl,
- identifier = mode_enter,
- device_guid= device_guid,
- is_pressed=False,
- mode = new_mode)
-
- # fire mode change control for mode exit (press + release)
- m1_list, f1_list = self.execute_event(event_exit_pressed)
- exit_release = Timer(delay, lambda : self._execute_callbacks(event_exit_released, m1_list, f1_list))
- exit_release.start()
-
- if validate:
- result = self.runModeValidator(new_mode)
- if not result:
- syslog.warning(f"CHANGE MODE: {current_profile.name} - mode change request to {new_mode} not authorized by a module - request ignored")
- return
-
-
- self.previous_runtime_mode = self.runtime_mode
- gremlin.shared_state.runtime_mode = new_mode
- # remember the last mode for this profile
-
- current_profile.set_last_runtime_mode(self.runtime_mode)
- self.previous_runtime_mode = self.runtime_mode
- self.runtime_mode = new_mode
- if verbose: syslog.info(f"CHANGE MODE: [{current_profile.name}] - Runtime Mode switch to: {new_mode}")
- if emit:
- el.runtime_mode_changed.emit(new_mode)
-
- # tell other internal components the mode is changing (runtime only)
-
- el.runtime_mode_changed.emit(new_mode)
-
-
-
-
-
- # fire mode change for mode enter (press + release)
- m2_list, f2_list = self.execute_event(event_enter_pressed)
- enter_release = Timer(delay, lambda : self._execute_callbacks(event_enter_released, m2_list, f2_list))
- enter_release.start()
-
- else:
- # non-runtime
- assert new_mode,"new mode cannot be blank"
- if self.edit_mode != new_mode or force_update:
- gremlin.config.Configuration().set_profile_last_edit_mode(new_mode)
- gremlin.shared_state.edit_mode = new_mode
- self.edit_mode = new_mode
- syslog.debug(f"Profile: {current_profile.name} - Design time Mode switch to: {new_mode}")
- if emit:
- el.edit_mode_changed.emit(self.edit_mode)
-
- el.pop_input_selection()
-
- # update the status bar
- self.mode_status_update.emit()
-
- # update the selection
- device_guid, input_type, input_id = gremlin.config.Configuration().get_last_input()
- if input_type and input_id:
- el.select_input.emit(device_guid, input_type, input_id, False, True, False)
-
- # fire the UI update on change mode
- el.update_input_state.emit(device_guid) # force a UI widget status update
- finally:
- gremlin.util.popCursor()
-
-
-
-
-
-
-
- def resume(self):
- """Resumes the processing of callbacks."""
- self.process_callbacks = True
- self.is_active.emit(self.process_callbacks)
-
- def pause(self):
- """Stops the processing of callbacks."""
- self.process_callbacks = False
- self.is_active.emit(self.process_callbacks)
-
- def toggle_active(self):
- """Toggles the processing of callbacks on or off."""
- self.process_callbacks = not self.process_callbacks
- self.is_active.emit(self.process_callbacks)
-
- def clear(self):
- """Removes all attached callbacks."""
- self.callbacks = {}
- self.callback_key_map.clear()
- self.latched_callbacks = {}
- self.midi_callbacks = {}
- self.osc_callbacks = {}
-
-
- def execute_event(self, event : Event):
- """Processes a single event by passing it to all callbacks registered for this event.
-
- :param event the event to process
- """
-
- import gremlin.config
- import gremlin.keyboard
-
- # list of callbacks
- m_list = []
- f_list = []
-
- # mode to act on
- mode = event.mode if event.mode else self.runtime_mode
-
-
- verbose = gremlin.config.Configuration().verbose_mode_inputs
- #verbose = True
-
- if verbose and event.event_type != InputType.JoystickAxis:
- syslog.info(f"process event - mode [{mode}] event: {str(event)}")
-
-
-
- input_item = self._matching_input_item(mode, event)
- if input_item is not None and not input_item.enabled:
- # input item registered but not enabled - ignore inputs that aren't registered or could not be found (latched keys for example)
- if verbose: syslog.info(f"Event: input disabled {str(event)}")
- return
-
- # filter latched keyboard or mouse events
- if event.event_type in (InputType.Keyboard, InputType.KeyboardLatched, InputType.Mouse):
- verbose = gremlin.config.Configuration().verbose_mode_detailed
- data = event.data # holds keyboard state info
- if event.event_type == InputType.Mouse:
- verbose = gremlin.config.Configuration().verbose_mode_mouse
- if verbose:
- syslog.info(f"process keyboard event: {event}")
- syslog.info(f"\tKeyboard state data:")
- keys = list(data.keys())
- for key in keys:
- syslog.info(f"\t\t{gremlin.keyboard.KeyMap.keyid_tostring(key)} {data[key]}")
-
- items = self._matching_event_keys(event) # returns list of primary keys
- if items:
- if verbose:
- syslog.info(f"Matched keys for mode: [{mode}] event {event} pressed: {event.is_pressed} keys: {len(items)} ")
- for index, input_item in enumerate(items):
- syslog.info(f"\t[{index}]: {input_item.name}")
-
- for input_item in items:
- if verbose:
- syslog.info("-"*50)
- is_latched = True
- latch_key = None
- # print (data)
- latched_keys = [input_item.key]
- latched_keys.extend(input_item.latched_keys)
- if verbose: syslog.info(f"KEY: Checking latching: {len(latched_keys)} key(s)")
- for k in latched_keys:
- index = k.index_tuple()
- found = index in data.keys()
- if not found:
- # try the reverse translate
- r_index = gremlin.keyboard.KeyMap.reverse_translate(index)
- if r_index is not None:
- found = r_index in data.keys()
- if found:
- index = r_index
-
- state = data[index] if found else False
- if verbose:
- syslog.info(f"\tcheck latched key: {gremlin.keyboard.KeyMap.keyid_tostring(index)} {k.name} found: {found} state: {state} {'*****' if state else ''}")
- if not found:
- syslog.info(f"\t\t* Key not found *")
- is_latched = is_latched and state
-
- if verbose:
- syslog.info(f"\tLatched state: {is_latched}")
-
- if is_latched:
- latch_key = input_item.key
-
- if latch_key:
- #print (f"Found latched key: {latch_key}")
- m_list = self._matching_latched_callbacks(event, latch_key)
- if m_list:
- if verbose:
- trigger_line = "***** TRIGGER " + "*"*30
- syslog.info(trigger_line)
- syslog.info(f"\tmode: [{mode}] Found latched key: Check key {latch_key.name} callbacks: {len(m_list)} event: {event}")
- syslog.info(trigger_line)
- self._trigger_callbacks(m_list, event)
- return
- # else:
- # print (f"No callbacks found for: {latch_key}")
- verbose = gremlin.config.Configuration().verbose_mode_inputs
- else:
- if verbose:
- syslog.info("No matching events")
- return
-
- elif event.event_type ==InputType.Midi:
- m_list = self._matching_midi_callbacks(event)
- if verbose and not m_list: syslog.info(f"EVENT: [MIDI] no matching inputs for {str(event.identifier.message_key)} mode: {self.runtime_mode}")
-
- elif event.event_type == InputType.OpenSoundControl:
- m_list = self._matching_osc_callbacks(event)
- if verbose and not m_list: syslog.info(f"EVENT: [OSC] no matching inputs for {event.identifier.message_key} mode: {self.runtime_mode}")
-
-
- elif event.event_type in (InputType.JoystickAxis, InputType.JoystickButton, InputType.JoystickHat):
- if event.identifier == 2:
- pass
- m_list = self._matching_callbacks(event)
- f_list = self._matching_functors(event)
- if verbose and not m_list: syslog.info(f"EVENT: [Joystick] no matching inputs for {str(event.identifier)} mode: {self.runtime_mode}")
- else:
- # other inputs including control inputs
- verbose = gremlin.config.Configuration().verbose_mode_inputs
- m_list = self._matching_callbacks(event)
- f_list = self._matching_functors(event)
- if verbose and not m_list: syslog.info(f"EVENT: [Generic] no matching inputs for {str(event.identifier)} mode: {self.runtime_mode}")
-
-
- self._execute_callbacks(event, m_list, f_list)
-
- # if m_list:
- # if verbose:
- # syslog.info(f"TRIGGER: mode: [{mode}] callbacks: {len(m_list)} event: {event}")
- # self._trigger_callbacks(m_list, event)
-
- # if f_list:
- # if verbose:
- # syslog.info(f"TRIGGER: mode: [{mode}] functors: {len(f_list)} event: {event}")
- # self._trigger_functor_callbacks(f_list, event)
-
- # returns what was executed if they need to be retriggered
- return m_list, f_list
-
-
-
-
- def _trigger_callbacks(self, callbacks, event):
- ''' trigger regular callbacks '''
- #verbose = gremlin.config.Configuration().verbose'
- for cb in callbacks:
- try:
- # if verbose:
- # syslog.info(f"CALLBACK: execute start")
- cb(event)
- # if verbose:
- # syslog.info(f"CALLBACK: execute done")
- except Exception as ex:
- syslog.error(f"CALLBACK: error {ex}")
-
-
- def _trigger_functor_callbacks(self, functors, event : Event):
- ''' trigger functor callbacks '''
- #verbose = gremlin.config.Configuration().verbose'
- import gremlin.actions
- for functor in functors:
- try:
- functor.process_event(event, gremlin.actions.Value(event.value))
- except Exception as ex:
- syslog.error(f"FUNCTOR CALLBACK: error {ex}")
-
-
- def _execute_callbacks(self, event, m_list, f_list):
- ''' triggers callbacks '''
- if m_list:
- self._trigger_callbacks(m_list, event)
-
- if f_list:
- self._trigger_functor_callbacks(f_list, event)
-
-
-
- def _matching_midi_callbacks(self, event):
- ''' returns list of callbacks matching the event '''
- callback_list = []
- if event.event_type == InputType.Midi:
- key = event.identifier.message_key
- import gremlin.ui.midi_device
- if event.identifier.command == gremlin.ui.midi_device.MidiCommandType.SysEx:
- pass
- if event.device_guid in self.midi_callbacks:
- import gremlin.execution_graph
- ec = gremlin.execution_graph.ExecutionContext() # current execution context
- # search callbacks for mode hierarchy
- callback_list = ec.getCallbacks(self.midi_callbacks[event.device_guid], key, self.runtime_mode)
- # callback_list = self.midi_callbacks[event.device_guid].get(
- # self.runtime_mode, {}
- # ).get(key, [])
-
- # Filter events when the system is paused
- if not self.process_callbacks:
- return [c[0] for c in callback_list if c[1]]
- else:
- return [c[0] for c in callback_list]
-
-
-
- def _matching_osc_callbacks(self, event):
- ''' returns list of callbacks matching the event '''
- callback_list = []
- if event.event_type == InputType.OpenSoundControl:
- key = event.identifier.message_key
- if event.device_guid in self.osc_callbacks:
- import gremlin.execution_graph
- ec = gremlin.execution_graph.ExecutionContext() # current execution context
- # search callbacks for mode hierarchy
- callback_list = ec.getCallbacks(self.osc_callbacks[event.device_guid], key, self.runtime_mode)
-
- verbose = config.Configuration().verbose_mode_osc
- if verbose and not callback_list:
- # syslog = logging.getLogger("system")
- syslog.info(f"OSC: no callbacks found for key: [{key}] mode: [{self.runtime_mode}]")
-
- # Filter events when the system is paused
- if not self.process_callbacks:
- return [c[0] for c in callback_list if c[1]]
- else:
- return [c[0] for c in callback_list]
-
-
-
- def _matching_functors(self, event) -> list:
- ''' gets the list of matching functors to call when an event occurs '''
- functors_list = []
- device_guid = event.device_guid
- if device_guid in self.latched_functors:
- modes = gremlin.shared_state.current_profile.getModeHierarchy(self.runtime_mode)
- for mode in modes:
- if mode in self.latched_functors[device_guid].keys():
- key = event.callbackKey
- if key in self.latched_functors[device_guid][mode].keys():
- functors_list = self.latched_functors[device_guid][mode][key]
- if functors_list:
- break
- return functors_list
-
-
-
-
-
- def _matching_callbacks(self, event):
- """Returns the list of callbacks to execute in response to
- the provided event.
-
- :param event the event for which to search the matching
- callbacks
- :return a list of all callbacks registered and valid for the
- given event
- """
-
- config = gremlin.config.Configuration()
- verbose = config.verbose_mode_details # or config.verbose_mode_condition
-
- # Obtain callbacks matching the event
- callback_list = []
- key = event.callbackKey
- device_guid = event.device_guid
- if device_guid in self.callbacks:
- mode = self.runtime_mode
- if mode in self.callbacks[device_guid]:
- if key in self.callbacks[device_guid][mode]:
- callback_list = self.callbacks[device_guid][mode][key]
- if verbose:
- event = self.callback_key_map[key]
- self.dump_exectree(device_guid, mode, event)
-
- if verbose:
- syslog.debug(f"CALLBACK: device: {gremlin.shared_state.get_device_name(event.device_guid)} mode: {self.runtime_mode} found: {len(callback_list)}")
-
-
- # Filter events when the system is paused
- if callback_list:
- if not self.process_callbacks:
- return [c[0] for c in callback_list if c[1]]
- else:
- return [c[0] for c in callback_list]
-
-
- def _matching_latched_callbacks(self, event, key):
- callback_list = []
- if event.event_type in (InputType.KeyboardLatched, InputType.Keyboard):
- if event.device_guid in self.latched_callbacks:
- import gremlin.execution_graph
- ec = gremlin.execution_graph.ExecutionContext() # current execution context
- # search callbacks for mode hierarchy
- callback_list = ec.getCallbacks(self.latched_callbacks[event.device_guid], key, self.runtime_mode)
-
- # Filter events when the system is paused
- if not self.process_callbacks:
- return [c[0] for c in callback_list if c[1]]
- else:
- return [c[0] for c in callback_list]
-
- def _install_plugins(self, callback):
- """Installs the current plugins into the given callback.
-
- :param callback the callback function to install the plugins into
- :return new callback with plugins installed
- """
- signature = inspect.signature(callback).parameters
- for keyword, plugin in self.plugins.items():
- if keyword in signature:
- callback = plugin.install(callback, functools.partial)
- return callback
+ """Listens to the inputs from multiple different input devices."""
+
+ mode_status_update = QtCore.Signal() # tell the UI to update the mode status bar
+
+ # signal emitted when the profile is changed
+ profile_changed = QtCore.Signal(str)
+
+ # Signal emitted when the application is pause / resumed
+ is_active = QtCore.Signal(bool)
+
+ last_action_changed = QtCore.Signal(
+ object, str
+ ) # fires when the action changes in the selector (drop_down, name)
+ last_container_changed = QtCore.Signal(
+ object, str
+ ) # fires when the action changes in the selector (drop_down, name)
+
+ def __init__(self):
+ """Initializes the EventHandler instance."""
+ QtCore.QObject.__init__(self)
+ self.plugins = {}
+ self._mode_validator_callbacks = {} # list of validators (callbacks) that return a boolean True if the mode change can occur - signature must be callable(str)->bool
+ self._last_tts_data = (
+ TTSNotifyData()
+ ) # last mode that triggered a TTS verbal notice
+ el = gremlin.event_handler.EventListener()
+ el.profile_start.connect(self._profile_start)
+ el.profile_stop.connect(self._profile_stop)
+ el.runtime_mode_changed.connect(self._update_mode_change)
+
+ self.reset()
+
+ @QtCore.Slot()
+ def _profile_start(self):
+ self._update_mode_change(gremlin.shared_state.runtime_mode)
+
+ @QtCore.Slot()
+ def _profile_stop(self):
+ self._last_tts_notify = None
+ self._last_tts_notify_time = None
+
+ def registerModeValidator(self, callback):
+ assert callable(callback)
+ self._mode_validator_callbacks[callback] = callback
+
+ def unregisterModeValidator(self, callback):
+ if callback in self._mode_validator_callbacks:
+ del self._mode_validator_callbacks[callback]
+
+ def clearModeValidator(self):
+ self._mode_validator_callbacks.clear()
+
+ def runModeValidator(self, mode):
+ """runs through all current validators to see if a mode change can occur"""
+ result = True # assume we can
+ for callback in self._mode_validator_callbacks:
+ result = result and callback(mode)
+ if not result:
+ break
+
+ return result
+
+ def reset(self):
+ config = gremlin.config.Configuration()
+ verbose = config.verbose
+ if verbose:
+ syslog.info("EventHandler: reset()")
+
+ self.process_callbacks = True
+ self.callbacks = {}
+ self.callback_key_map = {} # map of event callbackKey to event
+ self.input_item_map = {} # map of input items keyed by device_guid, mode, input_type, input_id
+ self.latched_events = {}
+ self.latched_callbacks = {}
+ self.midi_callbacks = {}
+ self.osc_callbacks = {}
+ self._event_lookup = {}
+ self.latched_functors = {}
+ self.experimental = config.experimental
+
+ @property
+ def runtime_mode(self):
+ """Returns the currently active mode.
+
+ :return name of the currently active mode
+ """
+ return gremlin.shared_state.runtime_mode
+
+ @runtime_mode.setter
+ def runtime_mode(self, value):
+ gremlin.shared_state.runtime_mode = value
+
+ @property
+ def edit_mode(self):
+ return gremlin.shared_state.edit_mode
+
+ @edit_mode.setter
+ def edit_mode(self, value):
+ gremlin.shared_state.edit_mode = value
+
+ @property
+ def current_mode(self):
+ """gets the current mode based on state"""
+ return gremlin.shared_state.current_mode
+
+ @property
+ def previous_runtime_mode(self):
+ """returns the previous mode"""
+ return gremlin.shared_state.previous_runtime_mode
+
+ @previous_runtime_mode.setter
+ def previous_runtime_mode(self, value):
+ """sets the active mode"""
+ gremlin.shared_state.previous_runtime_mode = value
+
+ def add_plugin(self, plugin):
+ """Adds a new plugin to be attached to event callbacks.
+
+ :param plugin the plugin to add
+ """
+ # Do not add the same type of plugin multiple times
+ if plugin.keyword not in self.plugins:
+ self.plugins[plugin.keyword] = plugin
+
+ def dump_exectree(self, device_guid, mode, event):
+ """outputs the execution tree to the log"""
+ from types import FunctionType, MethodType
+
+ verbose = gremlin.config.Configuration().verbose
+ if not verbose:
+ return
+
+ device_name = gremlin.shared_state.get_device_name(device_guid)
+
+ for callbacks in self.callbacks[device_guid][mode][event.callbackKey]:
+ for callback in callbacks:
+ if not hasattr(callback, "execution_graph"):
+ syslog.debug(
+ f"\tDevice ID: {device_name} mode: {mode} event: {event} - skip callback - missing execution graph - don't know how to handle {type(callback)} *********"
+ )
+ continue
+
+ for callback_functor in callback.execution_graph.functors:
+ if hasattr(callback_functor, "action_set"):
+ for functor in callback_functor.action_set.functors:
+ action_data = (
+ functor.action_data
+ if hasattr(functor, "action_data")
+ else None
+ )
+ syslog.debug(
+ f"\tDevice ID: {device_name} mode: {mode} event: {event} hash: {hash(event):X} type: {type(functor)}"
+ )
+ if action_data:
+ # dump member variables only
+ syslog.debug("\t\tData block:")
+ for attr in dir(action_data):
+ if not attr.startswith("_"):
+ item = getattr(action_data, attr)
+
+ if not (
+ isinstance(item, FunctionType)
+ or isinstance(item, MethodType)
+ or inspect.isabstract(item)
+ or inspect.isclass(item)
+ ):
+ syslog.debug(f"\t\t\t{attr}: {item}")
+ else:
+ syslog.debug(
+ f"\tFunctor '{type(callback_functor).__name__} does not define an action set"
+ )
+
+ def dump_callbacks(self):
+ # dump latched events
+ import gremlin.ui.keyboard_device
+ import gremlin.shared_state
+
+
+ syslog.debug("------------ Latched Events ----------------")
+ for device_guid in self.latched_events.keys():
+ device_name = gremlin.shared_state.get_device_name(device_guid)
+ for mode in self.latched_events[device_guid].keys():
+ for key_pair in self.latched_events[device_guid][mode]:
+ identifier = self.latched_events[device_guid][mode][key_pair]
+ if isinstance(
+ identifier, gremlin.ui.keyboard_device.KeyboardInputItem
+ ):
+ if isinstance(key_pair, tuple):
+ scan_code, is_extended = key_pair
+ key_data = (
+ f"scan code: 0x{scan_code:X} extended: {is_extended}"
+ )
+ else:
+ key_data = str(key_pair)
+ syslog.debug(
+ f"\tDevice ID: {device_name} mode: {mode} pair: {key_data} data: {identifier.to_string()}"
+ )
+
+ syslog.debug("------------ Execution callbacks ----------------")
+ for device_guid in self.callbacks.keys():
+ for mode in self.callbacks[device_guid].keys():
+ for key in self.callbacks[device_guid][mode]:
+ event = self.callback_key_map[key]
+ self.dump_exectree(device_guid, mode, event)
+
+ def add_latched_functor(self, device_guid, mode, event, functor):
+ """registers an extra latched functor on inputs if a functor uses multiple inputs"""
+ # regular event
+ if device_guid not in self.latched_functors:
+ self.latched_functors[device_guid] = {}
+ if mode not in self.latched_functors[device_guid]:
+ self.latched_functors[device_guid][mode] = {}
+ key = event.callbackKey
+ if key not in self.latched_functors[device_guid][mode]:
+ self.latched_functors[device_guid][mode][key] = []
+ self.latched_functors[device_guid][mode][key].append(functor)
+
+ def _matching_input_item(self, mode, event):
+ """gets the matching input item from the event"""
+
+ device_guid = event.device_guid
+ input_type = event.event_type
+ if input_type == InputType.Keyboard:
+ input_type = InputType.KeyboardLatched
+ if input_type == InputType.KeyboardLatched:
+ magic = json.dumps(event.identifier)
+ else:
+ magic = event.identifier
+
+ if device_guid not in self.input_item_map:
+ return None
+ if mode not in self.input_item_map[device_guid]:
+ return None
+ if input_type not in self.input_item_map[device_guid][mode]:
+ return None
+ if magic in self.input_item_map[device_guid][mode][input_type]:
+ return self.input_item_map[device_guid][mode][input_type][magic]
+
+ # syslog.info(f"No match: {input_type} {magic}")
+ # for key in self.input_item_map[device_guid][mode][input_type].keys():
+ # syslog.info(f"\t{key}")
+ return None
+
+ def registerInputItem(self, mode: str, input_item):
+ """registers an input item with the event handler"""
+ item: gremlin.base_profile.InputItem = input_item
+ device_guid = item.device_guid
+ input_type = item.input_type
+ if input_type == InputType.Keyboard:
+ input_type = InputType.KeyboardLatched
+
+ if input_type == InputType.KeyboardLatched:
+ # use the key sequence as the magic key
+ magic = json.dumps(input_item.input_id.key_tuple)
+ else:
+ magic = item.input_id
+
+ if device_guid not in self.input_item_map:
+ self.input_item_map[device_guid] = {}
+ if mode not in self.input_item_map[device_guid]:
+ self.input_item_map[device_guid][mode] = {}
+ if input_type not in self.input_item_map[device_guid][mode]:
+ self.input_item_map[device_guid][mode][input_type] = {}
+ self.input_item_map[device_guid][mode][input_type][magic] = input_item
+
+ verbose = gremlin.config.Configuration().verbose_mode_inputs
+ if verbose:
+ syslog.info(
+ f"Register InputItem: {input_item.display_name} mode {mode} {input_type} magic: {magic}"
+ )
+
+ def add_callback(
+ self, device_guid, mode, event, callback, permanent=False, node=None
+ ):
+ """Installs the provided callback for the given event.
+
+ :param device_guid the GUID of the device the callback is
+ associated with
+ :param mode the mode the callback belongs to
+ :param event the event for which to install the callback
+ :param callback the callback function to link to the provided
+ event
+ :param permanent if True the callback is always active even
+ if the system is paused
+ :node: the execution tree node
+ """
+ import gremlin.config
+ import gremlin.ui.keyboard_device
+ import gremlin.keyboard
+
+ assert callable(callback)
+
+ if event:
+ if event.event_type in (InputType.Keyboard, InputType.KeyboardLatched):
+ verbose = gremlin.config.Configuration().verbose_mode_keyboard
+ # keyboard latched event
+ identifier = event.identifier
+ primary_key = identifier.key
+
+ # verbose = True
+
+ # if the key can latch with multiple primary keys, build the table of all combinations
+ key_list = [primary_key]
+ if primary_key.is_latched:
+ # multiple keys
+ key_list.extend(primary_key._latched_keys)
+
+ for key in key_list:
+ # the events will arrive as keyboard events - in any order - this makes sure latching is checked regardless of the order of key presses
+
+ virtual_code = key.virtual_code
+ keyid_source = key.index_tuple() # use the scan code for now
+ # index = virtual_code if virtual_code > 0 else keyid
+ keyid, _ = gremlin.keyboard.KeyMap.translate(keyid_source)
+
+ if device_guid not in self.latched_events.keys():
+ self.latched_events[device_guid] = {}
+
+ if mode not in self.latched_events[device_guid].keys():
+ self.latched_events[device_guid][mode] = {}
+ if keyid not in self.latched_events[device_guid][mode].keys():
+ self.latched_events[device_guid][mode][keyid] = []
+ self.latched_events[device_guid][mode][keyid].append(identifier)
+ if verbose:
+ syslog.info(
+ f"Key latch registered by guid {device_guid} mode: {mode} vk: {virtual_code} (0x{virtual_code:X}) source keyid: {gremlin.keyboard.KeyMap.keyid_tostring(keyid_source)} -> translated keyId: {gremlin.keyboard.KeyMap.keyid_tostring(keyid)} name: {key.name} -> {identifier.display_name}"
+ )
+
+ if device_guid not in self.latched_callbacks.keys():
+ self.latched_callbacks[device_guid] = {}
+ if mode not in self.latched_callbacks[device_guid].keys():
+ self.latched_callbacks[device_guid][mode] = {}
+ if key not in self.latched_callbacks[device_guid][mode]:
+ self.latched_callbacks[device_guid][mode][primary_key] = []
+ data = self.latched_callbacks[device_guid][mode][primary_key]
+ data.append((self._install_plugins(callback), permanent))
+ return
+
+ elif event.event_type == InputType.Midi:
+ # MIDI event
+ verbose = gremlin.config.Configuration().verbose_mode_midi
+ midi_input = event.identifier
+ key = midi_input.message_key
+ if device_guid not in self.midi_callbacks.keys():
+ self.midi_callbacks[device_guid] = {}
+ if mode not in self.midi_callbacks[device_guid].keys():
+ self.midi_callbacks[device_guid][mode] = {}
+ if key not in self.midi_callbacks[device_guid][mode]:
+ self.midi_callbacks[device_guid][mode][key] = []
+ data = self.midi_callbacks[device_guid][mode][key]
+ data.append((self._install_plugins(callback), permanent))
+ if verbose:
+ syslog.info(f"MIDI: register callback {mode} {key}")
+
+ elif event.event_type == InputType.OpenSoundControl:
+ # OSC event
+ verbose = gremlin.config.Configuration().verbose
+ osc_input = event.identifier
+ key = osc_input.message_key
+ if device_guid not in self.osc_callbacks.keys():
+ self.osc_callbacks[device_guid] = {}
+ if mode not in self.osc_callbacks[device_guid].keys():
+ self.osc_callbacks[device_guid][mode] = {}
+ if key not in self.osc_callbacks[device_guid][mode]:
+ self.osc_callbacks[device_guid][mode][key] = []
+ data = self.osc_callbacks[device_guid][mode][key]
+ data.append((self._install_plugins(callback), permanent))
+
+ else:
+ # regular event - events are stored by the event key
+ if device_guid not in self.callbacks:
+ self.callbacks[device_guid] = {}
+ if mode not in self.callbacks[device_guid]:
+ self.callbacks[device_guid][mode] = {}
+ key = event.callbackKey
+ if key not in self.callbacks[device_guid][mode]:
+ self.callbacks[device_guid][mode][key] = []
+ self.callback_key_map[key] = event
+ self.callbacks[device_guid][mode][key].append(
+ (self._install_plugins(callback), permanent)
+ )
+
+ def _matching_event_keys(self, event):
+ """gets the list of latched keys for this event"""
+ if event.event_type not in (
+ InputType.Keyboard,
+ InputType.KeyboardLatched,
+ InputType.Mouse,
+ ):
+ # not a keyboard event
+ return []
+ import gremlin.config
+ import gremlin.keyboard
+
+ # convert mouse events to keyboard event
+ if event.event_type == InputType.Mouse:
+ from gremlin.ui.keyboard_device import KeyboardDeviceTabWidget
+
+ device_guid = KeyboardDeviceTabWidget.device_guid
+
+ mouse_button = event.identifier
+ # convert the mouse button to the virtual scan code we use for mouse events
+ index = (mouse_button.value + 0x1000, False)
+ verbose = gremlin.config.Configuration().v
+ if verbose:
+ syslog.info(
+ f"matching mouse event {event.identifier} to {gremlin.keyboard.KeyMap.keyid_tostring(index)}"
+ )
+ else:
+ verbose = gremlin.config.Configuration().verbose_mode_keyboard
+ device_guid = event.device_guid
+ # index = event.virtual_code if event.virtual_code > 0 else event.identifier # this is (scan_code, is_extended)
+ index, _ = gremlin.keyboard.KeyMap.translate(event.identifier)
+ if verbose:
+ syslog.info(
+ f"matching key event {event.identifier} to {gremlin.keyboard.KeyMap.keyid_tostring(index)}"
+ )
+
+ # event_key = Key(scan_code = identifier[0], is_extended = identifier[1], is_mouse = is_mouse, virtual_code= virtual_code)
+ input_items = []
+
+ if device_guid in self.latched_events:
+ # print (f"found guid: {device_guid}")
+ data = self.latched_events[event.device_guid]
+ if self.runtime_mode in data.keys():
+ data = data[self.runtime_mode]
+ matching_keys = []
+ if index in data.keys():
+ # print ("found identifier")
+ matching_keys = data[index]
+ if not matching_keys:
+ index_ex = (index[0], not index[1])
+ if index_ex in data.keys():
+ matching_keys = data[index_ex]
+
+ for input_item in matching_keys:
+ # key = input_item.key
+ input_items.append(input_item)
+
+ if verbose:
+ syslog.info(f"KEY: found {len(input_items)} matching items")
+ return input_items
+
+ return []
+
+ def build_event_lookup(self, inheritance_tree):
+ """Builds the lookup table linking event to callback.
+
+ This takes mode inheritance into account.
+
+ :param inheritance_tree the tree of parent and children in the
+ inheritance structure
+ """
+ # Propagate events from parent to children if the children lack
+ # handlers for the available events
+ callbacks_list = [self.callbacks, self.latched_callbacks, self.latched_events]
+
+ for parent, children in inheritance_tree.items():
+ # Each device is treated separately
+ for callback_items in callbacks_list:
+ for device_guid in callback_items:
+ # Only attempt to copy handlers if we have any available in
+ # the parent mode
+ if parent in callback_items[device_guid]:
+ device_cb = callback_items[device_guid]
+ parent_cb = device_cb[parent]
+ # Copy the handlers into each child mode, unless they
+ # have their own handlers already defined
+ for child in children:
+ if child not in device_cb:
+ device_cb[child] = {}
+ for event, callbacks in parent_cb.items():
+ if isinstance(event, gremlin.event_handler.Event):
+ key = event.callbackKey
+ else:
+ key = event
+ if key not in device_cb[child]:
+ device_cb[child][key] = callbacks
+
+ # Recurse until we've dealt with all modes
+ self.build_event_lookup(children)
+
+ def change_profile(self, new_profile):
+ """requests a profile load"""
+ if new_profile != gremlin.shared_state.current_profile:
+ self.profile_change.emit(new_profile)
+
+ def set_mode(self, new_mode):
+ """sets the edit or runtime mode based on the state"""
+ assert new_mode, "Mode cannot be blank"
+ if gremlin.shared_state.is_running:
+ gremlin.shared_state.runtime_mode = new_mode
+ else:
+ gremlin.shared_state.edit_mode = new_mode
+
+ def set_runtime_mode(self, new_mode):
+ """sets the active runtime mode"""
+ assert new_mode, "Mode cannot be blank"
+ gremlin.shared_state.runtime_mode = new_mode
+
+ def set_edit_mode(self, new_mode):
+ """sets the active edit mode"""
+ assert new_mode, "Mode cannot be blank"
+ gremlin.shared_state.edit_mode = new_mode
+
+ @QtCore.Slot(str)
+ def _update_mode_change(self, mode):
+ if gremlin.config.Configuration().initial_load_mode_tts:
+ # output verbal notification if requested
+ data = self._last_tts_data
+ profile = gremlin.shared_state.current_profile
+ if (
+ data.mode is None
+ or data.profile is None
+ or data.mode != mode
+ or data.profile != profile
+ ):
+ self._last_tts_data.mode = mode
+ self._last_tts_data.profile = profile
+ tts = gremlin.tts.TextToSpeech()
+ rate = gremlin.config.Configuration().initial_load_rate_tts
+ tts.speak(f"Mode change to {mode}", rate) # default rate is 100
+
+ def TTSNotify(self, text):
+ """outputs a notification only if TTS notifications are enabled and the profile/mode is different from the last message issued"""
+ config = gremlin.config.Configuration()
+ if config.initial_load_mode_tts:
+ data = self._last_tts_data
+ profile = gremlin.shared_state.current_profile
+ mode = gremlin.shared_state.current_mode
+ if (
+ data.mode is None
+ or data.profile is None
+ or data.mode != mode
+ or data.profile != profile
+ ):
+ self._last_tts_data.mode = mode
+ self._last_tts_data.profile = profile
+ rate = config.initial_load_rate_tts
+ tts = gremlin.tts.TextToSpeech()
+ tts.speak(text, rate) # default rate is 100
+
+ def change_mode(self, new_mode, emit=True, force_update=False, tts=True):
+ """Changes the GremlinEx currently active mode.
+
+ :param new_mode the new mode to use
+ """
+
+ import gremlin.ui.mode_device
+
+ el = EventListener()
+ try:
+ gremlin.util.pushCursor()
+
+ config = gremlin.config.Configuration()
+ verbose = config.verbose
+ current_profile = gremlin.shared_state.current_profile
+ is_running = gremlin.shared_state.is_running
+
+ if verbose:
+ if is_running:
+ syslog.debug(
+ f"CHANGE MODE: (runtime) change mode to [{new_mode}] requested - active mode: [{gremlin.shared_state.runtime_mode}] current mode: [{gremlin.shared_state.current_mode}] profile '{current_profile.name}'"
+ )
+ else:
+ syslog.debug(
+ f"CHANGE MODE: (edit time) change mode to [{new_mode}] requested - active mode: [{gremlin.shared_state.runtime_mode}] current mode: [{gremlin.shared_state.current_mode}] profile '{current_profile.name}'"
+ )
+
+ if new_mode == self.current_mode and not force_update:
+ # already in this mode
+ return
+
+ el.push_input_selection()
+
+ profile_modes = current_profile.get_modes()
+ mode_exists = new_mode in profile_modes
+
+ if not mode_exists:
+ for device in self.callbacks.values():
+ if new_mode in device:
+ mode_exists = True
+
+ if not mode_exists:
+ for device in self.osc_callbacks.values():
+ if new_mode in device:
+ mode_exists = True
+
+ if not mode_exists:
+ for device in self.midi_callbacks.values():
+ if new_mode in device:
+ mode_exists = True
+
+ if not mode_exists:
+ for device in self.latched_callbacks.values():
+ if new_mode in device:
+ mode_exists = True
+
+ if not mode_exists:
+ # import gremlin.config
+ # verbose = gremlin.config.Configuration().verbose
+ # if verbose:
+ syslog.warning(
+ f"CHANGE MODE: Mode Change Error: The mode \"{new_mode}\" does not exist or has no associated callbacks - profile '{current_profile.name}'"
+ )
+ return
+
+ if is_running:
+ # runtime event (prevents UI from reloading)
+ # if verbose:
+ # syslog.debug(f"EVENT: (runtime) change mode to [{new_mode}] requested - active mode: [{gremlin.shared_state.runtime_mode}] current mode: [{gremlin.shared_state.current_mode}] profile '{current_profile.name}'")
+
+ if self.runtime_mode != new_mode or force_update:
+ import gremlin.shared_state
+
+ device_guid = gremlin.shared_state.mode_tab_guid
+ mode_enter = gremlin.ui.mode_device.ModeInputModeType.ModeEnter
+ mode_exit = gremlin.ui.mode_device.ModeInputModeType.ModeExit
+ delay = 0.250 # delay in seconds between press/release events for mode control change
+
+ # fire off any mode changes
+ event_exit_pressed = Event(
+ InputType.ModeControl,
+ identifier=mode_exit,
+ device_guid=device_guid,
+ is_pressed=True,
+ mode=self.runtime_mode,
+ )
+ event_exit_released = Event(
+ InputType.ModeControl,
+ identifier=mode_exit,
+ device_guid=device_guid,
+ is_pressed=False,
+ mode=self.runtime_mode,
+ )
+
+ event_enter_pressed = Event(
+ InputType.ModeControl,
+ identifier=mode_enter,
+ device_guid=device_guid,
+ is_pressed=True,
+ mode=new_mode,
+ )
+ event_enter_released = Event(
+ InputType.ModeControl,
+ identifier=mode_enter,
+ device_guid=device_guid,
+ is_pressed=False,
+ mode=new_mode,
+ )
+
+ # fire mode change control for mode exit (press + release)
+ m1_list, f1_list = self.execute_event(event_exit_pressed)
+ exit_release = Timer(
+ delay,
+ lambda: self._execute_callbacks(
+ event_exit_released, m1_list, f1_list
+ ),
+ )
+ exit_release.start()
+
+ result = self.runModeValidator(new_mode)
+ if not result:
+ syslog.warning(
+ f"CHANGE MODE: {current_profile.name} - mode change request to {new_mode} not authorized by a module - request ignored"
+ )
+ return
+
+ self.previous_runtime_mode = self.runtime_mode
+ gremlin.shared_state.runtime_mode = new_mode
+ # remember the last mode for this profile
+
+ current_profile.set_last_runtime_mode(self.runtime_mode)
+ self.previous_runtime_mode = self.runtime_mode
+ self.runtime_mode = new_mode
+ if verbose:
+ syslog.info(
+ f"CHANGE MODE: [{current_profile.name}] - Runtime Mode switch to: {new_mode}"
+ )
+ if emit:
+ el.runtime_mode_changed.emit(new_mode)
+
+ # tell other internal components the mode is changing (runtime only)
+
+ el.runtime_mode_changed.emit(new_mode)
+
+ # fire mode change for mode enter (press + release)
+ m2_list, f2_list = self.execute_event(event_enter_pressed)
+ enter_release = Timer(
+ delay,
+ lambda: self._execute_callbacks(
+ event_enter_released, m2_list, f2_list
+ ),
+ )
+ enter_release.start()
+
+ else:
+ # non-runtime
+ assert new_mode, "new mode cannot be blank"
+ if self.edit_mode != new_mode or force_update:
+ gremlin.config.Configuration().set_profile_last_edit_mode(new_mode)
+ gremlin.shared_state.edit_mode = new_mode
+ self.edit_mode = new_mode
+ syslog.debug(
+ f"Profile: {current_profile.name} - Design time Mode switch to: {new_mode}"
+ )
+ if emit:
+ el.edit_mode_changed.emit(self.edit_mode)
+
+ el.pop_input_selection()
+
+ # update the status bar
+ self.mode_status_update.emit()
+
+ # update the selection
+ device_guid, input_type, input_id = (
+ gremlin.config.Configuration().get_last_input()
+ )
+ if input_type and input_id:
+ el.select_input.emit(
+ device_guid, input_type, input_id, False, True, False
+ )
+
+ # fire the UI update on change mode
+ el.update_input_state.emit(device_guid) # force a UI widget status update
+ finally:
+ gremlin.util.popCursor()
+
+ def resume(self):
+ """Resumes the processing of callbacks."""
+ self.process_callbacks = True
+ self.is_active.emit(self.process_callbacks)
+
+ def pause(self):
+ """Stops the processing of callbacks."""
+ self.process_callbacks = False
+ self.is_active.emit(self.process_callbacks)
+
+ def toggle_active(self):
+ """Toggles the processing of callbacks on or off."""
+ self.process_callbacks = not self.process_callbacks
+ self.is_active.emit(self.process_callbacks)
+
+ def clear(self):
+ """Removes all attached callbacks."""
+ self.callbacks = {}
+ self.callback_key_map.clear()
+ self.latched_callbacks = {}
+ self.midi_callbacks = {}
+ self.osc_callbacks = {}
+
+ def execute_event(self, event: Event):
+ """Processes a single event by passing it to all callbacks registered for this event.
+
+ :param event the event to process
+ """
+
+ import gremlin.config
+ import gremlin.keyboard
+
+ # list of callbacks
+ m_list = []
+ f_list = []
+
+ # mode to act on
+ mode = event.mode if event.mode else self.runtime_mode
+
+ verbose = gremlin.config.Configuration().verbose_mode_inputs
+ # verbose = True
+
+ if verbose and event.event_type != InputType.JoystickAxis:
+ syslog.info(f"process event - mode [{mode}] event: {str(event)}")
+
+ input_item = self._matching_input_item(mode, event)
+ if input_item is not None and not input_item.enabled:
+ # input item registered but not enabled - ignore inputs that aren't registered or could not be found (latched keys for example)
+ if verbose:
+ syslog.info(f"Event: input disabled {str(event)}")
+ return
+
+ # filter latched keyboard or mouse events
+ if event.event_type in (
+ InputType.Keyboard,
+ InputType.KeyboardLatched,
+ InputType.Mouse,
+ ):
+ verbose = gremlin.config.Configuration().verbose_mode_detailed
+ data = event.data # holds keyboard state info
+ if event.event_type == InputType.Mouse:
+ verbose = gremlin.config.Configuration().verbose_mode_mouse
+ if verbose:
+ syslog.info(f"process keyboard event: {event}")
+ syslog.info("\tKeyboard state data:")
+ keys = list(data.keys())
+ for key in keys:
+ syslog.info(
+ f"\t\t{gremlin.keyboard.KeyMap.keyid_tostring(key)} {data[key]}"
+ )
+
+ items = self._matching_event_keys(event) # returns list of primary keys
+ if items:
+ if verbose:
+ syslog.info(
+ f"Matched keys for mode: [{mode}] event {event} pressed: {event.is_pressed} keys: {len(items)} "
+ )
+ for index, input_item in enumerate(items):
+ syslog.info(f"\t[{index}]: {input_item.name}")
+
+ for input_item in items:
+ if verbose:
+ syslog.info("-" * 50)
+ is_latched = True
+ latch_key = None
+ # print (data)
+ latched_keys = [input_item.key]
+ latched_keys.extend(input_item.latched_keys)
+ if verbose:
+ syslog.info(
+ f"KEY: Checking latching: {len(latched_keys)} key(s)"
+ )
+ for k in latched_keys:
+ index = k.index_tuple()
+ found = index in data.keys()
+ if not found:
+ # try the reverse translate
+ r_index = gremlin.keyboard.KeyMap.reverse_translate(index)
+ if r_index is not None:
+ found = r_index in data.keys()
+ if found:
+ index = r_index
+
+ state = data[index] if found else False
+ if verbose:
+ syslog.info(
+ f"\tcheck latched key: {gremlin.keyboard.KeyMap.keyid_tostring(index)} {k.name} found: {found} state: {state} {'*****' if state else ''}"
+ )
+ if not found:
+ syslog.info("\t\t* Key not found *")
+ is_latched = is_latched and state
+
+ if verbose:
+ syslog.info(f"\tLatched state: {is_latched}")
+
+ if is_latched:
+ latch_key = input_item.key
+
+ if latch_key:
+ # print (f"Found latched key: {latch_key}")
+ m_list = self._matching_latched_callbacks(event, latch_key)
+ if m_list:
+ if verbose:
+ trigger_line = "***** TRIGGER " + "*" * 30
+ syslog.info(trigger_line)
+ syslog.info(
+ f"\tmode: [{mode}] Found latched key: Check key {latch_key.name} callbacks: {len(m_list)} event: {event}"
+ )
+ syslog.info(trigger_line)
+ self._trigger_callbacks(m_list, event)
+ return
+ # else:
+ # print (f"No callbacks found for: {latch_key}")
+ verbose = gremlin.config.Configuration().verbose_mode_inputs
+ else:
+ if verbose:
+ syslog.info("No matching events")
+ return
+
+ elif event.event_type == InputType.Midi:
+ m_list = self._matching_midi_callbacks(event)
+ if verbose and not m_list:
+ syslog.info(
+ f"EVENT: [MIDI] no matching inputs for {str(event.identifier.message_key)} mode: {self.runtime_mode}"
+ )
+
+ elif event.event_type == InputType.OpenSoundControl:
+ m_list = self._matching_osc_callbacks(event)
+ if verbose and not m_list:
+ syslog.info(
+ f"EVENT: [OSC] no matching inputs for {event.identifier.message_key} mode: {self.runtime_mode}"
+ )
+
+ elif event.event_type in (
+ InputType.JoystickAxis,
+ InputType.JoystickButton,
+ InputType.JoystickHat,
+ ):
+ m_list = self._matching_callbacks(event)
+ f_list = self._matching_functors(event)
+ if verbose and not m_list:
+ syslog.info(
+ f"EVENT: [Joystick] no matching inputs for {str(event.identifier)} mode: {self.runtime_mode}"
+ )
+ else:
+ # other inputs including control inputs
+ verbose = gremlin.config.Configuration().verbose_mode_inputs
+ m_list = self._matching_callbacks(event)
+ f_list = self._matching_functors(event)
+ if verbose and not m_list:
+ syslog.info(
+ f"EVENT: [Generic] no matching inputs for {str(event.identifier)} mode: {self.runtime_mode}"
+ )
+
+ self._execute_callbacks(event, m_list, f_list)
+
+ # if m_list:
+ # if verbose:
+ # syslog.info(f"TRIGGER: mode: [{mode}] callbacks: {len(m_list)} event: {event}")
+ # self._trigger_callbacks(m_list, event)
+
+ # if f_list:
+ # if verbose:
+ # syslog.info(f"TRIGGER: mode: [{mode}] functors: {len(f_list)} event: {event}")
+ # self._trigger_functor_callbacks(f_list, event)
+
+ # returns what was executed if they need to be retriggered
+ return m_list, f_list
+
+ def _trigger_callbacks(self, callbacks, event):
+ """trigger regular callbacks"""
+ # verbose = gremlin.config.Configuration().verbose'
+ for cb in callbacks:
+ try:
+ # if verbose:
+ # syslog.info(f"CALLBACK: execute start")
+ cb(event)
+ # if verbose:
+ # syslog.info(f"CALLBACK: execute done")
+ except Exception as ex:
+ syslog.error(f"CALLBACK: error {ex}")
+
+ def _trigger_functor_callbacks(self, functors, event: Event):
+ """trigger functor callbacks"""
+ # verbose = gremlin.config.Configuration().verbose'
+ import gremlin.actions
+
+ for functor in functors:
+ try:
+ functor.process_event(event, gremlin.actions.Value(event.value))
+ except Exception as ex:
+ syslog.error(f"FUNCTOR CALLBACK: error {ex}")
+
+ def _execute_callbacks(self, event, m_list, f_list):
+ """triggers callbacks"""
+ if m_list:
+ self._trigger_callbacks(m_list, event)
+
+ if f_list:
+ self._trigger_functor_callbacks(f_list, event)
+
+ def _matching_midi_callbacks(self, event):
+ """returns list of callbacks matching the event"""
+ callback_list = []
+ if event.event_type == InputType.Midi:
+ key = event.identifier.message_key
+ import gremlin.ui.midi_device
+
+ if event.identifier.command == gremlin.ui.midi_device.MidiCommandType.SysEx:
+ pass
+ if event.device_guid in self.midi_callbacks:
+ import gremlin.execution_graph
+
+ ec = (
+ gremlin.execution_graph.ExecutionContext()
+ ) # current execution context
+ # search callbacks for mode hierarchy
+ callback_list = ec.getCallbacks(
+ self.midi_callbacks[event.device_guid], key, self.runtime_mode
+ )
+ # callback_list = self.midi_callbacks[event.device_guid].get(
+ # self.runtime_mode, {}
+ # ).get(key, [])
+
+ # Filter events when the system is paused
+ if not self.process_callbacks:
+ return [c[0] for c in callback_list if c[1]]
+ else:
+ return [c[0] for c in callback_list]
+
+ def _matching_osc_callbacks(self, event):
+ """returns list of callbacks matching the event"""
+ callback_list = []
+ if event.event_type == InputType.OpenSoundControl:
+ key = event.identifier.message_key
+ if event.device_guid in self.osc_callbacks:
+ import gremlin.execution_graph
+
+ ec = (
+ gremlin.execution_graph.ExecutionContext()
+ ) # current execution context
+ # search callbacks for mode hierarchy
+ callback_list = ec.getCallbacks(
+ self.osc_callbacks[event.device_guid], key, self.runtime_mode
+ )
+
+ verbose = config.Configuration().verbose_mode_osc
+ if verbose and not callback_list:
+ # syslog = logging.getLogger("system")
+ syslog.info(
+ f"OSC: no callbacks found for key: [{key}] mode: [{self.runtime_mode}]"
+ )
+
+ # Filter events when the system is paused
+ if not self.process_callbacks:
+ return [c[0] for c in callback_list if c[1]]
+ else:
+ return [c[0] for c in callback_list]
+
+ def _matching_functors(self, event) -> list:
+ """gets the list of matching functors to call when an event occurs"""
+ functors_list = []
+ device_guid = event.device_guid
+ if device_guid in self.latched_functors:
+ import gremlin.execution_graph
+
+ ec = gremlin.execution_graph.ExecutionContext() # current execution context
+ modes = ec.getModeHierarchy(self.runtime_mode)
+ for mode in modes:
+ if mode in self.latched_functors[device_guid].keys():
+ key = event.callbackKey
+ if key in self.latched_functors[device_guid][mode].keys():
+ functors_list = self.latched_functors[device_guid][mode][key]
+ if functors_list:
+ break
+ return functors_list
+
+ # device_guid = event.device_guid
+ # if device_guid in self.latched_functors:
+ # mode = self.runtime_mode
+ # if mode in self.latched_functors[device_guid].keys():
+ # if event in self.latched_functors[device_guid][mode].keys():
+ # functors_list = self.latched_functors[device_guid][mode][event]
+
+ # return functors_list
+
+ def _matching_callbacks(self, event):
+ """Returns the list of callbacks to execute in response to
+ the provided event.
+
+ :param event the event for which to search the matching
+ callbacks
+ :return a list of all callbacks registered and valid for the
+ given event
+ """
+
+ config = gremlin.config.Configuration()
+ verbose = config.verbose_mode_details # or config.verbose_mode_condition
+
+ # Obtain callbacks matching the event
+ callback_list = []
+ key = event.callbackKey
+ device_guid = event.device_guid
+ if device_guid in self.callbacks:
+ mode = self.runtime_mode
+ if mode in self.callbacks[device_guid]:
+ if key in self.callbacks[device_guid][mode]:
+ callback_list = self.callbacks[device_guid][mode][key]
+ if verbose:
+ event = self.callback_key_map[key]
+ self.dump_exectree(device_guid, mode, event)
+
+ if verbose:
+ syslog.debug(
+ f"CALLBACK: device: {gremlin.shared_state.get_device_name(event.device_guid)} mode: {self.runtime_mode} found: {len(callback_list)}"
+ )
+
+ # Filter events when the system is paused
+ if callback_list:
+ if not self.process_callbacks:
+ return [c[0] for c in callback_list if c[1]]
+ else:
+ return [c[0] for c in callback_list]
+
+ def _matching_latched_callbacks(self, event, key):
+ callback_list = []
+ if event.event_type in (InputType.KeyboardLatched, InputType.Keyboard):
+ if event.device_guid in self.latched_callbacks:
+ import gremlin.execution_graph
+
+ ec = (
+ gremlin.execution_graph.ExecutionContext()
+ ) # current execution context
+ # search callbacks for mode hierarchy
+ callback_list = ec.getCallbacks(
+ self.latched_callbacks[event.device_guid], key, self.runtime_mode
+ )
+
+ # Filter events when the system is paused
+ if not self.process_callbacks:
+ return [c[0] for c in callback_list if c[1]]
+ else:
+ return [c[0] for c in callback_list]
+
+ def _install_plugins(self, callback):
+ """Installs the current plugins into the given callback.
+
+ :param callback the callback function to install the plugins into
+ :return new callback with plugins installed
+ """
+ signature = inspect.signature(callback).parameters
+ for keyword, plugin in self.plugins.items():
+ if keyword in signature:
+ callback = plugin.install(callback, functools.partial)
+ return callback
@gremlin.singleton_decorator.SingletonDecorator
class VjoyRemapEventHandler(QtCore.QObject):
- grid_visible_changed = QtCore.Signal(bool) # occurs when a grid was updated
+ grid_visible_changed = QtCore.Signal(bool) # occurs when a grid was updated
diff --git a/gremlin/execution_graph.py b/gremlin/execution_graph.py
index 14f028c3..cbc11e9e 100644
--- a/gremlin/execution_graph.py
+++ b/gremlin/execution_graph.py
@@ -17,11 +17,8 @@
from __future__ import annotations
-from abc import abstractmethod, ABCMeta
-from collections import namedtuple
import copy
import logging
-import time
import gremlin.base_buttons
import gremlin.base_classes
@@ -538,7 +535,7 @@ def dumpExecTree(self, root = None, exclude_empty = True, conditions_only = Fals
# syslog = logging.getLogger("system")
if root is None:
root = self.root
- syslog.info(f"Execution Tree:")
+ syslog.info("Execution Tree:")
if root:
for pre, fill, node in anytree.RenderTree(root, style=anytree.AsciiStyle()):
if exclude_empty:
@@ -552,7 +549,7 @@ def dumpExecTree(self, root = None, exclude_empty = True, conditions_only = Fals
def dumpInputTree(self):
''' dumps the input tree '''
- syslog.info(f"Input Tree:")
+ syslog.info("Input Tree:")
root = self.m_root
if root:
for pre, fill, node in anytree.RenderTree(root, style=anytree.AsciiStyle()):
@@ -562,7 +559,7 @@ def dumpInputTree(self):
def dumpActive(self):
''' dumps active execution nodes ONLY'''
# syslog = logging.getLogger("system")
- syslog.info(f"Execution Tree:")
+ syslog.info("Execution Tree:")
if self.root:
for pre, fill, node in anytree.RenderTree(self.root, style=anytree.AsciiStyle()):
if anytree.search.findall_by_attr(node, ExecutionGraphNodeType.Action, "nodeType"):
@@ -571,7 +568,7 @@ def dumpActive(self):
def dumpModeTree(self):
# syslog = logging.getLogger("system")
- syslog.info(f"Mode Tree:")
+ syslog.info("Mode Tree:")
if self.modeTree:
for pre, fill, node in anytree.RenderTree(self.modeTree, style=anytree.AsciiStyle()):
syslog.info(f"{pre}{node.display} [{node.mode}]")
@@ -676,9 +673,9 @@ def _get_functor_node(self, container, functor, parent):
def _register_condition(self, parent_node, node):
''' registers a condition in the condition map '''
node_id = parent_node.id
- if not node_id in self._condition_map:
+ if node_id not in self._condition_map:
self._condition_map[node_id] = []
- if not node in self._condition_map[node_id]:
+ if node not in self._condition_map[node_id]:
self._condition_map[node_id].append(node)
syslog.info(f"Register condition: {node_id} {parent_node.description} -> {node.description}")
@@ -830,8 +827,8 @@ def _get_action_functor(self, action, node):
return functor
def _get_gate_action_functor(self, action, node):
- functor : gremlin.base_conditions.AbstractFunctor = self._get_action_functor()
- event = gremlin.event_handler.Event(
+ self._get_action_functor()
+ gremlin.event_handler.Event(
event_type= gremlin.input_types.InputType.VirtualButton,
device_guid = gremlin.shared_state.virtual_device_guid,
identifier = 1
@@ -868,8 +865,8 @@ def _build_execution_tree(self):
mode_list = [mode for (_,mode) in mode_source if mode] # parent mode first
# syslog = logging.getLogger("system")
- tracker = gremlin.base_profile.ConditionTracker()
- eh = gremlin.event_handler.EventHandler()
+ gremlin.base_profile.ConditionTracker()
+ gremlin.event_handler.EventHandler()
# build the mode tree
@@ -889,7 +886,7 @@ def _build_execution_tree(self):
tree_nodes = {}
for node in anytree.PreOrderIter(mode_tree):
mode_name = node.name
- if not mode_name in tree_nodes:
+ if mode_name not in tree_nodes:
tree_node = ExecutionModeNode(mode_name)
tree_node.parent = self._mode_tree
tree_nodes[mode_name] = tree_node
@@ -899,7 +896,7 @@ def _build_execution_tree(self):
if mode_name and node.parent and node.parent.name:
parent_mode_name = node.parent.name
mode_nodes[mode_name].parent = mode_nodes[parent_mode_name]
- if not parent_mode_name in tree_nodes:
+ if parent_mode_name not in tree_nodes:
parent_tree_node = ExecutionModeNode(parent_mode_name)
parent_tree_node.parent = self._mode_tree
tree_nodes[parent_mode_name] = parent_tree_node
@@ -940,7 +937,6 @@ def _build_execution_tree(self):
'''
# latched functors tracker
- latched_data = [] # list of LatchedData items
for device in profile.devices.values():
device_node = ExecutionGraphNode(ExecutionGraphNodeType.Device)
device_node.device = device
@@ -948,7 +944,7 @@ def _build_execution_tree(self):
for mode in device.modes.values():
mode_name = mode.name
- if not mode_name in mode_nodes:
+ if mode_name not in mode_nodes:
syslog.error(f"Execution Tree: error: mode: {mode_name} is not found in the device node: {device_node.name}")
continue
@@ -959,7 +955,7 @@ def _build_execution_tree(self):
mode_node.mode = mode_name
# build list of parent modes - contains the current mode if a root mode, or the list of current and parent modes if nested
- if not mode_name in self._mode_ancestors:
+ if mode_name not in self._mode_ancestors:
self._mode_ancestors[mode_name] = current_profile.get_mode_ancestors(mode_name)
self._mode_descendants[mode_name] = current_profile.get_mode_descendants(mode_name)
@@ -979,7 +975,7 @@ def _build_execution_tree(self):
input_node.mode = mode_name
input_key = input_item.callbackKey()
- if not input_key in m_input_nodes:
+ if input_key not in m_input_nodes:
m_input_node = ExecutionGraphNode(ExecutionGraphNodeType.InputItem)
m_input_node.parent = self.m_root
m_input_node.input_item = input_item
diff --git a/gremlin/fsm.py b/gremlin/fsm.py
index 3c5cd9f4..ef567af1 100644
--- a/gremlin/fsm.py
+++ b/gremlin/fsm.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -18,10 +18,11 @@
"""Implementation of a very simple finite state machine."""
import logging
+
syslog = logging.getLogger("system")
-class Transition:
+class Transition:
"""Represents a single transition in the finite state machine."""
def __init__(self, callback, new_state):
@@ -36,7 +37,6 @@ def __init__(self, callback, new_state):
class FiniteStateMachine:
-
"""Simple finite state machine."""
def __init__(self, start_state, states, actions, transitions, debug=False):
@@ -48,7 +48,7 @@ def __init__(self, start_state, states, actions, transitions, debug=False):
:param transitions the states x actions transition matrix
:param debug log debug messages if True
"""
- assert(start_state in states)
+ assert start_state in states
self.states = states
self.actions = actions
@@ -63,11 +63,13 @@ def perform(self, action):
:return returns the state transition function's return value
"""
key = (self.current_state, action)
- assert(action in self.actions)
- assert(key in self.transitions)
- assert(self.transitions[key].new_state in self.states)
+ assert action in self.actions
+ assert key in self.transitions
+ assert self.transitions[key].new_state in self.states
value = self.transitions[key].callback()
if self.debug:
- syslog.debug(f"FSM: {self.current_state} -> {self.transitions[key].new_state} ({action})")
+ syslog.debug(
+ f"FSM: {self.current_state} -> {self.transitions[key].new_state} ({action})"
+ )
self.current_state = self.transitions[key].new_state
return value
diff --git a/gremlin/gamepad_handling.py b/gremlin/gamepad_handling.py
index 0232e1a3..f9d488a0 100644
--- a/gremlin/gamepad_handling.py
+++ b/gremlin/gamepad_handling.py
@@ -1,5 +1,3 @@
-import importlib.util
-import sys
import logging
import gremlin.event_handler
@@ -7,15 +5,15 @@
from vigem import vigem_gamepad as vg
from vigem.vigem_client import VigemClient as vc
import gremlin.config
-from enum import Enum, auto
syslog = logging.getLogger("system")
_gamepad_available = False
_gamepad_devices = {}
+
def gamepadAvailable():
- ''' checks that gamepads are available '''
+ """checks that gamepads are available"""
# check for the vgamepad package
global _gamepad_available
@@ -26,30 +24,30 @@ def gamepadAvailable():
try:
pad = vg.VX360Gamepad()
_gamepad_available = vc.initalized and pad.valid
-
+
if _gamepad_available:
- syslog.info(f"gamepad: enabled")
+ syslog.info("gamepad: enabled")
else:
- syslog.info(f"gamepad: not found")
+ syslog.info("gamepad: not found")
except:
- syslog.info("VIGEM not found or did not load correctly. If you have VIGEM installed, check the version and ensure it is the 64 bit version. This message is normal if VIGEM is not installed.")
+ syslog.info(
+ "VIGEM not found or did not load correctly. If you have VIGEM installed, check the version and ensure it is the 64 bit version. This message is normal if VIGEM is not installed."
+ )
-
-
return _gamepad_available
-
def gamepad_initialization():
- ''' sets up the game pads '''
+ """sets up the game pads"""
gamepad_reset()
+
def gamepad_reset():
- ''' resets the number of game pads to the configured value '''
+ """resets the number of game pads to the configured value"""
device_count = gremlin.config.Configuration().vigem_device_count
global _gamepad_available, _gamepad_devices
current_count = len(_gamepad_devices)
- count_changed =current_count != device_count
+ count_changed = current_count != device_count
if _gamepad_available:
if device_count == 0:
keys = list(_gamepad_devices.keys())
@@ -57,7 +55,6 @@ def gamepad_reset():
del _gamepad_devices[keys.pop()]
keys = list(_gamepad_devices.keys())
else:
-
if current_count > device_count:
# remove devices
while len(_gamepad_devices) > device_count:
@@ -66,25 +63,23 @@ def gamepad_reset():
else:
while len(_gamepad_devices) != device_count:
pad = vg.VX360Gamepad()
- _gamepad_devices[pad.get_index()-1] = pad
+ _gamepad_devices[pad.get_index() - 1] = pad
-
if count_changed:
# let items in the Ui know the device count changed
el = gremlin.event_handler.EventListener()
el.gamepad_change_event.emit()
-
def getGamepad(index):
- ''' gets a gamepad object '''
+ """gets a gamepad object"""
global _gamepad_available, _gamepad_devices
if index in _gamepad_devices.keys():
return _gamepad_devices[index]
return None
+
def gamepadDevices():
- ''' gets the gamepad devices '''
+ """gets the gamepad devices"""
global _gamepad_available, _gamepad_devices
return _gamepad_devices.values()
-
diff --git a/gremlin/gated_handler.py b/gremlin/gated_handler.py
index ecd4b048..9bae417c 100644
--- a/gremlin/gated_handler.py
+++ b/gremlin/gated_handler.py
@@ -24,7 +24,6 @@
import gremlin.config
import gremlin.event_handler
import gremlin.execution_graph
-import gremlin.execution_graph
from gremlin.input_types import InputType
import gremlin.joystick_handling
import gremlin.shared_state
@@ -38,7 +37,6 @@
from enum import Enum, auto
from gremlin.macro_handler import *
-import gremlin.util
import gremlin.singleton_decorator
from gremlin.util import InvokeUiMethod
import gremlin.util
@@ -449,7 +447,7 @@ def setValue(self, data, emit = False):
def itemData(self, condition : GateConditionType):
''' gets the inputitem for the given condition '''
- if not condition in self.item_data_map.keys():
+ if condition not in self.item_data_map.keys():
data = self.parent._new_item_data()
data.input_type = InputType.JoystickButton
data.input_id = 1
@@ -615,7 +613,7 @@ def __init__(self, min_gate, max_gate, profile_mode = None,
def to_display(self):
if self.v1 is None or self.v2 is None:
- rr = f"N/A"
+ rr = "N/A"
else:
rr = self.range_display()
return rr
@@ -705,7 +703,7 @@ def _get_gate(self, id):
def itemData(self, condition : GateConditionType):
''' gets the inputitem for the given condition '''
- if not condition in self.item_data_map.keys():
+ if condition not in self.item_data_map.keys():
item_data = self.parent._new_item_data()
# use ranged containers/actions for range conditions, buttons for the others
input_type = InputType.JoystickAxis if condition in (GateConditionType.InRange, GateConditionType.OutsideRange) else InputType.JoystickButton
@@ -994,7 +992,7 @@ def to_percent(self, value) -> float:
def __str__(self):
if self.v1 is None or self.v2 is None:
- rr = f"N/A"
+ rr = "N/A"
else:
rr = self.range_display()
fixed_value = f"{self._fixed_value:0.{_decimals}f}" if self._fixed_value else "n/a"
@@ -1037,7 +1035,7 @@ def triggers(self) -> list:
def registerTrigger(self, owner, trigger : TriggerData):
''' registers/overrides prior trigger '''
- if not owner in self._tracking_map:
+ if owner not in self._tracking_map:
self._tracking_map[owner] = {}
self._tracking_map[owner][trigger.mode] = trigger
verbose = gremlin.config.Configuration().verbose_mode_gate
@@ -1051,9 +1049,9 @@ def registerTrigger(self, owner, trigger : TriggerData):
def getTrigger(self, owner, mode: TriggerMode):
''' gets the registered trigger if present, none if not'''
- if not owner in self._tracking_map:
+ if owner not in self._tracking_map:
return None
- if not mode in self._tracking_map[owner]:
+ if mode not in self._tracking_map[owner]:
return None
return self._tracking_map[owner][mode]
@@ -1259,7 +1257,7 @@ def hooked(self) -> bool:
def registerValueChangedCallback(self, callback):
''' registers a value callback '''
- if not callback in self._value_changed_callbacks:
+ if callback not in self._value_changed_callbacks:
self._value_changed_callbacks.append(callback)
def unregisterValueChangedCallback(self, callback):
@@ -1269,7 +1267,7 @@ def unregisterValueChangedCallback(self, callback):
def registerTriggerCallback(self, callback):
''' registers a trigger callback '''
- if not callback in self._trigger_callbacks:
+ if callback not in self._trigger_callbacks:
self._trigger_callbacks.append(callback)
def unregisterTriggerCallback(self, callback):
@@ -1335,8 +1333,8 @@ def _profile_start_cb(self):
item_data: gremlin.ui.joystick_device.InputItemConfiguration
- eh = gremlin.event_handler.EventHandler()
- ec = gremlin.execution_graph.ExecutionContext()
+ gremlin.event_handler.EventHandler()
+ gremlin.execution_graph.ExecutionContext()
# register gate crossings
for gate in gates:
@@ -1366,11 +1364,11 @@ def _profile_start_cb(self):
callbacks.extend(container.generate_callbacks())
if verbose:
syslog.info(f"\tadd range triggers: {range_info.range_display()} callback count: {len(callbacks)}")
- if not range_info in callbacks_map:
+ if range_info not in callbacks_map:
callbacks_map[range_info] = {}
callbacks_map[range_info][condition] = callbacks
else:
- if verbose:syslog.info(f"\tno mappings found")
+ if verbose:syslog.info("\tno mappings found")
self._callbacks = callbacks_map
@@ -1489,7 +1487,6 @@ def _joystick_event_handler(self, event):
range_event.event_type = InputType.JoystickAxis # force linear
for trigger in triggers:
- short_press = False
delay = trigger.delay
match trigger.mode:
case TriggerMode.FixedValue:
@@ -1545,7 +1542,7 @@ def _joystick_event_handler(self, event):
self._fire_trigger_callbacks(trigger)
else:
- if not gremlin.shared_state.runtime_mode in self.valid_mode_list:
+ if gremlin.shared_state.runtime_mode not in self.valid_mode_list:
# incorrect mode
return
@@ -1656,7 +1653,7 @@ def setGateCondition(self, index, condition):
def getGateCondition(self, index):
''' gets the condition for the given gate index '''
- if not index in self._gate_condition_map:
+ if index not in self._gate_condition_map:
self._gate_condition_map[index] = GateConditionType.OnCross
return self._gate_condition_map[index]
@@ -1863,7 +1860,7 @@ def getRanges(self, include_default = False, used_only = True, update = False):
def getGate(self, id = None):
''' returns a gate object for the given index - the item is created if the index does not exist and the gate is marked used '''
- if id is None or not id in self._gate_item_map.keys():
+ if id is None or id not in self._gate_item_map.keys():
# return a new gate
gate : GateInfo = next((gate for gate in self._gates if not gate.used), None)
return gate
@@ -1935,7 +1932,7 @@ def getGates(self, include_default = False, used_only = True):
def getUsedGates(self):
''' gets a sorted list of used gates (gate is used and has a value)'''
- gate_list = [gate for gate in self._gates if gate.used and gate.value != None]
+ gate_list = [gate for gate in self._gates if gate.used and gate.value is not None]
gate_list.sort(key = lambda x: x.value)
return gate_list
@@ -2003,7 +2000,7 @@ def registerRange(self, g1 : GateInfo, g2 : GateInfo) -> RangeInfo:
def getRange(self, id = None):
''' returns a range object for the given index - the item is created if the index does not exist but gates are not initialized'''
- if id is None or not id in self._range_item_map.keys():
+ if id is None or id not in self._range_item_map.keys():
return None
return self._range_item_map[id]
@@ -2033,7 +2030,7 @@ def getGateRanges(self, gate : GateInfo):
def deleteGate(self, data):
''' removes a gate '''
id = data.id
- if not id in self._gate_item_map.keys():
+ if id not in self._gate_item_map.keys():
syslog.error(f"Error: unable to find gate {id}")
return
verbose = gremlin.config.Configuration().verbose_mode_gate
@@ -2087,7 +2084,7 @@ def _get_next_gate_index(self):
''' gets the next unused index '''
used_list = self._get_used_gate_ids()
for index in range(100):
- if not index in used_list:
+ if index not in used_list:
return index
return None
@@ -2099,7 +2096,6 @@ def _update_ranges(self):
value_list = self.getUsedGates()
# save the current range data
range_item_data = []
- range_condition = []
range_is_default = []
range_mode = []
range_data = []
@@ -2147,7 +2143,7 @@ def _update_ranges(self):
for r in ranges:
syslog.info(f"\tRange: {str(r)}")
else:
- syslog.info(f"\tNo ranges found")
+ syslog.info("\tNo ranges found")
return ranges
@@ -2588,7 +2584,7 @@ def process_triggers(self, current_value : float , ranges : list[RangeInfo], upd
if r.valueInRange(current_value):
if tt.getTrigger(r, TriggerMode.RangeEnter):
- if verbose: syslog.info(f"\talready has RangeEnter trigger")
+ if verbose: syslog.info("\talready has RangeEnter trigger")
continue
# range enter and previously exited
td = TriggerData()
@@ -2657,7 +2653,7 @@ def process_triggers(self, current_value : float , ranges : list[RangeInfo], upd
for trigger in tt.triggers:
mode = trigger.mode
- if not mode in self.filter_map.keys():
+ if mode not in self.filter_map.keys():
self.filter_map[mode] = True
if self.filter_map[mode]:
if trigger.is_range:
@@ -2889,7 +2885,7 @@ def from_xml(self, node, data = None):
gate_delay = safe_read(child, "delay", int, 250)
- if not gate_condition in _gate_condition_to_enum.keys():
+ if gate_condition not in _gate_condition_to_enum.keys():
syslog.error(f"GateData: Invalid condition type {gate_condition} gate id: {gate_id}")
return
gate_condition = GateConditionType.to_enum(gate_condition)
@@ -2913,7 +2909,7 @@ def from_xml(self, node, data = None):
for item_node in item_nodes:
if item_node is not None:
item_data = self._new_item_data()
- if not "condition" in item_node.attrib:
+ if "condition" not in item_node.attrib:
condition = gate_condition
else:
condition_str = item_node.get("condition")
@@ -2980,13 +2976,13 @@ def from_xml(self, node, data = None):
range_condition = safe_read(child, "condition", str, "")
- if not range_condition in _gate_condition_to_enum.keys():
+ if range_condition not in _gate_condition_to_enum.keys():
syslog.error(f"GateData: Invalid condition type {range_condition} range: {range_id}")
return
range_condition = _gate_condition_to_enum[range_condition]
range_mode = safe_read(child, "mode", str, "")
- if not range_mode in _gate_range_to_enum.keys():
+ if range_mode not in _gate_range_to_enum.keys():
syslog.error(f"GateData: Invalid mode {range_mode} range: {range_id}")
return
range_mode = _gate_range_to_enum[range_mode]
@@ -3015,7 +3011,7 @@ def from_xml(self, node, data = None):
for item_node in item_nodes:
if item_node is not None:
item_node.tag = item_node.get("type")
- if not "condition" in item_node.attrib:
+ if "condition" not in item_node.attrib:
condition = range_condition
else:
condition_str = item_node.get("condition")
@@ -3253,7 +3249,7 @@ def _create_widget(self, gate : GateInfo,
self.data = gate
- label_width = gremlin.shared_state.char_width * 2
+ gremlin.shared_state.char_width * 2
self.label_widget = QtWidgets.QLabel(f"Gate {gate.slider_index + 1}:") # the slider index is the ordered gate number
#self.label_widget.setMaximumWidth(label_width)
@@ -3351,7 +3347,7 @@ def __init__(self, display_index, rng : RangeInfo, decimals, configure_range_han
if rng.is_default:
# default range
- self.label_widget = QtWidgets.QLabel(f"Default:")
+ self.label_widget = QtWidgets.QLabel("Default:")
else:
self.label_widget = QtWidgets.QLabel(f"Range {display_index}:")
@@ -4355,7 +4351,7 @@ def _create_filter_widgets(self):
col = 0
for _, trigger in enumerate(TriggerMode):
widget = gremlin.ui.ui_common.QDataCheckbox(TriggerMode.to_display_name(trigger), data = trigger)
- if not trigger in self._gate_data.filter_map.keys():
+ if trigger not in self._gate_data.filter_map.keys():
self._gate_data.filter_map[trigger] = True
widget.setChecked(self._gate_data.filter_map[trigger])
widget.clicked.connect(self._filter_cb)
@@ -4581,7 +4577,7 @@ def _set_gate_count(self, gate_count):
if verbose:
gates = self._gate_data.getUsedGates()
- syslog.info(f"Updated gates:")
+ syslog.info("Updated gates:")
for gate in gates:
syslog.info(f"\tGate: {gate.slider_index} {gate.value:0.{_decimals}f}")
@@ -4701,7 +4697,7 @@ def _update_gate_icons(self):
for index, gate in enumerate(gates):
gate.isError = False # assume no error
value = f"{gate.value:0.4f}"
- if not value in conflicts_map:
+ if value not in conflicts_map:
conflicts_map[value] = []
conflicts_map[value].append(gate)
@@ -5127,7 +5123,6 @@ def _condition_changed_cb(self, index):
def _update_ui(self):
''' updates controls based on the options '''
- from gremlin.ui.joystick_device import InputItemConfiguration
if self._is_range:
# range conditions
fixed_visible = self._range_info.mode == GateRangeOutputMode.Fixed
diff --git a/gremlin/hid_guardian.py b/gremlin/hid_guardian.py
index b1e95e1c..006967c0 100644
--- a/gremlin/hid_guardian.py
+++ b/gremlin/hid_guardian.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -31,15 +31,9 @@ def _open_key(sub_key, access=winreg.KEY_READ):
:return the handle to the opened key
"""
try:
- return winreg.OpenKey(
- winreg.HKEY_LOCAL_MACHINE,
- str(sub_key),
- access=access
- )
+ return winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, str(sub_key), access=access)
except OSError:
- raise HidGuardianError(
- f"Unable to open sub key \"{sub_key}\""
- )
+ raise HidGuardianError(f'Unable to open sub key "{sub_key}"')
def _clear_key(handle):
@@ -96,16 +90,18 @@ def _write_value(handle, value_name, data):
try:
winreg.SetValueEx(handle, value_name, 0, data[1], data[0])
except PermissionError:
- raise HidGuardianError(f"Unable to write value '{value_name}', insufficient permissions"
+ raise HidGuardianError(
+ f"Unable to write value '{value_name}', insufficient permissions"
)
class HidGuardian:
-
"""Interfaces with HidGuardians registry configuration."""
root_path = "SYSTEM\\CurrentControlSet\\Services\\HidGuardian\\Parameters"
- process_path = "SYSTEM\\CurrentControlSet\\Services\\HidGuardian\\Parameters\\Whitelist"
+ process_path = (
+ "SYSTEM\\CurrentControlSet\\Services\\HidGuardian\\Parameters\\Whitelist"
+ )
storage_value = "AffectedDevices"
def __init__(self):
@@ -116,27 +112,17 @@ def __init__(self):
try:
# Ensure we have the needed parameter entries
- handle = winreg.CreateKey(
- winreg.HKEY_LOCAL_MACHINE,
- HidGuardian.root_path
- )
- data = _read_value(
- handle,
- HidGuardian.storage_value,
- winreg.REG_MULTI_SZ
- )
+ handle = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, HidGuardian.root_path)
+ data = _read_value(handle, HidGuardian.storage_value, winreg.REG_MULTI_SZ)
if data[0] is None:
_write_value(
- handle,
- HidGuardian.storage_value,
- [[], winreg.REG_MULTI_SZ]
+ handle, HidGuardian.storage_value, [[], winreg.REG_MULTI_SZ]
)
handle.Close()
# Ensure we can create per process keys
handle = winreg.CreateKey(
- winreg.HKEY_LOCAL_MACHINE,
- HidGuardian.process_path
+ winreg.HKEY_LOCAL_MACHINE, HidGuardian.process_path
)
handle.Close()
except OSError:
@@ -153,11 +139,7 @@ def add_device(self, vendor_id, product_id):
# Add device to the list of devices that HidGuardian is intercepting
handle = _open_key(HidGuardian.root_path, winreg.KEY_ALL_ACCESS)
- data = _read_value(
- handle,
- HidGuardian.storage_value,
- winreg.REG_MULTI_SZ
- )
+ data = _read_value(handle, HidGuardian.storage_value, winreg.REG_MULTI_SZ)
device_string = self._create_device_string(vendor_id, product_id)
if data[0] is None:
@@ -204,15 +186,8 @@ def get_device_list(self):
return
# Get list of handled devices
- root_handle = winreg.OpenKey(
- winreg.HKEY_LOCAL_MACHINE,
- HidGuardian.root_path
- )
- data = _read_value(
- root_handle,
- HidGuardian.storage_value,
- winreg.REG_MULTI_SZ
- )
+ root_handle = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, HidGuardian.root_path)
+ data = _read_value(root_handle, HidGuardian.storage_value, winreg.REG_MULTI_SZ)
# Process each entry to extract vendor and product id
device_data = []
@@ -221,16 +196,14 @@ def get_device_list(self):
match = split_regex.match(entry)
if match:
try:
- device_data.append((
- int(match.group(1), 16),
- int(match.group(2), 16)
- ))
+ device_data.append(
+ (int(match.group(1), 16), int(match.group(2), 16))
+ )
except ValueError:
gremlin.util.display_error(
f"Failed to extract vendor and product id for HidGuardian entry:\n\n{entry}"
)
-
return device_data
def add_process(self, process_id):
@@ -247,8 +220,7 @@ def add_process(self, process_id):
# Ensure the process key exists and write the identifying value
handle = winreg.CreateKey(
- winreg.HKEY_LOCAL_MACHINE,
- f"{HidGuardian.process_path}\\{process_id}"
+ winreg.HKEY_LOCAL_MACHINE, f"{HidGuardian.process_path}\\{process_id}"
)
winreg.SetValueEx(handle, "Joystick Gremlin", 0, winreg.REG_DWORD, 1)
self._synchronize_process(process_id)
@@ -262,10 +234,7 @@ def remove_process(self, process_id):
return
try:
- handle = winreg.OpenKey(
- winreg.HKEY_LOCAL_MACHINE,
- HidGuardian.process_path
- )
+ handle = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, HidGuardian.process_path)
key_handle = winreg.OpenKey(handle, str(process_id))
_clear_key(key_handle)
winreg.DeleteKey(handle, str(process_id))
@@ -331,15 +300,10 @@ def _synchronize_process(self, process_id):
# Get data about devices handled by HidGuardian
root_handle = _open_key(HidGuardian.root_path)
- data = _read_value(
- root_handle,
- HidGuardian.storage_value,
- winreg.REG_MULTI_SZ
- )
+ data = _read_value(root_handle, HidGuardian.storage_value, winreg.REG_MULTI_SZ)
# Write the same data to the process exemption list
handle = _open_key(
- f"{HidGuardian.process_path}\\{process_id}",
- access=winreg.KEY_WRITE
+ f"{HidGuardian.process_path}\\{process_id}", access=winreg.KEY_WRITE
)
_write_value(handle, HidGuardian.storage_value, data)
diff --git a/gremlin/hints.py b/gremlin/hints.py
index 4a669507..3700ae62 100644
--- a/gremlin/hints.py
+++ b/gremlin/hints.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -26,6 +26,6 @@
with open(resource_path("doc/hints.csv")) as csv_stream:
- reader = csv.reader(csv_stream, delimiter=",", quotechar="\"")
+ reader = csv.reader(csv_stream, delimiter=",", quotechar='"')
for row in reader:
hint[row[0]] = row[1]
diff --git a/gremlin/import_profile.py b/gremlin/import_profile.py
index 01c2856d..4cd30a50 100644
--- a/gremlin/import_profile.py
+++ b/gremlin/import_profile.py
@@ -28,11 +28,7 @@
from collections import namedtuple
-import os
-import copy
import logging
-import time
-from typing import Union, Any
import gremlin.import_profile
import gremlin.joystick_handling
import gremlin.plugin_manager
@@ -43,40 +39,32 @@
import gremlin.event_handler
import gremlin.shared_state
-import PySide6
-from PySide6 import QtCore, QtGui, QtWidgets, QtMultimedia
+from PySide6 import QtCore, QtGui, QtWidgets
# from gremlin.util import *
from gremlin.types import DeviceType, TabDeviceType
from gremlin.input_types import InputType
import gremlin.util
from gremlin.util import safe_read
from gremlin.ui import ui_common,midi_device,osc_device, keyboard_device
-from gremlin.clipboard import Clipboard
# from gremlin.input_types import InputType
import dinput
import uuid
-import copy
-import dinput
from dinput import DeviceSummary
import gremlin.base_classes
import gremlin.base_profile
import gremlin.event_handler
import gremlin.shared_state
-from vjoy import vjoy
import gremlin.config
-from gremlin.ui import ui_common
import gremlin.joystick_handling
import gremlin.ui.ui_common
-import dinput
import enum
-import vjoy
@@ -842,9 +830,9 @@ def _get_input_name(self, input_type: InputType, input_id):
def _register_mode_item(self, mode, item):
''' registers a mode item for a given mode '''
- if not mode in self._import_mode_item_mode_map.keys():
+ if mode not in self._import_mode_item_mode_map.keys():
self._import_mode_item_mode_map[mode] = []
- if not item in self._import_mode_item_mode_map[mode]:
+ if item not in self._import_mode_item_mode_map[mode]:
self._import_mode_item_mode_map[mode].append(item)
def _register_description(self, device_guid, mode, input_id, description):
@@ -853,9 +841,9 @@ def _register_description(self, device_guid, mode, input_id, description):
assert mode
assert input_id
if description:
- if not device_guid in self.description_map:
+ if device_guid not in self.description_map:
self.description_map[device_guid] = {}
- if not mode in self.description_map[device_guid]:
+ if mode not in self.description_map[device_guid]:
self.description_map[device_guid][mode] = {}
self.description_map[device_guid][mode][input_id] = description
syslog.info(f"register description: device guid: {str(device_guid)} {gremlin.shared_state.get_device_name(device_guid)} mode: {mode} input: {input_id} description: {description}")
@@ -973,7 +961,7 @@ def _load_import_profile(self):
import_mode_item.mode = mode
self._register_mode_item(mode, import_mode_item)
import_mode_item.parent_mode = parent_mode
- if not item.device_guid in self._import_mode_item_map:
+ if item.device_guid not in self._import_mode_item_map:
self._import_mode_item_map[item.device_guid] = {}
self._import_mode_item_map[item.device_guid][mode] = import_mode_item
@@ -1150,7 +1138,7 @@ def _load_import_profile(self):
if not import_list:
- syslog.warning(f"Import profile: warning: no data found")
+ syslog.warning("Import profile: warning: no data found")
return
@@ -1173,11 +1161,11 @@ def _load_import_profile(self):
parent_mode = self.parent_mode_map[mode]
- if not mode in mode_list:
+ if mode not in mode_list:
mode_list.append(mode)
# import (device) node - parents to nothing
- if not item.device_guid in self._import_map.keys():
+ if item.device_guid not in self._import_map.keys():
import_item = ImportItem()
import_item.device_name = item.device_name
import_item.device_guid = item.device_guid
@@ -1195,7 +1183,7 @@ def _load_import_profile(self):
import_item = self._import_map[item.device_guid]
# mode node - parents to import (device) node
- if not mode in import_item.mode_map.keys():
+ if mode not in import_item.mode_map.keys():
import_mode_item = ImportModeItem()
import_mode_item.mode = mode
self._register_mode_item(mode, import_mode_item)
@@ -1220,7 +1208,7 @@ def _load_import_profile(self):
import_input_item.data = item.data
import_input_item.device_guid = import_item.device_guid
- if not import_item.device_guid in self._input_items_by_source_device_guid.keys():
+ if import_item.device_guid not in self._input_items_by_source_device_guid.keys():
self._input_items_by_source_device_guid[import_item.device_guid] = [] # create list of ImportInputItems for that specific import device so we can find them easily
self._input_items_by_source_device_guid[import_item.device_guid].append(import_input_item)
@@ -1247,7 +1235,7 @@ def _load_import_profile(self):
# check for old profile format without container IDs - if not set - we need to find it from the profile so the generated IDs are in sync
# the logical question is: why don't we just use that "loaded" profile to start with - the answer is - malformed XML profiles that have double entries and other old stuff in them a new profile would not load
container_id = container.id
- if not "container_id" in node.attrib:
+ if "container_id" not in node.attrib:
try:
items = self.source_profile.devices[import_item.device_guid].modes[mode].config[input_type]
@@ -1326,11 +1314,11 @@ def _register_used_input(self, device_guid, input_type, input_id):
''' registers a device input as used '''
if input_id is None:
return # nothing to register
- if not device_guid in self.used_target_inputs:
+ if device_guid not in self.used_target_inputs:
self.used_target_inputs[device_guid] = {}
- if not input_type in self.used_target_inputs[device_guid]:
+ if input_type not in self.used_target_inputs[device_guid]:
self.used_target_inputs[device_guid][input_type] = []
- if not input_id in self.used_target_inputs[device_guid][input_type]:
+ if input_id not in self.used_target_inputs[device_guid][input_type]:
self.used_target_inputs[device_guid][input_type].append(input_id)
@@ -1344,7 +1332,7 @@ def _get_used_input(self, device_guid, input_type):
def _clear_used_input(self, device_guid, input_type):
''' clears the registered inputs '''
- if not device_guid in self.used_target_inputs:
+ if device_guid not in self.used_target_inputs:
self.used_target_inputs[device_guid] = {}
self.used_target_inputs[device_guid][input_type] = []
@@ -1399,7 +1387,7 @@ def _find_target(self, source_device_guid : dinput.GUID, target_device_guid : d
used_inputs = self._get_used_input(target_device_guid, input_type)
used_inputs.sort()
for id in range(1,target_device.axis_count+1):
- if not id in used_inputs:
+ if id not in used_inputs:
target_input_id = id
break
target_input_id = input_id
@@ -1424,7 +1412,7 @@ def _find_target(self, source_device_guid : dinput.GUID, target_device_guid : d
used_inputs = self._get_used_input(target_device_guid, input_type)
used_inputs.sort()
for id in range(1,target_device.button_count+1):
- if not id in used_inputs:
+ if id not in used_inputs:
target_input_id = id
break
target_input_id = input_id
@@ -1450,7 +1438,7 @@ def _find_target(self, source_device_guid : dinput.GUID, target_device_guid : d
used_inputs = self._get_used_input(target_device_guid, input_type)
used_inputs.sort()
for id in range(1,target_device.hat_count+1):
- if not id in used_inputs:
+ if id not in used_inputs:
target_input_id = id
break
target_input_id = input_id
@@ -1528,7 +1516,7 @@ def _update_import_item(self, import_item : ImportItem, device_node : QtWidgets.
if verbose:
syslog.info(f"\t\t{input_item.input_name} -> not set")
continue
- elif not input_item.input_type in self._default_info_map:
+ elif input_item.input_type not in self._default_info_map:
syslog.warning(f"Import: unable to map {input_item.input_type} - no matching suitable device found: type: {input_item.input_type} name: {input_item.input_name} ")
if input_item.input_type == InputType.JoystickHat:
input_item.input_type = InputType.JoystickButton
@@ -1644,7 +1632,7 @@ def _update_map(self):
''' updates the mappings source to target '''
self._map = {} # clear and rebuild the map
- verbose = gremlin.config.Configuration().verbose
+ gremlin.config.Configuration().verbose
try:
gremlin.util.pushCursor() # long running op potentially
@@ -1671,7 +1659,6 @@ def _update_map(self):
import_item : ImportItem
# get mapping output options
- rollover = self.mode
@@ -1823,7 +1810,7 @@ def _create_target_input_widget(self, source_import_item : ImportItem, source_in
source_input_type = InputType.KeyboardLatched # move to GremlinEX keyboard device
- if not target_device_guid in self._target_input_item_map.keys():
+ if target_device_guid not in self._target_input_item_map.keys():
items = {} # map of possible target input items keyed by input type
for _, input_type in enumerate(InputType):
items[input_type] = []
@@ -1961,7 +1948,7 @@ def _create_target_input_widget(self, source_import_item : ImportItem, source_in
if index is None:
index = 0
- if not source_import_item in self._input_id_to_target_input_id:
+ if source_import_item not in self._input_id_to_target_input_id:
self._input_id_to_target_input_id[source_import_item]={}
self._input_id_to_target_input_id[source_import_item][source_input_item] = target_input_id
@@ -2105,7 +2092,7 @@ def _cmd_set_level(self, level):
@QtCore.Slot()
def _clear_profile(self):
''' clears the current profile data before import '''
- msgbox = gremlin.ui.ui_common.ConfirmBox(f"Reset profile?")
+ msgbox = gremlin.ui.ui_common.ConfirmBox("Reset profile?")
result = msgbox.show()
if result == QtWidgets.QMessageBox.StandardButton.Ok:
self.target_profile = gremlin.base_profile.Profile()
@@ -2137,7 +2124,7 @@ def _execute_import(self):
for import_item in import_items:
# target output device
- if not import_item.device_guid in self._input_device_guid_to_target_device_guid:
+ if import_item.device_guid not in self._input_device_guid_to_target_device_guid:
# nothing mapped, skip
continue
target_device_guid = self._input_device_guid_to_target_device_guid[import_item.device_guid]
@@ -2160,14 +2147,14 @@ def _execute_import(self):
parent_mode = mode_item.parent_mode
# if the mode is not in the target profile, create that mode
- if not target_mode in mode_list:
+ if target_mode not in mode_list:
self.target_profile.add_mode(target_mode, parent_mode, emit=False)
if verbose:
syslog.info(f"Adding non existing mode {target_mode} to target profile")
if parent_mode is not None:
- if not parent_mode in mode_list:
+ if parent_mode not in mode_list:
self.target_profile.add_mode(parent_mode, emit=False) # ensure the parent mode exists first
self.target_profile.set_mode_parent(target_mode, parent_mode, emit = False)
@@ -2187,7 +2174,6 @@ def _execute_import(self):
input_input_id = input_item.input_id
- input_input_type = input_item.input_type
if input_input_id == 0:
# no map entry - skip
continue
@@ -2452,7 +2438,7 @@ def __init__(self, device_info : dinput.DeviceSummary, parent=None):
if selected_index is not None:
self.cb_vjoy_device_selector.setCurrentIndex(selected_index)
- if not self.vjoy_id in self.vjoy_map:
+ if self.vjoy_id not in self.vjoy_map:
self.vjoy_id = self.cb_vjoy_device_selector.itemData(0)
@@ -2560,7 +2546,7 @@ def create_1to1_mapping(self, vjoy_id : int = 1, vjoy_mapper : str = "Vjoy Remap
gremlin_ui = gremlin.shared_state.ui
tab_device_type = gremlin_ui.getActiveTabType()
- if not tab_device_type in (TabDeviceType.Joystick, TabDeviceType.VjoyInput):
+ if tab_device_type not in (TabDeviceType.Joystick, TabDeviceType.VjoyInput):
gremlin.ui.ui_common.MessageBox("Information","1:1 mapping is only available on input joysticks")
return
diff --git a/gremlin/input_devices.py b/gremlin/input_devices.py
index db41edd9..3613526b 100644
--- a/gremlin/input_devices.py
+++ b/gremlin/input_devices.py
@@ -37,43 +37,45 @@
import gremlin.keyboard
import gremlin.shared_state
import gremlin.types
-from dinput import DILL, GUID, GUID_Invalid
+from dinput import DILL, GUID_Invalid
import gremlin.util
from gremlin.util import get_guid
import gremlin.input_types
-import vjoy.vjoy
from . import error
import win32api
-import gremlin.sendinput, gremlin.tts
+import gremlin.sendinput
+import gremlin.tts
-import socketserver, socket, msgpack
+import socketserver
+import socket
+import msgpack
import enum
import gremlin.singleton_decorator
-
syslog = logging.getLogger("system")
class ControlAction(enum.Enum):
- ''' defines the available control actions for the control plugin'''
- EnableInput = 0 # enables an input
- DisableInput = 1 # disable an input
- ToggleInput = 2 # toggle the input
+ """defines the available control actions for the control plugin"""
+
+ EnableInput = 0 # enables an input
+ DisableInput = 1 # disable an input
+ ToggleInput = 2 # toggle the input
@staticmethod
def to_string(action):
return action.name
-
+
@staticmethod
def to_display_name(action):
return _control_action_display[action]
-
-
+
+
_control_action_display = {
ControlAction.EnableInput: "Enable Input",
ControlAction.DisableInput: "Disable Input",
@@ -81,43 +83,42 @@ def to_display_name(action):
}
-
-
class VjoyAction(enum.Enum):
- ''' defines available vjoy actions supported by the vjoy mapper plugins'''
- VJoyButton = 0 # action on button press
- VJoyToggle = 1 # toggle function on/off
- VJoyPulse = 2 # pulse function (pulses a button),
- VJoyInvertAxis = 3 # invert axis function
- VJoySetAxis = 4 # set axis value
- VJoyAxis = 5 # normal map to axis
- VJoyHat = 6 # normal map to hat
- VJoyRangeAxis = 7 # scale axis
- VJoyAxisToButton = 8 # axis to button mapping
- VJoyToggleRemote = 9 # toggle remote control
- VJoyEnableRemoteOnly = 10 # enables remote control, disables local control
- VJoyEnableLocalOnly = 11 # enables local control, disables remote control
- VJoyDisableRemote = 12 # turns remote control off
- VJoyDisableLocal = 13 # turns local control off
- VJoyEnableRemote = 14 # enables remote control (does not impact local control)
- VJoyEnableLocal = 15 # enables local control (does not impact remote control)
- VJoyEnableLocalAndRemote = 16 # enables concurrent local/remote control
- VJoyEnablePairedRemote = 17 # enables primary fire one and two on remote client
- VJoyDisablePairedRemote = 18 # disable primary fire one and two on remote client
- VJoyButtonRelease = 19 # action button release (clear a button if set)
- VJoyMergeAxis = 20 # action to merge another axis
- VJoyHatToButton = 21 # action to map a hat to a button
- VJoySetAxisStepped = 22 # like VjoySetAxis but uses a list of values to bump the index
-
-
+ """defines available vjoy actions supported by the vjoy mapper plugins"""
+
+ VJoyButton = 0 # action on button press
+ VJoyToggle = 1 # toggle function on/off
+ VJoyPulse = 2 # pulse function (pulses a button),
+ VJoyInvertAxis = 3 # invert axis function
+ VJoySetAxis = 4 # set axis value
+ VJoyAxis = 5 # normal map to axis
+ VJoyHat = 6 # normal map to hat
+ VJoyRangeAxis = 7 # scale axis
+ VJoyAxisToButton = 8 # axis to button mapping
+ VJoyToggleRemote = 9 # toggle remote control
+ VJoyEnableRemoteOnly = 10 # enables remote control, disables local control
+ VJoyEnableLocalOnly = 11 # enables local control, disables remote control
+ VJoyDisableRemote = 12 # turns remote control off
+ VJoyDisableLocal = 13 # turns local control off
+ VJoyEnableRemote = 14 # enables remote control (does not impact local control)
+ VJoyEnableLocal = 15 # enables local control (does not impact remote control)
+ VJoyEnableLocalAndRemote = 16 # enables concurrent local/remote control
+ VJoyEnablePairedRemote = 17 # enables primary fire one and two on remote client
+ VJoyDisablePairedRemote = 18 # disable primary fire one and two on remote client
+ VJoyButtonRelease = 19 # action button release (clear a button if set)
+ VJoyMergeAxis = 20 # action to merge another axis
+ VJoyHatToButton = 21 # action to map a hat to a button
+ VJoySetAxisStepped = (
+ 22 # like VjoySetAxis but uses a list of values to bump the index
+ )
@staticmethod
def to_string(mode):
return mode.name
-
+
def __str__(self):
return str(self.value)
-
+
@classmethod
def _missing_(cls, name):
for item in cls:
@@ -125,10 +126,9 @@ def _missing_(cls, name):
return item
return cls.VJoyButton
-
@staticmethod
def from_string(str):
- ''' converts from a string representation (text or numeric) to the enum, not case sensitive'''
+ """converts from a string representation (text or numeric) to the enum, not case sensitive"""
str = str.lower().strip()
if str.isnumeric():
mode = int(str)
@@ -138,10 +138,10 @@ def from_string(str):
return item
return None
-
+
@staticmethod
def to_description(action):
- ''' returns a descriptive string for the action '''
+ """returns a descriptive string for the action"""
match action:
case VjoyAction.VJoyAxis:
return "Maps a vjoy axis"
@@ -192,105 +192,103 @@ def to_description(action):
case VjoyAction.VJoySetAxisStepped:
return "Steps through set axis values"
-
- msg = f"Unknown [{action}]"
+ msg = f"Unknown [{action}]"
syslog.debug(f"Warning: missing action description mapping: {msg}")
return msg
-
+
@staticmethod
def to_name(action):
- ''' returns a name string for the action '''
+ """returns a name string for the action"""
match action:
- case VjoyAction.VJoyAxis:
+ case VjoyAction.VJoyAxis:
return "Axis"
- case VjoyAction.VJoyButton:
+ case VjoyAction.VJoyButton:
return "Button Press"
- case VjoyAction.VJoyHat:
+ case VjoyAction.VJoyHat:
return "Hat"
- case VjoyAction.VJoyHatToButton:
+ case VjoyAction.VJoyHatToButton:
return "Hat to Button"
- case VjoyAction.VJoyInvertAxis:
+ case VjoyAction.VJoyInvertAxis:
return "Invert Axis"
- case VjoyAction.VJoyPulse:
+ case VjoyAction.VJoyPulse:
return "Pulse Button"
- case VjoyAction.VJoySetAxis:
+ case VjoyAction.VJoySetAxis:
return "Sets Axis Value"
- case VjoyAction.VJoyToggle:
+ case VjoyAction.VJoyToggle:
return "Toggle Button"
case VjoyAction.VJoyRangeAxis:
return "Set Axis Range"
- case VjoyAction.VJoyAxisToButton:
+ case VjoyAction.VJoyAxisToButton:
return "Axis to Button"
- case VjoyAction.VJoyEnableLocalOnly:
+ case VjoyAction.VJoyEnableLocalOnly:
return "Local Control Only"
- case VjoyAction.VJoyEnableRemoteOnly:
+ case VjoyAction.VJoyEnableRemoteOnly:
return "Enable Remote Control (exclusive)"
- case VjoyAction.VJoyEnableLocal:
+ case VjoyAction.VJoyEnableLocal:
return "Enables Local Control"
- case VjoyAction.VJoyEnableRemoteOnly:
+ case VjoyAction.VJoyEnableRemoteOnly:
return "Enables Remote Control (exclusive)"
- case VjoyAction.VJoyEnableLocalAndRemote:
+ case VjoyAction.VJoyEnableLocalAndRemote:
return "Enable Concurrent Local and Remote control"
- case VjoyAction.VJoyToggleRemote:
+ case VjoyAction.VJoyToggleRemote:
return "Toggle Control"
- case VjoyAction.VJoyEnablePairedRemote:
+ case VjoyAction.VJoyEnablePairedRemote:
return "Enable remote pairing"
- case VjoyAction.VJoyDisablePairedRemote:
+ case VjoyAction.VJoyDisablePairedRemote:
return "Disable remote pairing"
- case VjoyAction.VJoyEnableRemote:
+ case VjoyAction.VJoyEnableRemote:
return "Enable remote control"
- case VjoyAction.VJoyDisableLocal:
+ case VjoyAction.VJoyDisableLocal:
return "Disable local control"
- case VjoyAction.VJoyDisableRemote:
+ case VjoyAction.VJoyDisableRemote:
return "Disable remote control"
- case VjoyAction.VJoyButtonRelease:
+ case VjoyAction.VJoyButtonRelease:
return "Button release"
- case VjoyAction.VJoyMergeAxis:
+ case VjoyAction.VJoyMergeAxis:
return "Merge Axis"
case VjoyAction.VJoySetAxisStepped:
return "Stepped Axis Value"
-
- msg = f"Unknown [{action}]"
+ msg = f"Unknown [{action}]"
syslog.debug(f"Warning: missing action name mapping: {msg}")
return msg
-
@staticmethod
def is_command(value):
return value in (
- VjoyAction.VJoyDisableLocal,
- VjoyAction.VJoyDisableRemote,
- VjoyAction.VJoyEnableLocalOnly,
- VjoyAction.VJoyEnableRemoteOnly,
- VjoyAction.VJoyEnableLocalAndRemote,
- VjoyAction.VJoyEnableLocal,
- VjoyAction.VJoyEnableRemote,
- VjoyAction.VJoyToggleRemote,
- VjoyAction.VJoyEnablePairedRemote,
- VjoyAction.VJoyDisablePairedRemote,
+ VjoyAction.VJoyDisableLocal,
+ VjoyAction.VJoyDisableRemote,
+ VjoyAction.VJoyEnableLocalOnly,
+ VjoyAction.VJoyEnableRemoteOnly,
+ VjoyAction.VJoyEnableLocalAndRemote,
+ VjoyAction.VJoyEnableLocal,
+ VjoyAction.VJoyEnableRemote,
+ VjoyAction.VJoyToggleRemote,
+ VjoyAction.VJoyEnablePairedRemote,
+ VjoyAction.VJoyDisablePairedRemote,
)
-class InternalSpeech():
- ''' tts interface '''
- def __init__(self):
- import win32com.client
- self.speaker = win32com.client.Dispatch("SAPI.SpVoice")
+class InternalSpeech:
+ """tts interface"""
+
+ def __init__(self):
+ import win32com.client
+
+ self.speaker = win32com.client.Dispatch("SAPI.SpVoice")
- def speak(self, text):
- try:
- self.speaker.speak(text)
- except:
- pass
+ def speak(self, text):
+ try:
+ self.speaker.speak(text)
+ except:
+ pass
@gremlin.singleton_decorator.SingletonDecorator
-class RemoteControl():
- ''' holds remote control status information'''
+class RemoteControl:
+ """holds remote control status information"""
def __init__(self):
-
self._is_remote = False
self._is_local = False
self._is_paired = False
@@ -301,9 +299,10 @@ def __init__(self):
el = gremlin.event_handler.EventListener()
el.config_changed.connect(self._config_changed)
el.broadcast_changed.connect(self._broadcast_changed)
-
+
def _update(self, value):
import gremlin.event_handler
+
is_local = self._is_local
is_remote = self._is_remote
is_paired = self._is_paired
@@ -334,17 +333,23 @@ def _update(self, value):
else:
# not sure what this was
return
-
+
self._mode = value
- syslog.info(f"SYSTEM: Remote control status: local: {self._is_local} remote: {self._is_remote}")
+ syslog.info(
+ f"SYSTEM: Remote control status: local: {self._is_local} remote: {self._is_remote}"
+ )
if self._is_local != is_local or self._is_remote != is_remote:
# status changed
self._is_local = is_local
self._is_remote = is_remote
-
+
el = gremlin.event_handler.EventListener()
- el.broadcast_changed.emit(gremlin.event_handler.StateChangeEvent(self._is_local, self._is_remote, self._is_broadcast))
+ el.broadcast_changed.emit(
+ gremlin.event_handler.StateChangeEvent(
+ self._is_local, self._is_remote, self._is_broadcast
+ )
+ )
if self._is_paired != is_paired:
# pairing mode changed
@@ -354,16 +359,20 @@ def _update(self, value):
else:
msg = "Paired mode disabled"
syslog.debug(f"Paired mode changed: {msg}")
- threading.Thread(target = self.say, args=(msg,), daemon=True).start()
+ threading.Thread(target=self.say, args=(msg,), daemon=True).start()
def _config_changed(self):
- ''' called when broadcast config item changes '''
-
+ """called when broadcast config item changes"""
+
config = gremlin.config.Configuration()
if self._is_broadcast != config.enable_remote_broadcast:
self._is_broadcast = config.enable_remote_broadcast
el = gremlin.event_handler.EventListener()
- el.broadcast_changed.emit(gremlin.event_handler.StateChangeEvent(self._is_local, self._is_remote, self._is_broadcast))
+ el.broadcast_changed.emit(
+ gremlin.event_handler.StateChangeEvent(
+ self._is_local, self._is_remote, self._is_broadcast
+ )
+ )
def say(self, msg):
speech = InternalSpeech()
@@ -380,50 +389,51 @@ def _broadcast_changed(self, event):
elif event.is_remote:
msg = "Remote control is enabled"
if msg:
- threading.Thread(target = self.say, args=(msg,), daemon=True).start()
-
+ threading.Thread(target=self.say, args=(msg,), daemon=True).start()
+
@property
def mode(self):
- ''' gets the current mode '''
+ """gets the current mode"""
return self._mode
-
+
@mode.setter
def mode(self, value):
self._update(value)
@property
def is_local(self):
- ''' status of local control '''
+ """status of local control"""
return self._is_local
+
@property
def is_remote(self):
- ''' status of remote control '''
+ """status of remote control"""
return self._is_remote and self._is_broadcast
-
+
@property
def state(self):
- ''' returns status as a pair of flags, local, remote'''
+ """returns status as a pair of flags, local, remote"""
return (self.is_local, self.is_remote)
-
+
@property
def paired(self):
- ''' paired status '''
+ """paired status"""
return self._is_paired
-
def to_state_event(self):
- ''' returns event data for the current state '''
+ """returns event data for the current state"""
from gremlin.event_handler import StateChangeEvent
+
event = StateChangeEvent(self.is_local, self.is_remote, self._is_broadcast)
return event
+
def get_remote_state():
- ''' gets the remote state '''
+ """gets the remote state"""
return remote_state
class CallbackRegistry:
-
"""Registry of all callbacks known to the system."""
def __init__(self):
@@ -452,7 +462,10 @@ def add(self, callback, event, mode, always_execute=False):
if event not in self._registry[event.device_guid][mode]:
self._registry[event.device_guid][mode][key] = {}
- self._registry[event.device_guid][mode][key][function_name] = (callback, always_execute)
+ self._registry[event.device_guid][mode][key][function_name] = (
+ callback,
+ always_execute,
+ )
@property
def registry(self):
@@ -468,7 +481,6 @@ def clear(self):
class PeriodicRegistry:
-
"""Registry for periodically executed functions."""
def __init__(self):
@@ -526,16 +538,12 @@ def _install_plugins(self, callback):
callback = plugin.install(callback, partial_fn)
return callback
-
def _thread_loop(self):
"""Main execution loop run in a separate thread."""
import uuid
+
# Setup plugins to use
- self._plugins = [
- JoystickPlugin(),
- VJoyPlugin(),
- KeyboardPlugin()
- ]
+ self._plugins = [JoystickPlugin(), VJoyPlugin(), KeyboardPlugin()]
callback_map = {}
period_map = {}
# Populate the queue
@@ -548,7 +556,6 @@ def _thread_loop(self):
value = time.time() + period_map[node_id]
heapq.heappush(self._queue, (value, node_id))
-
# Main thread loop
while self._running:
# Process all events that require running
@@ -558,19 +565,16 @@ def _thread_loop(self):
callback_map[node_id]()
heapq.heappush(
- self._queue,
- (time.time() + period_map[node_id], node_id)
+ self._queue, (time.time() + period_map[node_id], node_id)
)
# Sleep until either the next function needs to be run or
# our timeout expires
time.sleep(min(self._queue[0][0] - time.time(), 1.0))
-
class SimpleRegistry:
-
- """Registry for functions executed """
+ """Registry for functions executed"""
def __init__(self):
"""Creates a new instance."""
@@ -591,12 +595,10 @@ def start(self):
plugin_cb = self._install_plugins(item)
plugin_cb()
-
def stop(self):
"""Stops the event loop."""
self._running = False
-
def add(self, callback):
"""Adds a function to execute periodically.
@@ -604,8 +606,7 @@ def add(self, callback):
:param interval the time between executions
"""
assert callable(callback)
- self._registry[callback] = callback
-
+ self._registry[callback] = callback
def clear(self):
"""Clears the registry."""
@@ -628,8 +629,9 @@ def _install_plugins(self, callback):
return callback
-class ModeChangeRegistry():
- """Registry for functions executed on mode change """
+class ModeChangeRegistry:
+ """Registry for functions executed on mode change"""
+
def __init__(self):
"""Creates a new instance."""
self._registry = {}
@@ -645,7 +647,6 @@ def add(self, callback):
assert callable(callback)
self._registry[callback] = callback
-
def clear(self):
"""Clears the registry."""
self._registry = {}
@@ -670,8 +671,8 @@ def _install_plugins(self, callback):
callback = plugin.install(callback, partial_fn)
return callback
- def runtime_mode_changed(self, mode : str):
- ''' calls all registered callbacks when the GremlinEx mode changes '''
+ def runtime_mode_changed(self, mode: str):
+ """calls all registered callbacks when the GremlinEx mode changes"""
if len(self._registry) == 0:
return
for item in self._registry.values():
@@ -679,12 +680,13 @@ def runtime_mode_changed(self, mode : str):
plugin_cb(mode)
+class StateChangeRegistry:
+ """Registry for functions executed on state (remote/local) change"""
-class StateChangeRegistry():
- """Registry for functions executed on state (remote/local) change """
def __init__(self):
"""Creates a new instance."""
from gremlin.event_handler import EventListener
+
self._registry = {}
self._running = False
self._plugins = []
@@ -700,7 +702,6 @@ def add(self, callback):
assert callable(callback)
self._registry[callback] = callback
-
def clear(self):
"""Clears the registry."""
self._registry = {}
@@ -726,7 +727,7 @@ def _install_plugins(self, callback):
return callback
def state_changed(self, event):
- ''' calls all registered callbacks when the GremlinEx local or remote states change '''
+ """calls all registered callbacks when the GremlinEx local or remote states change"""
if len(self._registry) == 0:
return
for item in self._registry.values():
@@ -734,26 +735,22 @@ def state_changed(self, event):
plugin_cb(event)
-
-
-class GremlinServer(socketserver.ThreadingMixIn,socketserver.UDPServer):
+class GremlinServer(socketserver.ThreadingMixIn, socketserver.UDPServer):
pass
+
class GremlinSocketHandler(socketserver.BaseRequestHandler):
- ''' handles remote input from a gremlin client on the network
-
- received network events are processed here
-
- '''
+ """handles remote input from a gremlin client on the network
+ received network events are processed here
+
+ """
def handle(self):
-
-
# handles input data
raw_data = self.request[0].strip()
# socket = self.request[1]
-
+
data = msgpack.unpackb(raw_data)
# syslog.debug(f"Gremlin received remote data: {data}")
@@ -761,12 +758,12 @@ def handle(self):
if sender == remote_client.id:
# ignore our own broadcasts
return
-
+
action = data["action"]
if action == "hb":
# heart beat
return
-
+
if action == "key":
# keyboard output
virtual_code = data["vc"]
@@ -774,7 +771,6 @@ def handle(self):
flags = data["flags"]
win32api.keybd_event(virtual_code, scan_code, flags, 0)
elif action == "mouse":
-
subtype = data["subtype"]
if subtype == "wheel":
direction = data["direction"]
@@ -809,17 +805,21 @@ def handle(self):
max_speed = data["max_speed"]
time_to_max_speed = data["time_to_speed"]
mouse_controller = gremlin.sendinput.MouseController()
- mouse_controller.set_accelerated_motion(a,min_speed,max_speed,time_to_max_speed)
+ mouse_controller.set_accelerated_motion(
+ a, min_speed, max_speed, time_to_max_speed
+ )
elif action == "gamepad":
# gamepad handling
- index = data["index"] # id of the gamepad to send the data to
- subtype = data["subtype"] # axis or button
- output_mode = data["mode"] # either a gamepadoutput or the translated button code
+ index = data["index"] # id of the gamepad to send the data to
+ subtype = data["subtype"] # axis or button
+ output_mode = data[
+ "mode"
+ ] # either a gamepadoutput or the translated button code
vigem = gremlin.gamepad_handling.getGamepad(index)
if vigem is not None:
if subtype == "axis":
value = data["value"]
-
+
if vigem:
if output_mode == GamePadOutput.LeftStickX:
vigem.left_joystick_float_x(vscaled)
@@ -830,10 +830,14 @@ def handle(self):
elif output_mode == GamePadOutput.RightStickY:
vigem.right_joystick_float_y(vscaled)
if output_mode == GamePadOutput.LeftTrigger:
- vscaled = gremlin.util.scale_to_range(value.current,target_min=0.0, target_max=1.0)
+ vscaled = gremlin.util.scale_to_range(
+ value.current, target_min=0.0, target_max=1.0
+ )
vigem.left_trigger_float(vscaled)
if output_mode == GamePadOutput.RightTrigger:
- vscaled = gremlin.util.scale_to_range(value.current,target_min=0.0, target_max=1.0)
+ vscaled = gremlin.util.scale_to_range(
+ value.current, target_min=0.0, target_max=1.0
+ )
vigem.right_trigger_float(vscaled)
elif subtype == "button":
@@ -843,13 +847,8 @@ def handle(self):
else:
vigem.release_button(button)
vigem.update()
-
-
-
-
-
- elif action in ("button","axis","hat","relative_axis"):
+ elif action in ("button", "axis", "hat", "relative_axis"):
# joystick button
device = data["device"]
target = data["target"]
@@ -862,7 +861,7 @@ def handle(self):
if device in proxy.vjoy_devices:
# valid device
vjoy = proxy[device]
-
+
if action == "button":
# emit button change
if target > 0 and target < vjoy.button_count:
@@ -870,7 +869,7 @@ def handle(self):
elif action == "axis":
if value is None:
# relative mode = get the current value
- value = proxy[device].axis(target).value
+ value = proxy[device].axis(target).value
if relative_value:
# apply the relative value
value = gremlin.util.clamp(value + relative_value, -1.0, +1.0)
@@ -880,13 +879,16 @@ def handle(self):
if target > 0 and target <= vjoy.hat_count:
proxy[device].hat(target).direction = value
elif action == "relative_axis":
- if target > 0 and target <= vjoy.axis_count:
- proxy[device].axis(target).value = max(-1.0,min(1.0, proxy[device].axis(target).value + value))
+ if target > 0 and target <= vjoy.axis_count:
+ proxy[device].axis(target).value = max(
+ -1.0, min(1.0, proxy[device].axis(target).value + value)
+ )
-class RPCGremlin():
- ''' remote UDP multicast listener '''
- MULTICAST_GROUP = '224.3.29.72' # multicast group
+class RPCGremlin:
+ """remote UDP multicast listener"""
+
+ MULTICAST_GROUP = "224.3.29.72" # multicast group
# multicast time to live
MULTICAST_TTL = 2
@@ -901,26 +903,31 @@ def __init__(self):
self._server_thread = None
self._keep_running = False
-
-
def _run(self):
import struct
+
syslog.debug("Starting gremlin listener...")
- self._server = GremlinServer(('', self._port),GremlinSocketHandler)
- self._server_thread = threading.Thread(target=self._server.serve_forever, daemon=True)
+ self._server = GremlinServer(("", self._port), GremlinSocketHandler)
+ self._server_thread = threading.Thread(
+ target=self._server.serve_forever, daemon=True
+ )
self._server_thread.daemon = True
try:
self._server_thread.start()
# enable listen to multicast UDP
group = socket.inet_aton(RPCGremlin.MULTICAST_GROUP)
- mreq = struct.pack('4sL', group, socket.INADDR_ANY)
- self._server.socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
- syslog.debug(f"Starting gremlin server listener: multicast group {RPCGremlin.MULTICAST_GROUP} port {self._port} ...")
+ mreq = struct.pack("4sL", group, socket.INADDR_ANY)
+ self._server.socket.setsockopt(
+ socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq
+ )
+ syslog.debug(
+ f"Starting gremlin server listener: multicast group {RPCGremlin.MULTICAST_GROUP} port {self._port} ..."
+ )
self._keep_running = True
self._running = True
while self._keep_running:
time.sleep(1)
- except Exception as ex:
+ except Exception:
pass
self._server.shutdown()
@@ -931,13 +938,12 @@ def _run(self):
# release any locks on devices
proxy.reset()
-
@property
def running(self):
return self._running
def start(self):
- ''' starts the listener '''
+ """starts the listener"""
config = gremlin.config.Configuration()
if not config.enable_remote_control:
@@ -946,25 +952,27 @@ def start(self):
if self._running:
# already running
return
-
+
# register the devices we will need
- vjoyid_list = [dev.vjoy_id for dev in gremlin.joystick_handling.joystick_devices() if dev.is_virtual]
+ vjoyid_list = [
+ dev.vjoy_id
+ for dev in gremlin.joystick_handling.joystick_devices()
+ if dev.is_virtual
+ ]
for key in vjoyid_list:
try:
- device = gremlin.joystick_handling.VJoyProxy()[key]
+ gremlin.joystick_handling.VJoyProxy()[key]
syslog.debug(f"Remote proxy VJOY [{key}] ok")
except:
pass
self._thread = threading.Thread(target=self._run, daemon=True)
self._thread.start()
-
-
def stop(self):
- ''' stops the loop'''
+ """stops the loop"""
if not self._running:
return
-
+
# stop the server loop
self._keep_running = False
if self._thread.is_alive():
@@ -974,63 +982,56 @@ def stop(self):
syslog.debug("Gremlin RPC server stopped...")
-
class RemoteServer(QtCore.QObject):
- """ Provides access to remote a remote Gremlin instance events """
+ """Provides access to remote a remote Gremlin instance events"""
def __init__(self):
"""Initialises a new object."""
QtCore.QObject.__init__(self)
self._rpc = None
-
def start(self):
- ''' start listening '''
+ """start listening"""
config = gremlin.config.Configuration()
self._enabled = config.enable_remote_control
if self._enabled:
self._rpc = RPCGremlin()
self._rpc.start()
syslog.debug("Gremlin RPC server started...")
-
def stop(self):
- ''' stop listening'''
+ """stop listening"""
if self._rpc:
self._rpc.stop()
@property
def running(self):
- ''' true if the server is running'''
+ """true if the server is running"""
return self._rpc and self._rpc.running
-
+
@property
def enabled(self):
- ''' true if server is accepting input from clients '''
+ """true if server is accepting input from clients"""
return remote_state.is_remote
-
-
+
@enabled.setter
def enabled(self, value):
self._enabled = value
-
@gremlin.singleton_decorator.SingletonDecorator
class RemoteClient(QtCore.QObject):
- """ Provides access to a remote Gremlin instance """
+ """Provides access to a remote Gremlin instance"""
class ClientMode(enum.Enum):
Local = 1
Remote = 2
LocalAndRemote = 3
-
-
def __init__(self):
"""Initialises a new object."""
QtCore.QObject.__init__(self)
- #self._host = "localhost"
+ # self._host = "localhost"
config = gremlin.config.Configuration()
self._port = config.server_port
self._broadcast_enabled = config.enable_remote_broadcast
@@ -1043,29 +1044,28 @@ def __init__(self):
self._started = False
el = gremlin.event_handler.EventListener()
- el.profile_stop.connect(self.stop) # hook stop event
+ el.profile_stop.connect(self.stop) # hook stop event
def start(self):
- ''' creates a multicast client send socket on profile start '''
+ """creates a multicast client send socket on profile start"""
if not self._started:
self._started = True
self.ensure_socket()
el = gremlin.event_handler.EventListener()
el.heartbeat.connect(self._alive_ticker)
-
-
def ensure_socket(self):
# makes sure the socket exists
import struct
+
if not self._sock:
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- ttl = struct.pack('b', RPCGremlin.MULTICAST_TTL)
+ ttl = struct.pack("b", RPCGremlin.MULTICAST_TTL)
self._sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
syslog.debug("Gremlin RPC client started...")
def stop(self):
- ''' closes the client socket'''
+ """closes the client socket"""
if self._started:
el = gremlin.event_handler.EventListener()
el.heartbeat.disconnect(self._alive_ticker)
@@ -1073,13 +1073,12 @@ def stop(self):
if self._alive_thread:
syslog.debug("Alive stop requested...")
-
self._alive_thread_stop_requested = True
if self._alive_thread.is_alive():
self._alive_thread.join()
syslog.debug("Alive thread stopped")
self._alive_thread = None
-
+
if self._sock:
self._sock.close()
self._sock = None
@@ -1088,30 +1087,32 @@ def stop(self):
self._started = False
def _alive_ticker(self):
- ''' sends an alive packet to keep the ports alive '''
+ """sends an alive packet to keep the ports alive"""
if self._broadcast_enabled:
- data = {}
- data["sender"] = self._id
- data["action"] = "hb"
- raw_data = msgpack.packb(data)
- self._send(raw_data)
- verbose = gremlin.config.Configuration().verbose
- if verbose: syslog.info("Alive heartbeat")
-
-
- def _send(self, data = None):
- ''' sends data to the socket'''
+ data = {}
+ data["sender"] = self._id
+ data["action"] = "hb"
+ raw_data = msgpack.packb(data)
+ self._send(raw_data)
+ verbose = gremlin.config.Configuration().verbose
+ if verbose:
+ syslog.info("Alive heartbeat")
+
+ def _send(self, data=None):
+ """sends data to the socket"""
if data:
self.ensure_socket()
self._sock.sendto(data, self._address)
- def send_button(self, device_id, button_id, is_pressed, force_remote = False):
- ''' handles a remote joystick event '''
+ def send_button(self, device_id, button_id, is_pressed, force_remote=False):
+ """handles a remote joystick event"""
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
- syslog.info(f"REMOTE OUTPUT: send button: VJoyId: {device_id} button {button_id} pressed: {is_pressed}")
+ syslog.info(
+ f"REMOTE OUTPUT: send button: VJoyId: {device_id} button {button_id} pressed: {is_pressed}"
+ )
data = {}
data["sender"] = self._id
data["action"] = "button"
@@ -1120,14 +1121,16 @@ def send_button(self, device_id, button_id, is_pressed, force_remote = False):
data["value"] = is_pressed
raw_data = msgpack.packb(data)
self._send(raw_data)
- #syslog.debug(f"remote gremlin event set button: {device_id} {button_id} {is_pressed}")
+ # syslog.debug(f"remote gremlin event set button: {device_id} {button_id} {is_pressed}")
- def toggle_button(self, device_id, button_id, force_remote = False):
- ''' toggles a button '''
+ def toggle_button(self, device_id, button_id, force_remote=False):
+ """toggles a button"""
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
- syslog.info(f"REMOTE OUTPUT: toggle button: VJoyId: {device_id} button {button_id}")
+ syslog.info(
+ f"REMOTE OUTPUT: toggle button: VJoyId: {device_id} button {button_id}"
+ )
data = {}
data["sender"] = self._id
data["action"] = "toggle"
@@ -1135,14 +1138,18 @@ def toggle_button(self, device_id, button_id, force_remote = False):
data["target"] = button_id
raw_data = msgpack.packb(data)
self._send(raw_data)
- #syslog.debug(f"remote gremlin event toggle button: {device_id} {button_id}")
+ # syslog.debug(f"remote gremlin event toggle button: {device_id} {button_id}")
- def send_axis(self, device_id, axis_id, value, relative_value = None, force_remote = False):
- ''' handles a remote joystick event '''
+ def send_axis(
+ self, device_id, axis_id, value, relative_value=None, force_remote=False
+ ):
+ """handles a remote joystick event"""
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
- syslog.info(f"REMOTE OUTPUT: relative axis: VJoyId: {device_id} axis: {axis_id} value: {value:0.3f}")
+ syslog.info(
+ f"REMOTE OUTPUT: relative axis: VJoyId: {device_id} axis: {axis_id} value: {value:0.3f}"
+ )
data = {}
data["sender"] = self._id
data["action"] = "axis"
@@ -1152,14 +1159,16 @@ def send_axis(self, device_id, axis_id, value, relative_value = None, force_remo
data["relative_value"] = relative_value
raw_data = msgpack.packb(data)
self._send(raw_data)
- #syslog.debug(f"remote gremlin event set axis: {device_id} {axis_id} {value}")
+ # syslog.debug(f"remote gremlin event set axis: {device_id} {axis_id} {value}")
- def send_relative_axis(self, device_id, axis_id, value, force_remote = False):
- ''' handles a remote relative axis joystick event '''
+ def send_relative_axis(self, device_id, axis_id, value, force_remote=False):
+ """handles a remote relative axis joystick event"""
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
- syslog.info(f"REMOTE OUTPUT: relative axis: VJoyId: {device_id} axis: {axis_id} value: {value:0.3f}")
+ syslog.info(
+ f"REMOTE OUTPUT: relative axis: VJoyId: {device_id} axis: {axis_id} value: {value:0.3f}"
+ )
data = {}
data["sender"] = self._id
data["action"] = "relative_axis"
@@ -1169,13 +1178,15 @@ def send_relative_axis(self, device_id, axis_id, value, force_remote = False):
raw_data = msgpack.packb(data)
self._send(raw_data)
- def send_hat(self, device_id, hat_id, direction, force_remote = False):
- ''' handles a remote joystick event '''
+ def send_hat(self, device_id, hat_id, direction, force_remote=False):
+ """handles a remote joystick event"""
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
- syslog.info(f"REMOTE OUTPUT: VJoyId: {device_id} hat: {hat_id} direction: {direction}")
-
+ syslog.info(
+ f"REMOTE OUTPUT: VJoyId: {device_id} hat: {hat_id} direction: {direction}"
+ )
+
data = {}
data["sender"] = self._id
data["action"] = "hat"
@@ -1184,16 +1195,16 @@ def send_hat(self, device_id, hat_id, direction, force_remote = False):
data["value"] = direction
raw_data = msgpack.packb(data)
self._send(raw_data)
- #syslog.debug(f"remote gremlin event set hat: {device_id} {hat_id} {direction}")
+ # syslog.debug(f"remote gremlin event set hat: {device_id} {hat_id} {direction}")
- def send_key(self, virtual_code, scan_code, flags, force_remote = False):
- ''' handles a key event '''
+ def send_key(self, virtual_code, scan_code, flags, force_remote=False):
+ """handles a key event"""
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
code = int(scan_code)
syslog.info(f"REMOTE OUTPUT: key: 0x{code:02x} flags: 0x{flags:02x}")
-
+
data = {}
data["sender"] = self._id
data["action"] = "key"
@@ -1202,14 +1213,16 @@ def send_key(self, virtual_code, scan_code, flags, force_remote = False):
data["flags"] = flags
raw_data = msgpack.packb(data)
self._send(raw_data)
- #syslog.debug(f"remote gremlin event set key: virtual code: {virtual_code} scan code: {scan_code} flags: {flags}")
+ # syslog.debug(f"remote gremlin event set key: virtual code: {virtual_code} scan code: {scan_code} flags: {flags}")
- def send_mouse_button(self, button_id, is_pressed, force_remote = False):
- ''' sends a mouse button press or release '''
+ def send_mouse_button(self, button_id, is_pressed, force_remote=False):
+ """sends a mouse button press or release"""
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
- syslog.info(f"REMOTE OUTPUT: mouse button: {button_id} pressed: {is_pressed}")
+ syslog.info(
+ f"REMOTE OUTPUT: mouse button: {button_id} pressed: {is_pressed}"
+ )
data = {}
data["sender"] = self._id
data["action"] = "mouse"
@@ -1218,16 +1231,17 @@ def send_mouse_button(self, button_id, is_pressed, force_remote = False):
data["value"] = is_pressed
raw_data = msgpack.packb(data)
self._send(raw_data)
- #syslog.debug(f"remote gremlin event set mouse: button: {button_id} pressed: {is_pressed}")
+ # syslog.debug(f"remote gremlin event set mouse: button: {button_id} pressed: {is_pressed}")
-
- def send_mouse_button_double_click(self, button_id, is_pressed, force_remote = False):
- ''' sends a mouse button press or release '''
+ def send_mouse_button_double_click(self, button_id, is_pressed, force_remote=False):
+ """sends a mouse button press or release"""
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
- syslog.info(f"REMOTE OUTPUT: mouse dblclick {button_id} pressed: {is_pressed}")
-
+ syslog.info(
+ f"REMOTE OUTPUT: mouse dblclick {button_id} pressed: {is_pressed}"
+ )
+
data = {}
data["sender"] = self._id
data["action"] = "mouse"
@@ -1236,66 +1250,68 @@ def send_mouse_button_double_click(self, button_id, is_pressed, force_remote = F
data["value"] = is_pressed
raw_data = msgpack.packb(data)
self._send(raw_data)
- #syslog.debug(f"remote gremlin event set mouse: button: {button_id} pressed: {is_pressed}")
+ # syslog.debug(f"remote gremlin event set mouse: button: {button_id} pressed: {is_pressed}")
- def send_mouse_wheel(self, direction, force_remote = False):
- ''' sends mousewheel data '''
+ def send_mouse_wheel(self, direction, force_remote=False):
+ """sends mousewheel data"""
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
syslog.info(f"REMOTE OUTPUT: mouse wheel: {direction}")
-
+
data = {}
data["sender"] = self._id
data["action"] = "mouse"
data["subtype"] = "wheel"
data["direction"] = direction
-
+
raw_data = msgpack.packb(data)
self._send(raw_data)
- #syslog.debug(f"remote gremlin event set mouse: wheel {direction}")
+ # syslog.debug(f"remote gremlin event set mouse: wheel {direction}")
- def send_mouse_h_wheel(self, direction, force_remote = False):
- ''' sends horizontal mousewheel data '''
+ def send_mouse_h_wheel(self, direction, force_remote=False):
+ """sends horizontal mousewheel data"""
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
syslog.info(f"REMOTE OUTPUT: mouse H wheel: {direction}")
-
+
data = {}
data["sender"] = self._id
data["action"] = "mouse"
data["subtype"] = "hwheel"
data["direction"] = direction
-
+
raw_data = msgpack.packb(data)
self._send(raw_data)
- #syslog.debug(f"remote gremlin event set mouse: wheel {direction}")
+ # syslog.debug(f"remote gremlin event set mouse: wheel {direction}")
- def send_mouse_motion(self, dx, dy, force_remote = False):
- ''' sends mouse motion data '''
+ def send_mouse_motion(self, dx, dy, force_remote=False):
+ """sends mouse motion data"""
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
syslog.info(f"REMOTE OUTPUT: mouse motion: {dx}, {dy}")
-
+
data = {}
data["sender"] = self._id
data["action"] = "mouse"
data["subtype"] = "axis"
data["dx"] = dx
data["dy"] = dy
-
+
raw_data = msgpack.packb(data)
self._send(raw_data)
- #syslog.debug(f"remote gremlin event set mouse: axis {dx} {dy}")
+ # syslog.debug(f"remote gremlin event set mouse: axis {dx} {dy}")
- def send_mouse_motion_acceleration(self, a, min_speed, max_speed, time_to_max_speed, force_remote = False):
+ def send_mouse_motion_acceleration(
+ self, a, min_speed, max_speed, time_to_max_speed, force_remote=False
+ ):
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
- syslog.info(f"REMOTE OUTPUT: mouse motion acceleration")
-
+ syslog.info("REMOTE OUTPUT: mouse motion acceleration")
+
data = {}
data["sender"] = self._id
data["action"] = "mouse"
@@ -1307,30 +1323,34 @@ def send_mouse_motion_acceleration(self, a, min_speed, max_speed, time_to_max_sp
raw_data = msgpack.packb(data)
self._send(raw_data)
- def send_gamepad_axis(self, index, mode, value, force_remote = False):
- ''' sends a gamepad axis to the remote client '''
+ def send_gamepad_axis(self, index, mode, value, force_remote=False):
+ """sends a gamepad axis to the remote client"""
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
- syslog.info(f"REMOTE OUTPUT: gamepad axis: index: {index} mode: {mode} value: {value:0.3f}")
-
+ syslog.info(
+ f"REMOTE OUTPUT: gamepad axis: index: {index} mode: {mode} value: {value:0.3f}"
+ )
+
data = {}
data["sender"] = self._id
data["action"] = "gamepad"
data["subtype"] = "axis"
- data["index"] = index # which device to send to
+ data["index"] = index # which device to send to
data["mode"] = mode
data["value"] = value
raw_data = msgpack.packb(data)
self._send(raw_data)
-
- def send_gamepad_button(self, index, mode, is_pressed, force_remote = False):
- ''' sends a gamepad button to the remote client '''
+
+ def send_gamepad_button(self, index, mode, is_pressed, force_remote=False):
+ """sends a gamepad button to the remote client"""
if self.enabled or force_remote:
verbose = gremlin.config.Configuration().verbose_mode_outputs
if verbose:
- syslog.info(f"REMOTE OUTPUT: gamepad: index: {index} mode: {mode} pressed: {is_pressed}")
-
+ syslog.info(
+ f"REMOTE OUTPUT: gamepad: index: {index} mode: {mode} pressed: {is_pressed}"
+ )
+
data = {}
data["sender"] = self._id
data["action"] = "gamepad"
@@ -1341,22 +1361,15 @@ def send_gamepad_button(self, index, mode, is_pressed, force_remote = False):
raw_data = msgpack.packb(data)
self._send(raw_data)
-
@property
def enabled(self):
- ''' enables or disabled sending remote events'''
+ """enables or disabled sending remote events"""
return remote_state.is_remote
-
@property
def id(self):
return self._id
-
-
-
-
-
def register_callback(callback, device, input_type, input_id):
"""Adds a callback to the registry.
@@ -1378,19 +1391,15 @@ def register_callback(callback, device, input_type, input_id):
Index of the input on which to execute the callback
"""
event = gremlin.event_handler.Event(
- event_type=input_type,
- device_guid=device.device_guid,
- identifier=input_id
+ event_type=input_type, device_guid=device.device_guid, identifier=input_id
)
callback_registry.add(callback, event, device.mode, False)
class JoystickWrapper:
-
"""Wraps joysticks and presents an API similar to vjoy."""
class Input:
-
"""Represents a joystick input."""
def __init__(self, joystick_guid, index):
@@ -1403,7 +1412,6 @@ def __init__(self, joystick_guid, index):
self._index = index
class Axis(Input):
-
"""Represents a single axis of a joystick."""
def __init__(self, joystick_guid, index):
@@ -1416,7 +1424,6 @@ def value(self):
return DILL.get_axis(self._joystick_guid, self._index) / float(32768)
class Button(Input):
-
"""Represents a single button of a joystick."""
def __init__(self, joystick_guid, index):
@@ -1427,7 +1434,6 @@ def is_pressed(self):
return DILL.get_button(self._joystick_guid, self._index)
class Hat(Input):
-
"""Represents a single hat of a joystick,"""
def __init__(self, joystick_guid, index):
@@ -1436,13 +1442,14 @@ def __init__(self, joystick_guid, index):
@property
def direction(self):
import vjoy
+
value = gremlin.joystick_handling.get_hat(self._joystick_guid, self._index)
- if value in vjoy.vjoy.Hat.to_continuous_position:
+ if value in vjoy.vjoy.Hat.to_continuous_position:
position = vjoy.vjoy.Hat.to_continuous_position[value]
else:
- position = (0,0)
+ position = (0, 0)
return position
- #return gremlin.util.dill_hat_lookup(DILL.get_hat(self._joystick_guid, self._index))
+ # return gremlin.util.dill_hat_lookup(DILL.get_hat(self._joystick_guid, self._index))
def __init__(self, device_guid):
"""Creates a new wrapper object for the given object id.
@@ -1497,7 +1504,7 @@ def axis(self, index):
if index not in self._axis:
raise error.GremlinError(
f"Invalid axis {index} specified for device {self._device_guid}"
- )
+ )
return self._axis[index]
def button(self, index):
@@ -1568,9 +1575,11 @@ def _init_buttons(self):
:return list of JoystickWrapper.Button objects
"""
- buttons = [None,]
+ buttons = [
+ None,
+ ]
for i in range(self._info.button_count):
- buttons.append(JoystickWrapper.Button(self._device_guid, i+1))
+ buttons.append(JoystickWrapper.Button(self._device_guid, i + 1))
return buttons
def _init_hats(self):
@@ -1578,14 +1587,15 @@ def _init_hats(self):
:return list of JoystickWrapper.Hat objects
"""
- hats = [None,]
+ hats = [
+ None,
+ ]
for i in range(self._info.hat_count):
- hats.append(JoystickWrapper.Hat(self._device_guid, i+1))
+ hats.append(JoystickWrapper.Hat(self._device_guid, i + 1))
return hats
class JoystickProxy:
-
"""Allows read access to joystick state information."""
# Dictionary of initialized joystick devices
@@ -1607,23 +1617,21 @@ def __getitem__(self, device_guid):
joy = JoystickWrapper(device_guid)
JoystickProxy.joystick_devices[device_guid] = joy
else:
- syslog.warning(f"Requested device with guid {device_guid} not found in current hardware set")
+ syslog.warning(
+ f"Requested device with guid {device_guid} not found in current hardware set"
+ )
return None
-
return JoystickProxy.joystick_devices[device_guid]
class VJoyPlugin:
-
"""Plugin providing automatic access to the VJoyProxy object.
For a function to use this plugin it requires one of its parameters
to be named "vjoy".
"""
-
-
vjoy = gremlin.joystick_handling.VJoyProxy()
def __init__(self):
@@ -1644,7 +1652,6 @@ def install(self, callback, partial_fn):
class JoystickPlugin:
-
"""Plugin providing automatic access to the JoystickProxy object.
For a function to use this plugin it requires one of its parameters
@@ -1672,13 +1679,12 @@ def install(self, callback, partial_fn):
@gremlin.singleton_decorator.SingletonDecorator
class Keyboard(QtCore.QObject):
-
"""Provides access to the keyboard state."""
def __init__(self):
"""Initialises a new object."""
QtCore.QObject.__init__(self)
- self._keyboard_state = {} # holds the state of the keys
+ self._keyboard_state = {} # holds the state of the keys
@QtCore.Slot(object)
def keyboard_event(self, event):
@@ -1704,7 +1710,6 @@ def is_pressed(self, key):
class KeyboardPlugin:
-
"""Plugin providing automatic access to the Keyboard object.
For a function to use this plugin it requires one of its parameters
@@ -1728,7 +1733,6 @@ def install(self, callback, partial_fn):
class JoystickDecorator:
-
"""Creates customized decorators for physical joystick devices."""
def __init__(self, name, device_guid, mode):
@@ -1745,33 +1749,27 @@ def __init__(self, name, device_guid, mode):
try:
self.device_guid = gremlin.profile.parse_guid(device_guid)
except error.ProfileError:
- syslog.error(
- f"Invalid guid value '{device_guid}' received"
- )
+ syslog.error(f"Invalid guid value '{device_guid}' received")
self.device_guid = GUID_Invalid
- self.axis = functools.partial(
- _axis, device_guid=self.device_guid, mode=mode
- )
+ self.axis = functools.partial(_axis, device_guid=self.device_guid, mode=mode)
self.button = functools.partial(
_button, device_guid=self.device_guid, mode=mode
)
- self.hat = functools.partial(
- _hat, device_guid=self.device_guid, mode=mode
- )
+ self.hat = functools.partial(_hat, device_guid=self.device_guid, mode=mode)
class OscDecorator:
- ''' creates a decorator for OSC inputs '''
- def __init__(self, mode = "Default"):
+ """creates a decorator for OSC inputs"""
+ def __init__(self, mode="Default"):
self.mode = mode
- self.message = functools.partial(_osc, mode = mode)
+ self.message = functools.partial(_osc, mode=mode)
-def _osc(message, mode = "Default", always_execute=False):
- ''' decorator for osc callbacks '''
-
+def _osc(message, mode="Default", always_execute=False):
+ """decorator for osc callbacks"""
+
def wrap(callback):
import gremlin.ui.osc_device
import gremlin.input_types
@@ -1788,11 +1786,11 @@ def wrapper_fn(*args, **kwargs):
input_item.source_index = 0
event = gremlin.event_handler.Event(
- event_type = gremlin.input_types.InputType.OpenSoundControl,
- device_guid = gremlin.shared_state.osc_tab_guid,
- identifier = input_item
- )
-
+ event_type=gremlin.input_types.InputType.OpenSoundControl,
+ device_guid=gremlin.shared_state.osc_tab_guid,
+ identifier=input_item,
+ )
+
callback_registry.add(wrapper_fn, event, mode, always_execute)
return wrapper_fn
@@ -1800,18 +1798,11 @@ def wrapper_fn(*args, **kwargs):
return wrap
-
-
-
-
-ButtonReleaseEntry = collections.namedtuple(
- "Entry", ["callback", "event", "mode"]
-)
+ButtonReleaseEntry = collections.namedtuple("Entry", ["callback", "event", "mode"])
@gremlin.singleton_decorator.SingletonDecorator
class ButtonReleaseActions(QtCore.QObject):
-
"""Ensures a desired action is run when a button is released."""
def __init__(self):
@@ -1819,7 +1810,7 @@ def __init__(self):
QtCore.QObject.__init__(self)
self._registry = {}
- #self._registry_key_map = {} # map of event callback keys to the events
+ # self._registry_key_map = {} # map of event callback keys to the events
el = gremlin.event_handler.EventListener()
el.joystick_event.connect(self._input_event_cb)
el.keyboard_event.connect(self._input_event_cb)
@@ -1828,11 +1819,7 @@ def __init__(self):
el.runtime_mode_changed.connect(self._mode_changed_cb)
- def register_callback(
- self,
- callback: Callable[[], None],
- physical_event
- ) -> None:
+ def register_callback(self, callback: Callable[[], None], physical_event) -> None:
"""Registers a callback with the system.
Args:
@@ -1850,19 +1837,16 @@ def register_callback(
self._registry[key] = []
# Do not record the mode since we may want to run the release action
# independent of a mode
- self._registry[key].append(
- ButtonReleaseEntry(callback, release_evt, None)
- )
+ self._registry[key].append(ButtonReleaseEntry(callback, release_evt, None))
def register_button_release(
self,
vjoy_input: int,
physical_event,
activate_on: bool = False,
- is_local = True,
- is_remote = False,
- force_remote = False,
-
+ is_local=True,
+ is_remote=False,
+ force_remote=False,
):
"""Registers a physical and vjoy button pair for tracking.
@@ -1882,19 +1866,28 @@ def register_button_release(
key = release_evt.callbackKey
verbose = gremlin.config.Configuration().verbose_mode_outputs
- if verbose: syslog.info(f"AUTORELEASE: register autorelease key: {key} event: {str(release_evt)}")
+ if verbose:
+ syslog.info(
+ f"AUTORELEASE: register autorelease key: {key} event: {str(release_evt)}"
+ )
if release_evt not in self._registry:
self._registry[key] = []
- #self._registry_key_map[key] = release_evt
+ # self._registry_key_map[key] = release_evt
# Record current mode so we only release if we've changed mode
- self._registry[key].append(ButtonReleaseEntry(
- lambda: self._release_callback_prototype(vjoy_input, is_local, is_remote, force_remote),
- release_evt,
- self._current_mode
- ))
+ self._registry[key].append(
+ ButtonReleaseEntry(
+ lambda: self._release_callback_prototype(
+ vjoy_input, is_local, is_remote, force_remote
+ ),
+ release_evt,
+ self._current_mode,
+ )
+ )
- def _release_callback_prototype(self, vjoy_input: int, is_local = False, is_remote = False, force_remote = False) -> None:
+ def _release_callback_prototype(
+ self, vjoy_input: int, is_local=False, is_remote=False, force_remote=False
+ ) -> None:
"""Prototype of a button release callback, used with lambdas.
Args:
@@ -1906,14 +1899,16 @@ def _release_callback_prototype(self, vjoy_input: int, is_local = False, is_remo
if vjoy[vjoy_input[0]].is_button_valid(vjoy_input[1]):
if is_local:
vjoy[vjoy_input[0]].button(vjoy_input[1]).is_pressed = False
-
+
if is_remote or force_remote:
- remote_client.send_button(vjoy_input[0], vjoy_input[1], False, force_remote = force_remote )
-
+ remote_client.send_button(
+ vjoy_input[0], vjoy_input[1], False, force_remote=force_remote
+ )
+
else:
syslog.warning(
- f"Attempted to use non existent button: " +
- f"vJoy {vjoy_input[0]:d} button {vjoy_input[1]:d}"
+ "Attempted to use non existent button: "
+ + f"vJoy {vjoy_input[0]:d} button {vjoy_input[1]:d}"
)
def _input_event_cb(self, event):
@@ -1922,19 +1917,18 @@ def _input_event_cb(self, event):
Args:
event: the event to process
"""
-
+
verbose = gremlin.config.Configuration().verbose_mode_outputs
key = event.callbackKey
-
+
if key in self._registry:
- if verbose: syslog.info(f"AUTORELEASE: execute trigger : {key}")
+ if verbose:
+ syslog.info(f"AUTORELEASE: execute trigger : {key}")
new_list = []
for entry in self._registry[key]:
-
if entry.event.is_pressed == event.is_pressed:
try:
-
entry.callback()
except:
pass
@@ -1952,15 +1946,13 @@ def _mode_changed_cb(self, mode):
@gremlin.singleton_decorator.SingletonDecorator
class JoystickInputSignificant:
-
"""Checks whether or not joystick inputs are significant."""
def __init__(self):
"""Initializes the instance."""
self.reset()
-
- def should_process(self, event, deviation = 0.1) -> bool:
+ def should_process(self, event, deviation=0.1) -> bool:
"""Returns whether or not a particular event is significant enough to
process.
@@ -1971,6 +1963,7 @@ def should_process(self, event, deviation = 0.1) -> bool:
True if the event should be processed, False otherwise
"""
from gremlin.input_types import InputType
+
self._mre_registry[event.callbackKey] = event
match event.event_type:
@@ -2007,12 +2000,11 @@ def reset(self) -> None:
self._event_registry = {}
self._mre_registry = {}
self._time_registry = {}
-
- def should_process_axis(self, event, deviation = 0.1) -> bool:
+ def should_process_axis(self, event, deviation=0.1) -> bool:
return self._process_axis(event, deviation)
- def _process_axis(self, event, deviation = 0.1) -> bool:
+ def _process_axis(self, event, deviation=0.1) -> bool:
"""Process an axis event.
Args:
@@ -2031,14 +2023,14 @@ def _process_axis(self, event, deviation = 0.1) -> bool:
# Update state
else:
self._time_registry[key] = time.time()
-
+
if abs(self._event_registry[key].value - event.value) > deviation:
self._event_registry[key] = event
self._time_registry[key] = time.time()
- #print (f"axis move: {abs(self._event_registry[key].value - event.value)} deviation: {deviation} TRUE")
+ # print (f"axis move: {abs(self._event_registry[key].value - event.value)} deviation: {deviation} TRUE")
return True
else:
- #print (f"axis move: {abs(self._event_registry[key].value - event.value)} deviation: {deviation} FALSE")
+ # print (f"axis move: {abs(self._event_registry[key].value - event.value)} deviation: {deviation} FALSE")
return False
else:
self._event_registry[key] = event
@@ -2079,7 +2071,6 @@ def _button(button_id, device_guid, mode, always_execute=False):
"""
def wrap(callback):
-
@functools.wraps(callback)
def wrapper_fn(*args, **kwargs):
callback(*args, **kwargs)
@@ -2087,7 +2078,7 @@ def wrapper_fn(*args, **kwargs):
event = gremlin.event_handler.Event(
event_type=gremlin.input_types.InputType.JoystickButton,
device_guid=device_guid,
- identifier=button_id
+ identifier=button_id,
)
callback_registry.add(wrapper_fn, event, mode, always_execute)
@@ -2107,7 +2098,6 @@ def _hat(hat_id, device_guid, mode, always_execute=False):
"""
def wrap(callback):
-
@functools.wraps(callback)
def wrapper_fn(*args, **kwargs):
callback(*args, **kwargs)
@@ -2115,7 +2105,7 @@ def wrapper_fn(*args, **kwargs):
event = gremlin.event_handler.Event(
event_type=gremlin.input_types.InputType.JoystickHat,
device_guid=device_guid,
- identifier=hat_id
+ identifier=hat_id,
)
callback_registry.add(wrapper_fn, event, mode, always_execute)
@@ -2135,7 +2125,6 @@ def _axis(axis_id, device_guid, mode, always_execute=False):
"""
def wrap(callback):
-
@functools.wraps(callback)
def wrapper_fn(*args, **kwargs):
callback(*args, **kwargs)
@@ -2143,7 +2132,7 @@ def wrapper_fn(*args, **kwargs):
event = gremlin.event_handler.Event(
event_type=gremlin.input_types.InputType.JoystickAxis,
device_guid=device_guid,
- identifier=axis_id
+ identifier=axis_id,
)
callback_registry.add(wrapper_fn, event, mode, always_execute)
@@ -2152,7 +2141,9 @@ def wrapper_fn(*args, **kwargs):
return wrap
-''' KEYBOARD DECORATOR '''
+""" KEYBOARD DECORATOR """
+
+
def keyboard(key_name, mode, always_execute=False):
"""Decorator for keyboard key callbacks.
@@ -2163,7 +2154,6 @@ def keyboard(key_name, mode, always_execute=False):
"""
def wrap(callback):
-
@functools.wraps(callback)
def wrapper_fn(*args, **kwargs):
callback(*args, **kwargs)
@@ -2177,10 +2167,9 @@ def wrapper_fn(*args, **kwargs):
return wrap
+""" PERIODIC DECORATOR """
-
-''' PERIODIC DECORATOR '''
def periodic(interval):
"""Decorator for periodic function callbacks.
@@ -2188,7 +2177,6 @@ def periodic(interval):
"""
def wrap(callback):
-
@functools.wraps(callback)
def wrapper_fn(*args, **kwargs):
callback(*args, **kwargs)
@@ -2200,27 +2188,32 @@ def wrapper_fn(*args, **kwargs):
return wrap
+""" PROFILE START DECORATOR """
+
-''' PROFILE START DECORATOR '''
def gremlin_start():
- ''' decorator when a profile is activated '''
- def wrap(callback):
+ """decorator when a profile is activated"""
+ def wrap(callback):
@functools.wraps(callback)
def wrapper_fn(*args, **kwargs):
callback(*args, **kwargs)
- vjoy = gremlin.joystick_handling.VJoyProxy()
+
+ gremlin.joystick_handling.VJoyProxy()
start_registry.add(wrapper_fn)
return wrapper_fn
return wrap
-''' PROFILE STOP DECORATOR '''
+
+""" PROFILE STOP DECORATOR """
+
+
def gremlin_stop():
- ''' decorator when a profile is de-activated '''
- def wrap(callback):
+ """decorator when a profile is de-activated"""
+ def wrap(callback):
@functools.wraps(callback)
def wrapper_fn(*args, **kwargs):
callback(*args, **kwargs)
@@ -2231,9 +2224,13 @@ def wrapper_fn(*args, **kwargs):
return wrap
-''' PROFILE MODE DECORATOR'''
+
+""" PROFILE MODE DECORATOR"""
+
+
def gremlin_mode():
- ''' decorator when gremlin changes profile modes - passes the new mode to the plugin '''
+ """decorator when gremlin changes profile modes - passes the new mode to the plugin"""
+
def wrap(callback):
@functools.wraps(callback)
def wrapper_fn(*args, **kwargs):
@@ -2245,9 +2242,13 @@ def wrapper_fn(*args, **kwargs):
return wrap
-''' STATE DECORATOR '''
+
+""" STATE DECORATOR """
+
+
def gremlin_state():
- ''' decorator when gremlin changes states local or remote or both '''
+ """decorator when gremlin changes states local or remote or both"""
+
def wrap(callback):
@functools.wraps(callback)
def wrapper_fn(*args, **kwargs):
@@ -2260,9 +2261,6 @@ def wrapper_fn(*args, **kwargs):
return wrap
-
-
-
def squash(value, func):
"""Returns the appropriate function value when the function is
squashed to [-1, 1].
@@ -2298,7 +2296,6 @@ def deadzone(value, low, low_center, high_center, high):
high_center = 0.0
if high is None:
high = 1.0
-
if value >= 0:
return min(1, max(0, (value - high_center) / abs(high - high_center)))
@@ -2325,19 +2322,14 @@ def format_input(event) -> str:
# Retrieve device name
label = ""
if device is None:
- logging.warning(
- f"Unable to find a device with GUID {str(event.device_guid)}"
- )
+ logging.warning(f"Unable to find a device with GUID {str(event.device_guid)}")
label = "Unknown"
else:
label = device.name
# Retrive input name
label += " - "
- label += gremlin.common.input_to_ui_string(
- event.event_type,
- event.identifier
- )
+ label += gremlin.common.input_to_ui_string(event.event_type, event.identifier)
return label
@@ -2368,5 +2360,3 @@ def format_input(event) -> str:
# Global remote client = sends events to server
remote_client = RemoteClient()
-
-
diff --git a/gremlin/input_types.py b/gremlin/input_types.py
index 46ea7e55..8123dd7f 100644
--- a/gremlin/input_types.py
+++ b/gremlin/input_types.py
@@ -1,7 +1,6 @@
-
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,8 +18,8 @@
from __future__ import annotations
import enum
-class InputType(enum.IntEnum):
+class InputType(enum.IntEnum):
"""Enumeration of possible input types."""
NotSet = 0
@@ -30,11 +29,10 @@ class InputType(enum.IntEnum):
JoystickHat = 4
Mouse = 5
VirtualButton = 6
- KeyboardLatched = 7 # latched keyboard input
- OpenSoundControl = 8 # open sound control button input
- Midi = 9 # midi input
- ModeControl = 10 # mode actions
-
+ KeyboardLatched = 7 # latched keyboard input
+ OpenSoundControl = 8 # open sound control button input
+ Midi = 9 # midi input
+ ModeControl = 10 # mode actions
@staticmethod
def to_string(value):
@@ -56,9 +54,11 @@ def to_enum(value):
return _InputType_to_enum_lookup[value]
except KeyError:
raise ValueError("Invalid type in lookup")
-
+
@staticmethod
- def to_list(include_notset = False, include_mouse = False, include_virtualbutton = False) -> list:
+ def to_list(
+ include_notset=False, include_mouse=False, include_virtualbutton=False
+ ) -> list:
data = [it for it in InputType]
if not include_notset:
data.remove(InputType.NotSet)
@@ -67,13 +67,13 @@ def to_list(include_notset = False, include_mouse = False, include_virtualbutton
if not include_virtualbutton:
data.remove(InputType.VirtualButton)
return data
-
+
@staticmethod
def to_display_name(value) -> str:
if value in _InputType_to_display_lookup.keys():
return _InputType_to_string_lookup[value]
return f"Unknown type: {value}"
-
+
@staticmethod
def convert(value) -> InputType:
try:
@@ -83,11 +83,11 @@ def convert(value) -> InputType:
if value in _InputType_to_string_lookup.keys():
input_type = InputType(value)
return input_type
-
+
except:
pass
return None
-
+
# JSON serializer
@@ -100,7 +100,7 @@ def convert(value) -> InputType:
InputType.KeyboardLatched: "keylatched",
InputType.OpenSoundControl: "osc",
InputType.Midi: "midi",
- InputType.ModeControl : "modecontrol"
+ InputType.ModeControl: "modecontrol",
}
_InputType_to_display_lookup = {
@@ -111,7 +111,7 @@ def convert(value) -> InputType:
InputType.KeyboardLatched: "Latched Key",
InputType.OpenSoundControl: "OSC Button",
InputType.Midi: "MIDI",
- InputType.ModeControl: "Mode Control"
+ InputType.ModeControl: "Mode Control",
}
@@ -124,6 +124,5 @@ def convert(value) -> InputType:
"keylatched": InputType.KeyboardLatched,
"osc": InputType.OpenSoundControl,
"midi": InputType.Midi,
- "modecontrol" : InputType.ModeControl
+ "modecontrol": InputType.ModeControl,
}
-
diff --git a/gremlin/joystick_handling.py b/gremlin/joystick_handling.py
index ceebcb06..0419038c 100644
--- a/gremlin/joystick_handling.py
+++ b/gremlin/joystick_handling.py
@@ -29,13 +29,13 @@
-from . import common, error, util
+from . import error, util
from vjoy import vjoy
from dinput import DeviceSummary
from gremlin.input_types import InputType
import gremlin.config
-from PySide6 import QtWidgets, QtCore, QtGui
+from PySide6 import QtWidgets, QtCore
# List of all joystick devices
_joystick_devices = [] # all devices
@@ -267,7 +267,7 @@ def registerSpecialDevice(dev : DeviceSummary):
assert (_joystick_initialized)
device_guid = dev.device_guid
device_id = dev.device_id
- if not device_id in _joystick_device_guid_map:
+ if device_id not in _joystick_device_guid_map:
_joystick_device_guid_map[device_guid] = dev
_joystick_device_guid_map[device_id] = dev
verbose = gremlin.config.Configuration().verbose_mode_details
@@ -298,7 +298,6 @@ def known_devices() -> list:
def device_info_from_guid(device_guid : str | dinput.GUID) -> DeviceSummary:
''' gets physical device information '''
- import gremlin.shared_state
assert (_joystick_initialized)
if device_guid in _joystick_device_guid_map:
return _joystick_device_guid_map[device_guid]
@@ -389,7 +388,7 @@ def _scan_dinput():
for device_index in range(device_count):
dev = dinput.DILL.get_device_information_by_index(device_index)
- if not dev.device_guid in _joystick_device_guid_map:
+ if dev.device_guid not in _joystick_device_guid_map:
syslog.info(f"\tindex: [{device_index}] {str(dev)}")
_joystick_devices.append(dev)
_joystick_device_guid_map[dev.device_guid] = dev # key by GUID
@@ -413,7 +412,7 @@ def joystick_devices_initialization():
_joystick_initialized = False
- verbose = gremlin.config.Configuration().verbose_mode_inputs
+ gremlin.config.Configuration().verbose_mode_inputs
_joystick_init_lock.acquire()
@@ -437,7 +436,7 @@ def joystick_devices_initialization():
syslog.info(f"INIT: {device_count} hardware devices detected:")
dinput.DILL.dumpDevices()
else:
- syslog.warning(f"INIT: DirectX reports no hardware devices detected")
+ syslog.warning("INIT: DirectX reports no hardware devices detected")
# Process all connected devices in order to properly initialize the device registry
@@ -572,13 +571,13 @@ def joystick_devices_initialization():
if hash_value in vjoy_lookup:
try:
# register the vjoy device with the proxy
- vjoy_dev = vjoy_proxy[vjoy_index]
- except error.VJoyError as e:
+ vjoy_proxy[vjoy_index]
+ except error.VJoyError:
syslog.debug(f"vJoy id {vjoy_index:} can't be acquired")
if not should_terminate:
if len(_joystick_device_guid_map) == 0:
- syslog.error(f"Error (fatal): no usable VJOY devices found.")
+ syslog.error("Error (fatal): no usable VJOY devices found.")
should_terminate = True
if should_terminate:
@@ -614,7 +613,7 @@ def joystick_devices_update():
# add missing items
for device_index in range(device_count):
dev = dinput.DILL.get_device_information_by_index(device_index)
- if not dev.device_guid in _joystick_device_guid_map:
+ if dev.device_guid not in _joystick_device_guid_map:
_joystick_devices.append(dev)
_joystick_device_guid_map[dev.device_guid] = dev # key by GUID
_joystick_device_guid_map[dev.device_id] = dev # key by string ID
@@ -734,11 +733,11 @@ def ensure_profile(self):
def ensure_valid(self, vjoy_id : int, input_id : int):
''' checks vjoy button mapping exists '''
- if not vjoy_id in self._button_usage:
+ if vjoy_id not in self._button_usage:
# create it
self._button_usage[vjoy_id] = {}
self._button_usage_map[vjoy_id] = {}
- if not input_id in self._button_usage[vjoy_id]:
+ if input_id not in self._button_usage[vjoy_id]:
self._button_usage[vjoy_id][input_id] = False
self._button_usage_map[vjoy_id][input_id] = []
@@ -777,21 +776,21 @@ def ensure_vjoy(self, force_update = False):
def _ensure_maps(self, device_guid, input_id):
''' automatically registers new inputs if needed '''
- if not device_guid in self._axis_invert_map:
+ if device_guid not in self._axis_invert_map:
self._axis_invert_map[device_guid] = {}
- if not input_id in self._axis_invert_map[device_guid]:
+ if input_id not in self._axis_invert_map[device_guid]:
self._axis_invert_map[device_guid][input_id] = False
- if not device_guid in self._axis_range_map:
+ if device_guid not in self._axis_range_map:
self._axis_range_map[device_guid] = {}
- if not input_id in self._axis_range_map[device_guid]:
+ if input_id not in self._axis_range_map[device_guid]:
self._axis_range_map[device_guid][input_id] = [-1.0, 1.0]
- if not device_guid in self._button_usage:
+ if device_guid not in self._button_usage:
self._button_usage[device_guid] = {}
self._button_usage_map[device_guid] = {}
- if not input_id in self._button_usage[device_guid]:
+ if input_id not in self._button_usage[device_guid]:
self._button_usage[device_guid][input_id] = False
self._button_usage_map[device_guid][input_id] = []
@@ -919,7 +918,7 @@ def set_usage_state(self, vjoy_id : int, button_id : int, action, state : bool,
self.ensure_vjoy()
self.ensure_valid(vjoy_id, button_id) # create entry if needed
if state:
- if not action in self._button_usage_map[vjoy_id][button_id]:
+ if action not in self._button_usage_map[vjoy_id][button_id]:
self._button_usage_map[vjoy_id][button_id].append(action)
else:
# remove the data
@@ -954,7 +953,7 @@ def used_list(self, device_id, input_type):
unused_list = self._free_inputs[device_id][name]
count = self.get_count(device_id, input_type)
if count > 0:
- return [id for id in range(1, count+1) if not id in unused_list]
+ return [id for id in range(1, count+1) if id not in unused_list]
return []
def unused_list(self, device_id, input_type):
@@ -1073,12 +1072,12 @@ def extract_remap_actions(action_sets):
vjoy_device_id = action.vjoy_device_id
vjoy_input_id = action.vjoy_input_id
- if not vjoy_device_id in action_map.keys():
+ if vjoy_device_id not in action_map.keys():
action_map[vjoy_device_id] = {}
- if not input_type in action_map[vjoy_device_id].keys():
+ if input_type not in action_map[vjoy_device_id].keys():
action_map[vjoy_device_id][input_type] = {}
- if not vjoy_input_id in action_map[vjoy_device_id][input_type].keys():
+ if vjoy_input_id not in action_map[vjoy_device_id][input_type].keys():
action_map[vjoy_device_id][input_type][vjoy_input_id] = []
action_map[vjoy_device_id][input_type][vjoy_input_id].append([dev.device_guid, dev.name, input_type, input_id])
diff --git a/gremlin/keyboard.py b/gremlin/keyboard.py
index 870b9406..f452c998 100644
--- a/gremlin/keyboard.py
+++ b/gremlin/keyboard.py
@@ -16,7 +16,6 @@
# along with this program. If not, see .
-
import ctypes
import logging
from ctypes import wintypes
@@ -26,6 +25,7 @@
# from gremlin.base_classes import TraceableList
from gremlin.types import MouseButton
+
# from gremlin.singleton_decorator import SingletonDecorator
import gremlin.config
@@ -33,6 +33,7 @@
user32 = ctypes.WinDLL("user32")
syslog = logging.getLogger("system")
+
def _create_function(lib_name, fn_name, param_types, return_type):
"""Creates a handle to a windows dll library function.
@@ -50,18 +51,12 @@ def _create_function(lib_name, fn_name, param_types, return_type):
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms646296(v=vs.85).aspx
_get_keyboard_layout = _create_function(
- "user32",
- "GetKeyboardLayout",
- [wintypes.DWORD],
- wintypes.HKL
+ "user32", "GetKeyboardLayout", [wintypes.DWORD], wintypes.HKL
)
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms646299(v=vs.85).aspx
_get_keyboard_state = _create_function(
- "user32",
- "GetKeyboardState",
- [ctypes.POINTER(ctypes.c_char)],
- wintypes.BOOL
+ "user32", "GetKeyboardState", [ctypes.POINTER(ctypes.c_char)], wintypes.BOOL
)
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms646307(v=vs.85).aspx
@@ -69,7 +64,7 @@ def _create_function(lib_name, fn_name, param_types, return_type):
"user32",
"MapVirtualKeyExW",
[ctypes.c_uint, ctypes.c_uint, wintypes.HKL],
- ctypes.c_uint
+ ctypes.c_uint,
)
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms646322(v=vs.85).aspx
@@ -83,32 +78,33 @@ def _create_function(lib_name, fn_name, param_types, return_type):
ctypes.POINTER(ctypes.c_wchar),
ctypes.c_int,
ctypes.c_uint,
- ctypes.c_void_p
+ ctypes.c_void_p,
],
- ctypes.c_int
+ ctypes.c_int,
)
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms646332(v=vs.85).aspx
_vk_key_scan_ex = _create_function(
- "user32",
- "VkKeyScanExW",
- [ctypes.c_wchar, wintypes.HKL],
- ctypes.c_short
+ "user32", "VkKeyScanExW", [ctypes.c_wchar, wintypes.HKL], ctypes.c_short
)
-
+class Key:
+ """Represents a single key on the keyboard or mouse together with its different representations and latching (multiple keys)
+
+ If in mouse mode, the virtual code contains the MouseButton enum value for which mouse button this key corresponds to.
-class Key():
- """Represents a single key on the keyboard or mouse together with its different representations and latching (multiple keys)
-
- If in mouse mode, the virtual code contains the MouseButton enum value for which mouse button this key corresponds to.
-
-
"""
- def __init__(self, name = None, scan_code : int = None, is_extended : bool = None, virtual_code : int = None, is_mouse : bool = False):
+ def __init__(
+ self,
+ name=None,
+ scan_code: int = None,
+ is_extended: bool = None,
+ virtual_code: int = None,
+ is_mouse: bool = False,
+ ):
"""Creates a new Key instance.
:param name the name used to refer to this key
@@ -125,7 +121,7 @@ def __init__(self, name = None, scan_code : int = None, is_extended : bool = Non
scan_code = 0
if virtual_code is None:
virtual_code = 0
- if is_extended is None:
+ if is_extended is None:
is_extended = False
if is_mouse is None:
is_mouse = False
@@ -133,9 +129,9 @@ def __init__(self, name = None, scan_code : int = None, is_extended : bool = Non
self._lookup_name = None
self._latched_code = ""
self._latched_name = ""
-
- self._latched_keys = [] # TraceableList() #[] # list of keys latched to this keystroke (modifiers)
- # self._latched_keys.add_callback(self._changed_cb)
+
+ self._latched_keys = [] # TraceableList() #[] # list of keys latched to this keystroke (modifiers)
+ # self._latched_keys.add_callback(self._changed_cb)
self._name = None
self._key_id_translate = None
@@ -144,13 +140,12 @@ def __init__(self, name = None, scan_code : int = None, is_extended : bool = Non
if not name:
self._load(scan_code, is_extended, virtual_code, is_mouse)
name = self._name
-
+
else:
- self._key_id = (scan_code, is_extended)
+ self._key_id = (scan_code, is_extended)
self._scan_code = scan_code
self._is_extended = is_extended
- self._virtual_code = virtual_code
-
+ self._virtual_code = virtual_code
self._name = name
self._is_mouse = is_mouse
@@ -159,37 +154,36 @@ def __init__(self, name = None, scan_code : int = None, is_extended : bool = Non
@property
def key_id(self) -> tuple:
return self._key_id
-
+
@property
def key_id_translated(self) -> tuple:
if not self._key_id_translate and self._key_id:
self._key_id_translate, _ = KeyMap.translate(self._key_id)
return self._key_id_translate
-
@property
def virtual_code(self):
return self._virtual_code
-
@property
def scan_code(self):
return self._scan_code
-
+
@property
def is_extended(self):
return self._is_extended
-
+
def index_tuple(self):
- ''' returns the gremlin index key for this key '''
- return (self._scan_code, self._is_extended)
-
+ """returns the gremlin index key for this key"""
+ return (self._scan_code, self._is_extended)
+
@property
def is_mouse(self):
return self._is_mouse
-
- def _load(self, scan_code : int, is_extended : bool, virtual_code : int, is_mouse : bool):
+ def _load(
+ self, scan_code: int, is_extended: bool, virtual_code: int, is_mouse: bool
+ ):
self._mouse_button = None
name = None
if is_mouse:
@@ -201,18 +195,17 @@ def _load(self, scan_code : int, is_extended : bool, virtual_code : int, is_mous
mouse_button = mouse_from_name(name)
if not mouse_button:
raise ValueError(f"Don't know how to handle mouse button name: {name}")
- scan_code = mouse_button.value + 0x1000 # makes it unique in the tuple
+ scan_code = mouse_button.value + 0x1000 # makes it unique in the tuple
virtual_code = scan_code
name = MouseButton.to_string(mouse_button)
self._lookup_name = name
- if name and len(name)==1:
+ if name and len(name) == 1:
name = name.upper()
self._name = name
is_mouse = True
self._mouse_button = mouse_button
-
else:
# regular key
@@ -220,8 +213,6 @@ def _load(self, scan_code : int, is_extended : bool, virtual_code : int, is_mous
scan_code = scan_code & 0xFF
is_extended = True
-
-
if virtual_code > 0 and scan_code == 0:
# get scan code from VK
scan_code, is_extended = KeyMap.find_virtual(virtual_code)
@@ -236,20 +227,20 @@ def _load(self, scan_code : int, is_extended : bool, virtual_code : int, is_mous
self._scan_code = scan_code
self._is_extended = is_extended
self._virtual_code = virtual_code
-
- self._update()
+ self._update()
# duplicate
def duplicate(self):
- '''' creates a copy of this key '''
+ """' creates a copy of this key"""
import copy
+
new_key = copy.deepcopy(self)
return new_key
@property
def sequence(self):
- ''' returns a list of (scan_code, extended) tuples for all latched keys in this sequence '''
+ """returns a list of (scan_code, extended) tuples for all latched keys in this sequence"""
sequence = [self.index_tuple()]
lk: Key
for lk in self._latched_keys:
@@ -258,12 +249,12 @@ def sequence(self):
@property
def mouse_button(self):
- ''' returns a mouse button if the key is a virtual mouse button or mouse wheel '''
+ """returns a mouse button if the key is a virtual mouse button or mouse wheel"""
return self._mouse_button
-
+
@mouse_button.setter
def mouse_button(self, button):
- ''' sets a mouse button '''
+ """sets a mouse button"""
scan_code = button.value + 0x1000
self._mouse_button = button
self._is_mouse = True
@@ -275,14 +266,13 @@ def mouse_button(self, button):
# self._update()
def _update(self):
-
if len(self._latched_keys) > 0:
keys = [self]
keys.extend(self._latched_keys)
- # order the key by modifier
+ # order the key by modifier
keys = sort_keys(keys)
result = ""
- code = ""
+ code = ""
for key in keys:
if result:
result += " + "
@@ -291,21 +281,15 @@ def _update(self):
result += key._name
code += f"0x{key._scan_code:X}({key._scan_code:02}){' EX' if key._is_extended else ''}"
self._latched_name = result
- else:
+ else:
code = f"0x{self._scan_code:02X}({self._scan_code:02}){' EX' if self._is_extended else ''}"
self._latched_name = ""
self._latched_code = code
-
-
-
-
-
-
@property
def name(self):
return self._name
-
+
@property
def latched_name(self):
return self._latched_name if self._latched_name else self._name
@@ -314,18 +298,18 @@ def latched_name(self):
def latched_code(self):
return self._latched_code
-
@property
def lookup_name(self):
if self._lookup_name is not None:
return self._lookup_name
else:
- return self._name
+ return self._name
@property
def latched(self):
- ''' returns true if the current latch keys are pressed (runtime only) '''
+ """returns true if the current latch keys are pressed (runtime only)"""
from gremlin.event_handler import EventListener
+
el = EventListener()
# assume the current key is pressed
latched = el.get_key_state(self)
@@ -336,37 +320,35 @@ def latched(self):
# one key isn't pressed = not latched
return False
- #syslog.info(f"latch check: key {self.name} latched: {latched}")
+ # syslog.info(f"latch check: key {self.name} latched: {latched}")
return latched
-
-
@property
def is_latched(self):
- ''' returns true if the key has latched components '''
+ """returns true if the key has latched components"""
return len(self._latched_keys) > 0
-
+
@property
def state(self):
- ''' returns the pressed state of the current key '''
+ """returns the pressed state of the current key"""
from gremlin.event_handler import EventListener
+
el = EventListener()
return el.get_key_state(self)
-
+
@property
def data(self):
# unique key for this key
return self.__hash__()
-
+
@property
def message_key(self):
return {self._scan_code, self._is_extended}
-
+
@property
def debug_name(self):
return f"{self.name} (0x{self._scan_code:02X}/{self._scan_code}{' EX' if self._is_extended else ''}]"
-
def __eq__(self, other):
return hash(self) == hash(other)
@@ -375,7 +357,7 @@ def __ne__(self, other):
def __hash__(self):
# computes the hash value for this key combination
- #if self._latched_keys:
+ # if self._latched_keys:
data = f"{self._scan_code:x}{1 if self._is_extended else 0}"
for key in self._latched_keys:
data += f"|{key._scan_code:x}{1 if key._is_extended else 0}"
@@ -385,83 +367,78 @@ def __hash__(self):
# return (0x0E << 8) + self._scan_code
# else:
# return self._scan_code
-
+
def __lt__(self, other):
return self.name < other.name
-
+
def __le__(self, other):
return self.name <= other.name
-
+
def __gt__(self, other):
return self.name > other.name
-
+
def __ge__(self, other):
return self.name > other.name
-
+
def __str__(self):
return self.name
-
-
-
@property
def latched_keys(self):
- ''' list of key objects that are latched to this key (modifiers)'''
+ """list of key objects that are latched to this key (modifiers)"""
return self._latched_keys
-
+
@latched_keys.setter
def latched_keys(self, value):
self._latched_keys.clear()
self._latched_keys.extend(value)
self._update()
-
+
@property
def is_latched(self):
- ''' true if this key is latched '''
+ """true if this key is latched"""
return len(self._latched_keys) > 0
-
+
@property
def is_modifier(self):
- ''' true if the key is a modifier '''
+ """true if the key is a modifier"""
return self._lookup_name in KeyMap._keyboard_modifiers
-
+
def modifier_order(self):
- ''' returns the order of the modifier '''
+ """returns the order of the modifier"""
lookup_name = self.lookup_name
modifiers = KeyMap._keyboard_modifiers
if lookup_name in modifiers:
return modifiers.index(lookup_name)
- return -1 # not found
-
+ return -1 # not found
+
def key_order(self):
- ''' gets a unique and predictable key index for ordering a key sequence
-
+ """gets a unique and predictable key index for ordering a key sequence
+
Modifiers will be a lower index than normal character which will be lower than special keys
-
- '''
+
+ """
lookup_name = self.lookup_name.lower()
if lookup_name in KeyMap._keyboard_modifiers:
return self.modifier_order()
-
+
# bump to next index
start_index = 100
-
+
if len(lookup_name) == 1:
# single keys - use the ascii sequence
value = ord(lookup_name)
return start_index + value
-
+
start_index = 1000
# special keys
special = KeyMap._keyboard_special
if lookup_name in special:
value = special.index(lookup_name)
return start_index + value
-
+
# no clue
return -1
-
-
def send_key_down(key):
@@ -470,17 +447,20 @@ def send_key_down(key):
:param key the key for which to send the KEYDOWN event
"""
from gremlin import input_devices
+
key: gremlin.keyboard.Key
flags = win32con.KEYEVENTF_EXTENDEDKEY if key.is_extended else 0
verbose = gremlin.config.Configuration().verbose_mode_outputs
-
+
is_local, is_remote = input_devices.remote_state.state
if is_local:
- if verbose: syslog.info(f"OUTPUT: (local) keydown {key.debug_name}")
+ if verbose:
+ syslog.info(f"OUTPUT: (local) keydown {key.debug_name}")
win32api.keybd_event(key.virtual_code, key.scan_code, flags, 0)
if is_remote:
- if verbose: syslog.info(f"OUTPUT: (remote) keydown {key.debug_name}")
- input_devices.remote_client.send_key(key.virtual_code, key.scan_code, flags )
+ if verbose:
+ syslog.info(f"OUTPUT: (remote) keydown {key.debug_name}")
+ input_devices.remote_client.send_key(key.virtual_code, key.scan_code, flags)
def send_key_up(key):
@@ -490,48 +470,88 @@ def send_key_up(key):
"""
from gremlin import input_devices
+
key: gremlin.keyboard.Key
verbose = gremlin.config.Configuration().verbose_mode_outputs
flags = win32con.KEYEVENTF_EXTENDEDKEY if key.is_extended else 0
flags |= win32con.KEYEVENTF_KEYUP
-
(is_local, is_remote) = input_devices.remote_state.state
if is_local:
- if verbose: syslog.info(f"OUTPUT: (local) keyup {key.debug_name}")
+ if verbose:
+ syslog.info(f"OUTPUT: (local) keyup {key.debug_name}")
win32api.keybd_event(key.virtual_code, key.scan_code, flags, 0)
if is_remote:
- if verbose: syslog.info(f"OUTPUT: (remote) keyup {key.debug_name}")
- input_devices.remote_client.send_key(key.virtual_code, key.scan_code, flags )
+ if verbose:
+ syslog.info(f"OUTPUT: (remote) keyup {key.debug_name}")
+ input_devices.remote_client.send_key(key.virtual_code, key.scan_code, flags)
+
def mouse_from_name(name):
- ''' validates if this is a special mouse key - returns None if it is not'''
+ """validates if this is a special mouse key - returns None if it is not"""
from gremlin.types import MouseButton
+
mouse_button = None
name = name.lower()
- if name in ("mouse_1", "mouse_left", MouseButton.to_string(MouseButton.Left).lower()):
+ if name in (
+ "mouse_1",
+ "mouse_left",
+ MouseButton.to_string(MouseButton.Left).lower(),
+ ):
mouse_button = MouseButton.Left
- elif name in ("mouse_2", "mouse_right", MouseButton.to_string(MouseButton.Middle).lower()):
+ elif name in (
+ "mouse_2",
+ "mouse_right",
+ MouseButton.to_string(MouseButton.Middle).lower(),
+ ):
mouse_button = MouseButton.Middle
- elif name in ("mouse_3", "mouse_middle", MouseButton.to_string(MouseButton.Right).lower()):
+ elif name in (
+ "mouse_3",
+ "mouse_middle",
+ MouseButton.to_string(MouseButton.Right).lower(),
+ ):
mouse_button = MouseButton.Right
- elif name in ("mouse_4", "mouse_forward", MouseButton.to_string(MouseButton.Forward).lower()):
+ elif name in (
+ "mouse_4",
+ "mouse_forward",
+ MouseButton.to_string(MouseButton.Forward).lower(),
+ ):
mouse_button = MouseButton.Forward
- elif name in ("mouse_5", "mouse_back", MouseButton.to_string(MouseButton.Back).lower()):
+ elif name in (
+ "mouse_5",
+ "mouse_back",
+ MouseButton.to_string(MouseButton.Back).lower(),
+ ):
mouse_button = MouseButton.Back
- elif name in ("mouse_up", "wheel_up", MouseButton.to_string(MouseButton.WheelUp).lower()):
+ elif name in (
+ "mouse_up",
+ "wheel_up",
+ MouseButton.to_string(MouseButton.WheelUp).lower(),
+ ):
mouse_button = MouseButton.WheelUp
- elif name in ("mouse_down", "wheel_down", MouseButton.to_string(MouseButton.WheelDown).lower()):
+ elif name in (
+ "mouse_down",
+ "wheel_down",
+ MouseButton.to_string(MouseButton.WheelDown).lower(),
+ ):
mouse_button = MouseButton.WheelDown
- elif name in ("mouse_wleft", "wheel_left", MouseButton.to_string(MouseButton.WheelLeft).lower()):
+ elif name in (
+ "mouse_wleft",
+ "wheel_left",
+ MouseButton.to_string(MouseButton.WheelLeft).lower(),
+ ):
mouse_button = MouseButton.WheelLeft
- elif name in ("mouse_wright", "wheel_right", MouseButton.to_string(MouseButton.WheelRight).lower()):
+ elif name in (
+ "mouse_wright",
+ "wheel_right",
+ MouseButton.to_string(MouseButton.WheelRight).lower(),
+ ):
mouse_button = MouseButton.WheelRight
return mouse_button
-def key_from_name(name, validate = False) -> Key:
+def key_from_name(name, validate=False) -> Key:
"""Returns the key corresponding to the provided name.
If no key exists with the provided name None is returned.
@@ -549,12 +569,11 @@ def key_from_name(name, validate = False) -> Key:
scan_code = 0x1000 + mouse_button.value
is_extended = False
key = Key(key_name, scan_code, is_extended, 0, is_mouse=True)
- return key
+ return key
# Attempt to located the key in our database and return it if successful
key_name = name.lower().replace(" ", "")
-
key = KeyMap.find_by_name(key_name)
if key is not None:
return key
@@ -565,45 +584,39 @@ def key_from_name(name, validate = False) -> Key:
if validate:
# skip error reporting on validation
return None
-
- syslog.warning(
- f"Invalid key name specified \"{name}\""
- )
- raise error.KeyboardError(
- f"Invalid key specified, {name}"
- )
+
+ syslog.warning(f'Invalid key name specified "{name}"')
+ raise error.KeyboardError(f"Invalid key specified, {name}")
else:
- KeyMap.register(key)
+ KeyMap.register(key)
return key
def sort_keys(keys):
- ''' sorts a list of keys so the keys are in a predictable order '''
+ """sorts a list of keys so the keys are in a predictable order"""
key: Key
sequence = []
for key in keys:
index = key.key_order()
sequence.append((key, index))
-
- sequence.sort(key = lambda x: x[1])
+
+ sequence.sort(key=lambda x: x[1])
keys_list = [pair[0] for pair in sequence]
return keys_list
-def key_name_from_code(scan_code, is_extended):
- ''' gets the key name '''
+def key_name_from_code(scan_code, is_extended):
+ """gets the key name"""
if scan_code >= 0x1000:
scan_code -= 0x1000
return MouseButton.to_string(scan_code)
-
-
-
+
# Attempt to located the key in our database and return it if successful
key = KeyMap.find(scan_code, is_extended)
if key:
return key.name
-
+
# Attempt to create the key to store and return if successful
virtual_code = KeyMap.scan_code_to_virtual_code(scan_code, is_extended)
@@ -611,8 +624,6 @@ def key_name_from_code(scan_code, is_extended):
return name
-
-
def key_from_code(scan_code, is_extended):
"""Returns the key corresponding to the provided scan code.
@@ -622,24 +633,23 @@ def key_from_code(scan_code, is_extended):
:param is_extended flag indicating if the key is extended
:return Key instance or None
"""
-
+
import copy
if scan_code >= 0x1000:
# mouse special code
- key = Key(scan_code = scan_code, is_mouse = True)
+ key = Key(scan_code=scan_code, is_mouse=True)
return key
-
+
# Attempt to located the key in our database and return it if successful
key = KeyMap.find(scan_code, is_extended)
if key is not None:
return copy.deepcopy(key)
-
-
+
# Attempt to create the key to store and return if successful
virtual_code = KeyMap.scan_code_to_virtual_code(scan_code, is_extended)
name = KeyMap.virtual_input_to_unicode(virtual_code)
-
+
if virtual_code == 0xFF or name is None:
syslog.warning(
f"Invalid scan code specified ({scan_code} (0x{scan_code:x}), {is_extended})"
@@ -652,12 +662,11 @@ def key_from_code(scan_code, is_extended):
key = Key(name, scan_code, is_extended, virtual_code)
KeyMap.register(key)
return key
-
-
class scan_codes(enum.IntEnum):
- ''' windows scan codes lookup '''
+ """windows scan codes lookup"""
+
sc_escape = 0x01
sc_1 = 0x02
sc_2 = 0x03
@@ -741,16 +750,16 @@ class scan_codes(enum.IntEnum):
sc_numpad_3 = 0x51
sc_numpad_0 = 0x52
sc_numpad_period = 0x53
- sc_alt_printScreen = 0x54 # Alt + print screen. MapVirtualKeyEx( VK_SNAPSHOT MAPVK_VK_TO_VSC_EX 0 ) returns scancode 0x54. */
- sc_bracketAngle = 0x56 # Key between the left shift and Z. */
+ sc_alt_printScreen = 0x54 # Alt + print screen. MapVirtualKeyEx( VK_SNAPSHOT MAPVK_VK_TO_VSC_EX 0 ) returns scancode 0x54. */
+ sc_bracketAngle = 0x56 # Key between the left shift and Z. */
sc_f11 = 0x57
sc_f12 = 0x58
- sc_oem_1 = 0x5a # VK_OEM_WSCTRL */
- sc_oem_2 = 0x5b # VK_OEM_FINISH */
- sc_oem_3 = 0x5c # VK_OEM_JUMP */
- sc_eraseEOF = 0x5d
- sc_oem_4 = 0x5e # VK_OEM_BACKTAB */
- sc_oem_5 = 0x5f # VK_OEM_AUTO */
+ sc_oem_1 = 0x5A # VK_OEM_WSCTRL */
+ sc_oem_2 = 0x5B # VK_OEM_FINISH */
+ sc_oem_3 = 0x5C # VK_OEM_JUMP */
+ sc_eraseEOF = 0x5D
+ sc_oem_4 = 0x5E # VK_OEM_BACKTAB */
+ sc_oem_5 = 0x5F # VK_OEM_AUTO */
sc_zoom = 0x62
sc_help = 0x63
sc_f13 = 0x64
@@ -759,18 +768,18 @@ class scan_codes(enum.IntEnum):
sc_f16 = 0x67
sc_f17 = 0x68
sc_f18 = 0x69
- sc_f19 = 0x6a
- sc_f20 = 0x6b
- sc_f21 = 0x6c
- sc_f22 = 0x6d
- sc_f23 = 0x6e
- sc_oem_6 = 0x6f # VK_OEM_PA3 */
+ sc_f19 = 0x6A
+ sc_f20 = 0x6B
+ sc_f21 = 0x6C
+ sc_f22 = 0x6D
+ sc_f23 = 0x6E
+ sc_oem_6 = 0x6F # VK_OEM_PA3 */
sc_katakana = 0x70
- sc_oem_7 = 0x71 # VK_OEM_RESET */
+ sc_oem_7 = 0x71 # VK_OEM_RESET */
sc_f24 = 0x76
sc_sbcschar = 0x77
sc_convert = 0x79
- sc_nonconvert = 0x7B # VK_OEM_PA1 */
+ sc_nonconvert = 0x7B # VK_OEM_PA1 */
sc_media_previous = 0xE010
sc_media_next = 0xE019
@@ -791,9 +800,9 @@ class scan_codes(enum.IntEnum):
# - break: 0xE0B7 0xE0AA
# - MapVirtualKeyEx( VK_SNAPSHOT MAPVK_VK_TO_VSC_EX 0 ) returns scancode 0x54;
# - There is no VK_KEYDOWN with VK_SNAPSHOT.
-
+
sc_altRight = 0xE038
- sc_cancel = 0xE046 # CTRL + Pause */
+ sc_cancel = 0xE046 # CTRL + Pause */
sc_home = 0xE047
sc_arrowUp = 0xE048
sc_pageUp = 0xE049
@@ -821,7 +830,7 @@ class scan_codes(enum.IntEnum):
sc_launch_media = 0xE06D
sc_pause = 0xE11D45
-
+
# sc_pause:
# - make: 0xE11D 45 0xE19D C5
# - make in raw input: 0xE11D 0x45
@@ -829,26 +838,22 @@ class scan_codes(enum.IntEnum):
# - No repeat when you hold the key down
# - There are no break so I don't know how the key down/up is expected to work. Raw input sends "keydown" and "keyup" messages and it appears that the keyup message is sent directly after the keydown message (you can't hold the key down) so depending on when GetMessage or PeekMessage will return messages you may get both a keydown and keyup message "at the same time". If you use VK messages most of the time you only get keydown messages but some times you get keyup messages too.
# - when pressed at the same time as one or both control keys generates a 0xE046 (sc_cancel) and the string for that scancode is "break".
-
-
class KeyMap:
-
- _g_virtual_code_to_key = {} # map of keyboard virtual codes to the key
- _g_scan_code_to_key = {} # map of (scancode, extended) to the key
+ _g_virtual_code_to_key = {} # map of keyboard virtual codes to the key
+ _g_scan_code_to_key = {} # map of (scancode, extended) to the key
_key_map = {}
-
@staticmethod
def register(key):
assert key.lookup_name
-
+
if key.virtual_code > 0:
KeyMap._g_virtual_code_to_key[key.virtual_code] = key
-
+
index = (key.scan_code, key.is_extended)
- if not index in KeyMap._g_scan_code_to_key.keys():
+ if index not in KeyMap._g_scan_code_to_key.keys():
KeyMap._g_scan_code_to_key[index] = key
if key.name:
name = key.lookup_name.lower().replace(" ", "")
@@ -857,39 +862,38 @@ def register(key):
@staticmethod
def find(scan_code, is_extended):
- ''' does a key lookup by scan code and extended key status '''
+ """does a key lookup by scan code and extended key status"""
lookup = (scan_code, is_extended)
if lookup in KeyMap._g_map:
return KeyMap._g_map[lookup].duplicate()
- if not lookup in KeyMap._g_scan_code_to_key.keys():
+ if lookup not in KeyMap._g_scan_code_to_key.keys():
# see if we can add it
key = KeyMap.get_key(scan_code, is_extended)
if key:
- KeyMap._g_scan_code_to_key[lookup] = key
- return key.duplicate()
+ KeyMap._g_scan_code_to_key[lookup] = key
+ return key.duplicate()
return None
key = KeyMap._g_scan_code_to_key.get((scan_code, is_extended), None)
if key:
return key.duplicate()
return None
-
+
@staticmethod
def find_virtual(virtual_code):
if virtual_code in KeyMap._g_virtual_code_to_key.keys():
return KeyMap._g_virtual_code_to_key[virtual_code].duplicate()
return None
-
@staticmethod
def find_by_name(name):
- name = name.replace(" ","").lower()
+ name = name.replace(" ", "").lower()
if name in KeyMap._key_map:
return KeyMap._key_map[name].duplicate()
return None
-
+
@staticmethod
def from_event(event):
- ''' returns a key based on a keyboard event '''
+ """returns a key based on a keyboard event"""
try:
if event:
scan_code = event.identifier[0]
@@ -898,15 +902,15 @@ def from_event(event):
if not key and event.virtual_code > 0:
key = KeyMap.find_virtual(event.virtual_code)
if key is None:
- syslog.error(f"KEY: Don't know how to handle event: 0x{scan_code:02X} ({scan_code}) extended: {extended}")
+ syslog.error(
+ f"KEY: Don't know how to handle event: 0x{scan_code:02X} ({scan_code}) extended: {extended}"
+ )
return key
except Exception as ex:
syslog.error(f"KEY: error: invalid event: {ex}")
syslog.error("KEY: invalid event")
return None
-
-
@staticmethod
def scan_code_to_virtual_code(scan_code, is_extended):
"""Returns the virtual code corresponding to the given scan code.
@@ -919,22 +923,22 @@ def scan_code_to_virtual_code(scan_code, is_extended):
"""
- if scan_code:
+ if scan_code:
value = scan_code
if is_extended:
- value = 0xe0 << 8 | scan_code
+ value = 0xE0 << 8 | scan_code
virtual_code = _map_virtual_key_ex(value, 3, _get_keyboard_layout(0))
return virtual_code
return None
-
+
@staticmethod
def virtual_code_to_scan_code(virtual_code):
- scan_code = _map_virtual_key_ex(virtual_code, 4,_get_keyboard_layout(0))
+ scan_code = _map_virtual_key_ex(virtual_code, 4, _get_keyboard_layout(0))
return scan_code
@staticmethod
- def virtual_input_to_unicode(virtual_code, scan_code = 0):
+ def virtual_input_to_unicode(virtual_code, scan_code=0):
"""Returns the unicode character corresponding to a given virtual code.
:param virtual_code virtual code for which to return a unicode character
@@ -950,7 +954,7 @@ def virtual_input_to_unicode(virtual_code, scan_code = 0):
# Translate three times to get around dead keys showing up in funny ways
# as the translation takes them into account for future keys
- for _ in range (5):
+ for _ in range(5):
state = _to_unicode_ex(
virtual_code,
scan_code,
@@ -958,7 +962,7 @@ def virtual_input_to_unicode(virtual_code, scan_code = 0):
output_buffer,
8,
0,
- keyboard_layout
+ keyboard_layout,
)
if state > 0:
break
@@ -966,16 +970,21 @@ def virtual_input_to_unicode(virtual_code, scan_code = 0):
if state == 0:
# resolve manually
name = None
- for p_name, p_scan_code, p_extended, p_virtual_code in KeyMap._g_name_map.values():
+ for (
+ p_name,
+ p_scan_code,
+ p_extended,
+ p_virtual_code,
+ ) in KeyMap._g_name_map.values():
if scan_code == p_scan_code and virtual_code == p_virtual_code:
name = p_name
break
-
+
if not name:
name = f"Key 0x{scan_code:02X}({scan_code:02}) VK {virtual_code:X}))"
return name
return output_buffer.value.upper()
-
+
@staticmethod
def get_key(scan_code, is_extended):
virtual_code = KeyMap.scan_code_to_virtual_code(scan_code, is_extended)
@@ -983,7 +992,6 @@ def get_key(scan_code, is_extended):
name = KeyMap.virtual_input_to_unicode(virtual_code, scan_code)
return Key(name, scan_code, is_extended, virtual_code)
return None
-
@staticmethod
def unicode_to_key(character):
@@ -1004,17 +1012,14 @@ def unicode_to_key(character):
is_extended = False
if code_value << 8 & 0xE0 or code_value << 8 & 0xE1:
is_extended = True
- return Key(character, scan_code, is_extended, virtual_code)
-
-
-
+ return Key(character, scan_code, is_extended, virtual_code)
@staticmethod
def get_latched_key(keys):
- ''' derives a single latched key from a set of keys'''
+ """derives a single latched key from a set of keys"""
modifier_map = {}
- modifiers = gremlin.keyboard.KeyMap._keyboard_modifiers # ["leftshift","leftcontrol","leftalt","rightshift","rightcontrol","rightalt","leftwin","rightwin"]
+ modifiers = gremlin.keyboard.KeyMap._keyboard_modifiers # ["leftshift","leftcontrol","leftalt","rightshift","rightcontrol","rightalt","leftwin","rightwin"]
for key_name in modifiers:
modifier_map[key_name] = []
@@ -1042,34 +1047,33 @@ def get_latched_key(keys):
return_key = None
if return_key:
- latched = list(set(keys)) # remove any duplicates
- latched.remove(return_key) # remove self
+ latched = list(set(keys)) # remove any duplicates
+ latched.remove(return_key) # remove self
return_key.latched_keys = latched
return return_key
-
@staticmethod
def translate(keyid) -> tuple:
- ''' translates a key id and returns a list of equivalent keys
- this is to map similar keys together
- :param keyid (scan_code, is_extended)
- :returns ((scan_code, is_extended), virtual_code)
- '''
+ """translates a key id and returns a list of equivalent keys
+ this is to map similar keys together
+ :param keyid (scan_code, is_extended)
+ :returns ((scan_code, is_extended), virtual_code)
+ """
# flip the extended bit to force numlock OFF for numeric keypad so we always get the numeric keys
scan_code, is_extended = keyid
if keyid in KeyMap._g_translate_map.keys():
return KeyMap._g_translate_map[keyid]
vk = KeyMap.scan_code_to_virtual_code(scan_code, is_extended)
return (keyid, vk)
-
+
@staticmethod
def translate_lookup(key_tuple) -> tuple:
- ''' translates a key id and returns a list of equivalent keys
- this is to map similar keys together
- :param keyid (scan_code, is_extended)
- :returns (scan_code, is_extended)
- '''
+ """translates a key id and returns a list of equivalent keys
+ this is to map similar keys together
+ :param keyid (scan_code, is_extended)
+ :returns (scan_code, is_extended)
+ """
# flip the extended bit to force numlock OFF for numeric keypad so we always get the numeric keys
if key_tuple in KeyMap._g_map:
return key_tuple
@@ -1077,52 +1081,49 @@ def translate_lookup(key_tuple) -> tuple:
key_trans, _ = KeyMap._g_translate_map[key_tuple]
return key_trans
return key_tuple
-
+
@staticmethod
def reverse_translate(key_tuple) -> tuple:
- if not KeyMap._g_reverse_translate_map:
- for key_id, data in KeyMap._g_translate_map.items():
- t_key, vk = data
- KeyMap._g_reverse_translate_map[t_key] = key_id
-
- if key_tuple in KeyMap._g_reverse_translate_map:
- return KeyMap._g_reverse_translate_map[key_tuple]
-
-
-
- return None
-
-
-
+ if not KeyMap._g_reverse_translate_map:
+ for key_id, data in KeyMap._g_translate_map.items():
+ t_key, vk = data
+ KeyMap._g_reverse_translate_map[t_key] = key_id
+
+ if key_tuple in KeyMap._g_reverse_translate_map:
+ return KeyMap._g_reverse_translate_map[key_tuple]
+
+ return None
+
@staticmethod
def vk_lookup(key_tuple):
- ''' gets a virtual key from (scancode, extended) '''
+ """gets a virtual key from (scancode, extended)"""
scan_code, is_extended = key_tuple
vk = KeyMap.scan_code_to_virtual_code(scan_code, is_extended)
return vk
-
-
+
@staticmethod
def keyid_tostring(keyid):
scan_code, is_extended = keyid
return f"({scan_code} 0x{scan_code:X}, {is_extended})"
-
+
@staticmethod
def get_vk_keyboard_state(virtual_code):
- ''' get the hardware keyboard state by virtual key '''
- return (user32.GetAsyncKeyState(virtual_code) & 1) != 0 # true if the key is pressed
-
+ """get the hardware keyboard state by virtual key"""
+ return (
+ user32.GetAsyncKeyState(virtual_code) & 1
+ ) != 0 # true if the key is pressed
+
@staticmethod
def get_keyboard_state(scan_code, is_extended):
- ''' gets the hardware keyboard state by scan code '''
+ """gets the hardware keyboard state by scan code"""
virtual_code = KeyMap.scan_code_to_virtual_code(scan_code, is_extended)
return KeyMap.get_vk_keyboard_state(virtual_code)
-
+
@staticmethod
def numlock_state():
- ''' gets the state of the numlock key '''
+ """gets the state of the numlock key"""
return KeyMap.get_vk_keyboard_state(win32con.VK_NUMLOCK)
-
+
@staticmethod
def set_numlock_state(value):
state = KeyMap.numlock_state()
@@ -1132,52 +1133,57 @@ def set_numlock_state(value):
@staticmethod
def toggle_numlock():
import gremlin.sendinput
+
# key down
- flags = win32con.KEYEVENTF_EXTENDEDKEY
+ flags = win32con.KEYEVENTF_EXTENDEDKEY
gremlin.sendinput.send_key(win32con.VK_NUMLOCK, 0x45, flags)
# key up
flags |= win32con.KEYEVENTF_KEYUP
gremlin.sendinput.send_key(win32con.VK_NUMLOCK, 0x45, flags)
-
# holds the number pad scan codes
- _g_numpad_codes = (win32con.VK_NUMPAD0,
- win32con.VK_NUMPAD1,
- win32con.VK_NUMPAD2,
- win32con.VK_NUMPAD3,
- win32con.VK_NUMPAD4,
- win32con.VK_NUMPAD5,
- win32con.VK_NUMPAD6,
- win32con.VK_NUMPAD7,
- win32con.VK_NUMPAD8,
- win32con.VK_NUMPAD9,
- )
-
+ _g_numpad_codes = (
+ win32con.VK_NUMPAD0,
+ win32con.VK_NUMPAD1,
+ win32con.VK_NUMPAD2,
+ win32con.VK_NUMPAD3,
+ win32con.VK_NUMPAD4,
+ win32con.VK_NUMPAD5,
+ win32con.VK_NUMPAD6,
+ win32con.VK_NUMPAD7,
+ win32con.VK_NUMPAD8,
+ win32con.VK_NUMPAD9,
+ )
+
_g_translate_map = {
- (0x52,True): ((0x52, False), win32con.VK_NUMPAD0), # make all numpad keys report as numpad
- (0x4F,True): ((0x4F, False), win32con.VK_NUMPAD1),
- (0x50,True): ((0x50, True), win32con.VK_DOWN), # down arrow
- (0x51,True): ((0x51, False), win32con.VK_NUMPAD3),
- (0x4B,True): ((0x4B, True), win32con.VK_LEFT), # left arrow
- (0x4C,True): ((0x4C, False), win32con.VK_NUMPAD5),
- (0x4D,True): ((0x4D, True), win32con.VK_RIGHT), # right arrow
- (0x47,True): ((0x47, False), win32con.VK_NUMPAD7),
- (0x48,True): ((0x48, True), win32con.VK_UP), # up arrow
- (0x49,True): ((0x49, False), win32con.VK_NUMPAD9),
-
- (0x52,False): ((0x52, False), win32con.VK_NUMPAD0),
- (0x4F,False): ((0x4F, False), win32con.VK_NUMPAD1),
- (0x50,False): ((0x50, False), win32con.VK_NUMPAD2),
- (0x51,False): ((0x51, False), win32con.VK_NUMPAD3),
- (0x4B,False): ((0x4B, False), win32con.VK_NUMPAD4),
- (0x4C,False): ((0x4C, False), win32con.VK_NUMPAD5),
- (0x4D,False): ((0x4D, False), win32con.VK_NUMPAD6),
- (0x47,False): ((0x47, False), win32con.VK_NUMPAD7),
- (0x48,False): ((0x48, False), win32con.VK_NUMPAD8),
- (0x49,False): ((0x49, False), win32con.VK_NUMPAD9),
-
- (0x36,True): ((0x36, False), win32con.VK_RSHIFT), # combine rshift and rshift 2
+ (0x52, True): (
+ (0x52, False),
+ win32con.VK_NUMPAD0,
+ ), # make all numpad keys report as numpad
+ (0x4F, True): ((0x4F, False), win32con.VK_NUMPAD1),
+ (0x50, True): ((0x50, True), win32con.VK_DOWN), # down arrow
+ (0x51, True): ((0x51, False), win32con.VK_NUMPAD3),
+ (0x4B, True): ((0x4B, True), win32con.VK_LEFT), # left arrow
+ (0x4C, True): ((0x4C, False), win32con.VK_NUMPAD5),
+ (0x4D, True): ((0x4D, True), win32con.VK_RIGHT), # right arrow
+ (0x47, True): ((0x47, False), win32con.VK_NUMPAD7),
+ (0x48, True): ((0x48, True), win32con.VK_UP), # up arrow
+ (0x49, True): ((0x49, False), win32con.VK_NUMPAD9),
+ (0x52, False): ((0x52, False), win32con.VK_NUMPAD0),
+ (0x4F, False): ((0x4F, False), win32con.VK_NUMPAD1),
+ (0x50, False): ((0x50, False), win32con.VK_NUMPAD2),
+ (0x51, False): ((0x51, False), win32con.VK_NUMPAD3),
+ (0x4B, False): ((0x4B, False), win32con.VK_NUMPAD4),
+ (0x4C, False): ((0x4C, False), win32con.VK_NUMPAD5),
+ (0x4D, False): ((0x4D, False), win32con.VK_NUMPAD6),
+ (0x47, False): ((0x47, False), win32con.VK_NUMPAD7),
+ (0x48, False): ((0x48, False), win32con.VK_NUMPAD8),
+ (0x49, False): ((0x49, False), win32con.VK_NUMPAD9),
+ (0x36, True): (
+ (0x36, False),
+ win32con.VK_RSHIFT,
+ ), # combine rshift and rshift 2
}
_g_reverse_translate_map = {}
@@ -1186,11 +1192,11 @@ def toggle_numlock():
_g_name_map = {
# Function keys (name, scancode, extended, virtual code)
- "f1": ("F1", 0x3b, False, win32con.VK_F1),
- "f2": ("F2", 0x3c, False, win32con.VK_F2),
- "f3": ("F3", 0x3d, False, win32con.VK_F3),
- "f4": ("F4", 0x3e, False, win32con.VK_F4),
- "f5": ("F5", 0x3f, False, win32con.VK_F5),
+ "f1": ("F1", 0x3B, False, win32con.VK_F1),
+ "f2": ("F2", 0x3C, False, win32con.VK_F2),
+ "f3": ("F3", 0x3D, False, win32con.VK_F3),
+ "f4": ("F4", 0x3E, False, win32con.VK_F4),
+ "f5": ("F5", 0x3F, False, win32con.VK_F5),
"f6": ("F6", 0x40, False, win32con.VK_F6),
"f7": ("F7", 0x41, False, win32con.VK_F7),
"f8": ("F8", 0x42, False, win32con.VK_F8),
@@ -1204,12 +1210,12 @@ def toggle_numlock():
"f16": ("F16", 0x67, False, win32con.VK_F16),
"f17": ("F17", 0x68, False, win32con.VK_F17),
"f18": ("F18", 0x69, False, win32con.VK_F18),
- "f19": ("F19", 0x6a, False, win32con.VK_F19),
- "f20": ("F20", 0x6b, False, win32con.VK_F20),
- "f21": ("F21", 0x6c, False, win32con.VK_F21),
- "f22": ("F22", 0x6d, False, win32con.VK_F22),
- "f23": ("F23", 0x6e, False, win32con.VK_F23),
- "f24": ("F24", 0x76, False, win32con.VK_F24),
+ "f19": ("F19", 0x6A, False, win32con.VK_F19),
+ "f20": ("F20", 0x6B, False, win32con.VK_F20),
+ "f21": ("F21", 0x6C, False, win32con.VK_F21),
+ "f22": ("F22", 0x6D, False, win32con.VK_F22),
+ "f23": ("F23", 0x6E, False, win32con.VK_F23),
+ "f24": ("F24", 0x76, False, win32con.VK_F24),
# Control keys
"printscreen": ("Print Screen", 0x37, True, win32con.VK_PRINT),
"scrolllock": ("Scroll Lock", 0x46, False, win32con.VK_SCROLL),
@@ -1219,55 +1225,67 @@ def toggle_numlock():
"home": ("Home", 0x47, True, win32con.VK_HOME),
"pageup": ("PageUp", 0x49, True, win32con.VK_PRIOR),
"delete": ("Delete", 0x53, True, win32con.VK_DELETE),
- "end": ("End", 0x4f, True, win32con.VK_END),
+ "end": ("End", 0x4F, True, win32con.VK_END),
"pagedown": ("PageDown", 0x51, True, win32con.VK_NEXT),
# Arrow keys
"up": ("Up", 0x48, True, win32con.VK_UP),
- "left": ("Left", 0x4b, True, win32con.VK_LEFT),
+ "left": ("Left", 0x4B, True, win32con.VK_LEFT),
"down": ("Down", 0x50, True, win32con.VK_DOWN),
- "right": ("Right", 0x4d, True, win32con.VK_RIGHT),
+ "right": ("Right", 0x4D, True, win32con.VK_RIGHT),
# Numpad
"numlock": ("NumLock", 0x45, True, win32con.VK_NUMLOCK),
"npdivide": ("Numpad / (FSlash)", 0x35, True, win32con.VK_DIVIDE),
"npmultiply": ("Numpad *", 0x37, False, win32con.VK_MULTIPLY),
- "npminus": ("Numpad -", 0x4a, False, win32con.VK_SUBTRACT),
- "npplus": ("Numpad +", 0x4e, False, win32con.VK_ADD),
- "npenter": ("Numpad Enter", 0x1c, True, win32con.VK_SEPARATOR),
+ "npminus": ("Numpad -", 0x4A, False, win32con.VK_SUBTRACT),
+ "npplus": ("Numpad +", 0x4E, False, win32con.VK_ADD),
+ "npenter": ("Numpad Enter", 0x1C, True, win32con.VK_SEPARATOR),
"npdelete": ("Numpad Delete", 0x53, False, win32con.VK_DECIMAL),
"np0": ("Numpad 0", 0x52, False, win32con.VK_NUMPAD0),
- "np1": ("Numpad 1", 0x4f, False, win32con.VK_NUMPAD1),
+ "np1": ("Numpad 1", 0x4F, False, win32con.VK_NUMPAD1),
"np2": ("Numpad 2", 0x50, False, win32con.VK_NUMPAD2),
"np3": ("Numpad 3", 0x51, False, win32con.VK_NUMPAD3),
- "np4": ("Numpad 4", 0x4b, False, win32con.VK_NUMPAD4),
- "np5": ("Numpad 5", 0x4c, False, win32con.VK_NUMPAD5),
- "np6": ("Numpad 6", 0x4d, False, win32con.VK_NUMPAD6),
+ "np4": ("Numpad 4", 0x4B, False, win32con.VK_NUMPAD4),
+ "np5": ("Numpad 5", 0x4C, False, win32con.VK_NUMPAD5),
+ "np6": ("Numpad 6", 0x4D, False, win32con.VK_NUMPAD6),
"np7": ("Numpad 7", 0x47, False, win32con.VK_NUMPAD7),
"np8": ("Numpad 8", 0x48, False, win32con.VK_NUMPAD8),
"np9": ("Numpad 9", 0x49, False, win32con.VK_NUMPAD9),
# Misc keys
- "backspace": ("Backspace", 0x0e, False, win32con.VK_BACK),
+ "backspace": ("Backspace", 0x0E, False, win32con.VK_BACK),
"space": ("Space", 0x39, False, win32con.VK_SPACE),
- "tab": ("Tab", 0x0f, False, win32con.VK_TAB),
- "capslock": ("CapsLock", 0x3a, False, win32con.VK_CAPITAL),
- "leftshift": ("Left Shift", 0x2a, False, win32con.VK_LSHIFT),
- "leftcontrol": ("Left Control", 0x1d, False, win32con.VK_LCONTROL),
- "leftwin": ("Left Win", 0x5b, True, win32con.VK_LWIN),
+ "tab": ("Tab", 0x0F, False, win32con.VK_TAB),
+ "capslock": ("CapsLock", 0x3A, False, win32con.VK_CAPITAL),
+ "leftshift": ("Left Shift", 0x2A, False, win32con.VK_LSHIFT),
+ "leftcontrol": ("Left Control", 0x1D, False, win32con.VK_LCONTROL),
+ "leftwin": ("Left Win", 0x5B, True, win32con.VK_LWIN),
"leftalt": ("Left Alt", 0x38, False, win32con.VK_LMENU),
# Right shift key appears to exist in both extended and
# non-extended version
"rightshift": ("Right Shift", 0x36, True, win32con.VK_RSHIFT),
- #"rightshift2": ("Right Shift", 0x36, True, win32con.VK_RSHIFT),
- "rightcontrol": ("Right Control", 0x1d, True, win32con.VK_RCONTROL),
- "rightwin": ("Right Win", 0x5c, True, win32con.VK_RWIN),
+ # "rightshift2": ("Right Shift", 0x36, True, win32con.VK_RSHIFT),
+ "rightcontrol": ("Right Control", 0x1D, True, win32con.VK_RCONTROL),
+ "rightwin": ("Right Win", 0x5C, True, win32con.VK_RWIN),
"rightalt": ("Right Alt", 0x38, True, win32con.VK_RMENU),
"rightalt2": ("Right Alt", 0x38, True, win32con.VK_RMENU),
- "apps": ("Apps", 0x5d, True, win32con.VK_APPS),
- "enter": ("Enter", 0x1c, False, win32con.VK_RETURN),
- "esc": ("Esc", 0x01, False, win32con.VK_ESCAPE)
+ "apps": ("Apps", 0x5D, True, win32con.VK_APPS),
+ "enter": ("Enter", 0x1C, False, win32con.VK_RETURN),
+ "esc": ("Esc", 0x01, False, win32con.VK_ESCAPE),
}
_keyboard_special = list(_g_name_map.keys())
- _keyboard_modifiers = ["leftshift","leftcontrol","leftalt","rightshift","rightshift2","rightcontrol","rightalt","rightalt2","leftwin","rightwin"]
+ _keyboard_modifiers = [
+ "leftshift",
+ "leftcontrol",
+ "leftalt",
+ "rightshift",
+ "rightshift2",
+ "rightcontrol",
+ "rightalt",
+ "rightalt2",
+ "leftwin",
+ "rightwin",
+ ]
+
# populate special mouse keys
for mouse_button in MouseButton:
@@ -1285,7 +1303,7 @@ def toggle_numlock():
for name_, data in KeyMap._g_name_map.items():
key = Key(*data)
key._lookup_name = name_
- KeyMap._g_map[(data[1],data[2])] = key
+ KeyMap._g_map[(data[1], data[2])] = key
KeyMap.register(key)
@@ -1296,7 +1314,7 @@ def toggle_numlock():
is_extended = False
if code_value << 8 & 0xE0 or code_value << 8 & 0xE1:
is_extended = True
- if not (scan_code, is_extended) in KeyMap._g_scan_code_to_key:
+ if (scan_code, is_extended) not in KeyMap._g_scan_code_to_key:
virtual_code = KeyMap.scan_code_to_virtual_code(scan_code, is_extended)
if virtual_code > 0:
# only store keys that have a virtual key code
@@ -1307,5 +1325,3 @@ def toggle_numlock():
name = enum_code_value.name
key = Key(name, scan_code, is_extended, 0)
KeyMap.register(key)
-
-
diff --git a/gremlin/macro.py b/gremlin/macro.py
index cf31ae10..3892d3e6 100644
--- a/gremlin/macro.py
+++ b/gremlin/macro.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -28,7 +28,6 @@
from PySide6 import QtCore
import win32con
-import win32api
import gremlin
import gremlin.config
@@ -45,8 +44,7 @@
MacroEntry = collections.namedtuple(
- "MacroEntry",
- ["macro", "state", "is_local", "is_remote"]
+ "MacroEntry", ["macro", "state", "is_local", "is_remote"]
)
@@ -67,19 +65,13 @@ def _create_function(lib_name, fn_name, param_types, return_type):
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms646296(v=vs.85).aspx
_get_keyboard_layout = _create_function(
- "user32",
- "GetKeyboardLayout",
- [wintypes.DWORD],
- wintypes.HKL
+ "user32", "GetKeyboardLayout", [wintypes.DWORD], wintypes.HKL
)
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms646299(v=vs.85).aspx
_get_keyboard_state = _create_function(
- "user32",
- "GetKeyboardState",
- [ctypes.POINTER(ctypes.c_char)],
- wintypes.BOOL
+ "user32", "GetKeyboardState", [ctypes.POINTER(ctypes.c_char)], wintypes.BOOL
)
@@ -88,7 +80,7 @@ def _create_function(lib_name, fn_name, param_types, return_type):
"user32",
"MapVirtualKeyExW",
[ctypes.c_uint, ctypes.c_uint, wintypes.HKL],
- ctypes.c_uint
+ ctypes.c_uint,
)
@@ -103,18 +95,15 @@ def _create_function(lib_name, fn_name, param_types, return_type):
ctypes.POINTER(ctypes.c_wchar),
ctypes.c_int,
ctypes.c_uint,
- ctypes.c_void_p
+ ctypes.c_void_p,
],
- ctypes.c_int
+ ctypes.c_int,
)
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms646332(v=vs.85).aspx
_vk_key_scan_ex = _create_function(
- "user32",
- "VkKeyScanExW",
- [ctypes.c_wchar, wintypes.HKL],
- ctypes.c_short
+ "user32", "VkKeyScanExW", [ctypes.c_wchar, wintypes.HKL], ctypes.c_short
)
@@ -127,7 +116,7 @@ def _scan_code_to_virtual_code(scan_code, is_extended):
"""
value = scan_code
if is_extended:
- value = 0xe0 << 8 | scan_code
+ value = 0xE0 << 8 | scan_code
virtual_code = _map_virtual_key_ex(value, 3, _get_keyboard_layout(0))
return virtual_code
@@ -143,44 +132,23 @@ def _virtual_input_to_unicode(virtual_code):
output_buffer = ctypes.create_unicode_buffer(8)
state_buffer = ctypes.create_string_buffer(256)
- if virtual_code == 0x7c:
+ if virtual_code == 0x7C:
pass
-
# Translate three times to get around dead keys showing up in funny ways
# as the translation takes them into account for future keys
state = _to_unicode_ex(
- virtual_code,
- 0x00,
- state_buffer,
- output_buffer,
- 8,
- 0,
- keyboard_layout
+ virtual_code, 0x00, state_buffer, output_buffer, 8, 0, keyboard_layout
)
state = _to_unicode_ex(
- virtual_code,
- 0x00,
- state_buffer,
- output_buffer,
- 8,
- 0,
- keyboard_layout
+ virtual_code, 0x00, state_buffer, output_buffer, 8, 0, keyboard_layout
)
state = _to_unicode_ex(
- virtual_code,
- 0x00,
- state_buffer,
- output_buffer,
- 8,
- 0,
- keyboard_layout
+ virtual_code, 0x00, state_buffer, output_buffer, 8, 0, keyboard_layout
)
if state == 0:
- syslog.error(
- f"No translation for key {hex(virtual_code)} available"
- )
+ syslog.error(f"No translation for key {hex(virtual_code)} available")
return str(hex(virtual_code))
return output_buffer.value.upper()
@@ -206,55 +174,60 @@ def _unicode_to_key(character):
return gremlin.keyboard.Key(character, scan_code, is_extended, virtual_code)
-def _send_mouse_button(button_id, is_pressed, is_local = True, is_remote = False, force_remote = False):
- from gremlin.types import MouseButton
- import gremlin.sendinput
- import gremlin.input_devices
+def _send_mouse_button(
+ button_id, is_pressed, is_local=True, is_remote=False, force_remote=False
+):
+ from gremlin.types import MouseButton
+ import gremlin.sendinput
+ import gremlin.input_devices
- if force_remote:
- is_remote = True
- if button_id in [MouseButton.WheelDown, MouseButton.WheelUp]:
- if is_pressed:
- direction = -16
- if button_id == MouseButton.WheelDown:
- direction = 16
- if is_local:
- gremlin.sendinput.mouse_wheel(direction)
- if is_remote:
- gremlin.input_devices.remote_client.send_mouse_wheel(direction)
- elif button_id in [MouseButton.WheelLeft, MouseButton.WheelRight]:
- if is_pressed:
- direction = -16
- if button_id == MouseButton.WheelRight:
- direction = 16
- if is_local:
- gremlin.sendinput.mouse_h_wheel(direction)
- if is_remote:
- gremlin.input_devices.remote_client.send_mouse_h_wheel(direction)
+ if force_remote:
+ is_remote = True
+ if button_id in [MouseButton.WheelDown, MouseButton.WheelUp]:
+ if is_pressed:
+ direction = -16
+ if button_id == MouseButton.WheelDown:
+ direction = 16
+ if is_local:
+ gremlin.sendinput.mouse_wheel(direction)
+ if is_remote:
+ gremlin.input_devices.remote_client.send_mouse_wheel(direction)
+ elif button_id in [MouseButton.WheelLeft, MouseButton.WheelRight]:
+ if is_pressed:
+ direction = -16
+ if button_id == MouseButton.WheelRight:
+ direction = 16
+ if is_local:
+ gremlin.sendinput.mouse_h_wheel(direction)
+ if is_remote:
+ gremlin.input_devices.remote_client.send_mouse_h_wheel(direction)
+ else:
+ if is_pressed:
+ if is_local:
+ gremlin.sendinput.mouse_press(button_id)
+ if is_remote:
+ gremlin.input_devices.remote_client.send_mouse_button(
+ button_id.value, True
+ )
else:
- if is_pressed:
- if is_local:
- gremlin.sendinput.mouse_press(button_id)
- if is_remote:
- gremlin.input_devices.remote_client.send_mouse_button(button_id.value, True)
- else:
- if is_local:
- gremlin.sendinput.mouse_release(button_id)
- if is_remote:
- gremlin.input_devices.remote_client.send_mouse_button(button_id.value, False)
+ if is_local:
+ gremlin.sendinput.mouse_release(button_id)
+ if is_remote:
+ gremlin.input_devices.remote_client.send_mouse_button(
+ button_id.value, False
+ )
-def _send_key_down(key, is_local = True, is_remote = False, force_remote = False):
+def _send_key_down(key, is_local=True, is_remote=False, force_remote=False):
"""Sends the KEYDOWN event for a single key.
:param key the key for which to send the KEYDOWN event
"""
assert key.virtual_code and key.scan_code, f"Invalid key: {key}"
-
if key.is_mouse:
# special handling of virtual keys for mouse buttons
- _send_mouse_button(key.mouse_button, True, is_local, is_remote, force_remote )
+ _send_mouse_button(key.mouse_button, True, is_local, is_remote, force_remote)
return
if force_remote:
@@ -266,17 +239,18 @@ def _send_key_down(key, is_local = True, is_remote = False, force_remote = False
# win32api.keybd_event(key.virtual_code, key.scan_code, flags, 0)
if is_remote:
- gremlin.input_devices.remote_client.send_key(key.virtual_code, key.scan_code, flags, force_remote)
-
+ gremlin.input_devices.remote_client.send_key(
+ key.virtual_code, key.scan_code, flags, force_remote
+ )
-def _send_key_up(key, is_local = True, is_remote = False, force_remote = False):
+def _send_key_up(key, is_local=True, is_remote=False, force_remote=False):
"""Sends the KEYUP event for a single key.
:param key the key for which to send the KEYUP event
"""
if key.is_mouse:
# special handling of virtual keys for mouse buttons
- _send_mouse_button(key.mouse_button, False, is_local, is_remote, force_remote )
+ _send_mouse_button(key.mouse_button, False, is_local, is_remote, force_remote)
return
flags = win32con.KEYEVENTF_EXTENDEDKEY if key.is_extended else 0
@@ -284,21 +258,23 @@ def _send_key_up(key, is_local = True, is_remote = False, force_remote = False):
if is_local:
gremlin.sendinput.send_key(key.virtual_code, key.scan_code, flags)
if is_remote:
- gremlin.input_devices.remote_client.send_key(key.virtual_code, key.scan_code, flags, force_remote )
+ gremlin.input_devices.remote_client.send_key(
+ key.virtual_code, key.scan_code, flags, force_remote
+ )
+
def key_from_code(scan_code, is_extended):
- ''' returns a key from a code '''
+ """returns a key from a code"""
return gremlin.keyboard.key_from_code(scan_code, is_extended)
-def key_from_name(name, validate = False):
- ''' returns a key from a lookup name '''
- return gremlin.keyboard.key_from_name(name, validate)
+def key_from_name(name, validate=False):
+ """returns a key from a lookup name"""
+ return gremlin.keyboard.key_from_name(name, validate)
@SingletonDecorator
class MacroManager(QtCore.QObject):
-
"""Manages the proper dispatching and scheduling of macros."""
def __init__(self):
@@ -319,15 +295,14 @@ def __init__(self):
self._is_executing_exclusive = False
self._is_running = False
- self._schedule_event = Event() # used to step through macro executions
+ self._schedule_event = Event() # used to step through macro executions
self._run_scheduler_thread = None
self.el.profile_stop.connect(self._profile_stop)
-
@QtCore.Slot()
def _profile_stop(self):
- ''' triggered when profiles stop '''
+ """triggered when profiles stop"""
self.stop()
def start(self):
@@ -344,9 +319,10 @@ def start(self):
def stop(self):
"""Stops the scheduler."""
self._is_running = False
- if self._run_scheduler_thread is not None and \
- self._run_scheduler_thread.is_alive():
-
+ if (
+ self._run_scheduler_thread is not None
+ and self._run_scheduler_thread.is_alive()
+ ):
# Terminate the scheduler
self._schedule_event.set()
self._run_scheduler_thread.join()
@@ -357,7 +333,7 @@ def stop(self):
for key, value in self._flags.items():
self._flags[key] = False
- def queue_macro(self, macro : Macro, is_local : bool = None, is_remote : bool = None):
+ def queue_macro(self, macro: Macro, is_local: bool = None, is_remote: bool = None):
"""Queues a macro in the schedule taking the repeat type into account.
:param macro: the macro to add to the scheduler
@@ -370,10 +346,10 @@ def queue_macro(self, macro : Macro, is_local : bool = None, is_remote : bool =
verbose = gremlin.config.Configuration().verbose_mode_macro
if verbose:
syslog.info(f"MACRO: queue macro ID [{macro.id}]")
- action : MacroAbstractAction
+ action: MacroAbstractAction
for action in macro.sequence:
syslog.info(f"\t{str(action)}")
-
+
if isinstance(macro.repeat, ToggleRepeat) and macro.id in self._active:
self.terminate_macro(macro)
else:
@@ -390,27 +366,29 @@ def queue_macro(self, macro : Macro, is_local : bool = None, is_remote : bool =
return macro.id
-
def clear_queue(self):
- ''' clears the current macro queue '''
+ """clears the current macro queue"""
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_macro
- if verbose: syslog.info("MACRO: clear queue")
+ if verbose:
+ syslog.info("MACRO: clear queue")
with self._queue_lock:
self._queue.clear()
self._schedule_event.set()
-
- def terminate_macro(self, macro : Macro):
+ def terminate_macro(self, macro: Macro):
"""Adds a termination request for a macro to the execution queue.
:param macro the macro to terminate
"""
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_macro
- if verbose: syslog.info(f"MACRO: terminate macro [{macro.id}]")
+ if verbose:
+ syslog.info(f"MACRO: terminate macro [{macro.id}]")
with self._queue_lock:
- self._queue.append(MacroEntry(macro, False, macro.is_local, macro.is_remote))
+ self._queue.append(
+ MacroEntry(macro, False, macro.is_local, macro.is_remote)
+ )
self._schedule_event.set()
def _run_scheduler(self):
@@ -432,12 +410,15 @@ def _run_scheduler(self):
for entry in self._queue:
# Terminate macro if needed
if entry.state is False:
- # entry marked as completed
- if entry.macro.id in self._flags and self._flags[entry.macro.id]:
+ # entry marked as completed
+ if (
+ entry.macro.id in self._flags
+ and self._flags[entry.macro.id]
+ ):
# Terminate currently running macro
with self._flags_lock:
self._flags[entry.macro.id] = False
-
+
# Remove all queued up macros with the same id as
# they should have been impossible to queue up
# in the first place
@@ -454,12 +435,16 @@ def _run_scheduler(self):
elif entry.macro.exclusive:
has_exclusive = True
if len(self._active) == 0:
- self._dispatch_macro(entry.macro, entry.is_local, entry.is_remote)
+ self._dispatch_macro(
+ entry.macro, entry.is_local, entry.is_remote
+ )
self._is_executing_exclusive = True
entries_to_remove.append(entry)
# Start a queued up macro
elif not has_exclusive and not self._is_executing_exclusive:
- self._dispatch_macro(entry.macro, entry.is_local, entry.is_remote)
+ self._dispatch_macro(
+ entry.macro, entry.is_local, entry.is_remote
+ )
entries_to_remove.append(entry)
# Remove all entries we've processed
@@ -467,7 +452,9 @@ def _run_scheduler(self):
if entry in self._queue:
self._queue.remove(entry)
- def _dispatch_macro(self, macro : Macro, is_local : bool = None, is_remote : bool = None):
+ def _dispatch_macro(
+ self, macro: Macro, is_local: bool = None, is_remote: bool = None
+ ):
"""Dispatches a single macro to be run.
:param macro the macro to dispatch
@@ -475,12 +462,20 @@ def _dispatch_macro(self, macro : Macro, is_local : bool = None, is_remote : boo
:param is_remote true if remote control, set to None to use the macro flag
"""
if macro.id not in self._active:
- self._active[macro.id] = macro # add the macro to the active queue
- Thread(target=functools.partial(self._execute_macro, macro, is_local, is_remote)).start()
+ self._active[macro.id] = macro # add the macro to the active queue
+ Thread(
+ target=functools.partial(
+ self._execute_macro, macro, is_local, is_remote
+ )
+ ).start()
else:
- syslog.warning(f"Attempting to dispatch an already running macro: ID: {macro.id}")
+ syslog.warning(
+ f"Attempting to dispatch an already running macro: ID: {macro.id}"
+ )
- def _execute_macro(self, macro : Macro, is_local : bool = None, is_remote : bool = None):
+ def _execute_macro(
+ self, macro: Macro, is_local: bool = None, is_remote: bool = None
+ ):
"""Executes a given macro in a separate thread.
This method will run all provided actions and once they all have been
@@ -493,18 +488,18 @@ def _execute_macro(self, macro : Macro, is_local : bool = None, is_remote : bool
"""
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_macro
- if verbose: syslog.info(f"MACRO: execute [{macro.id}]")
+ if verbose:
+ syslog.info(f"MACRO: execute [{macro.id}]")
(state_is_local, state_is_remote) = gremlin.input_devices.remote_state.state
if not is_remote:
is_remote = state_is_remote
if not is_local:
is_local = state_is_local
-
+
if macro.force_remote:
is_remote = True
is_local = False
-
if macro.repeat is not None:
delay = macro.repeat.delay
@@ -515,10 +510,12 @@ def _execute_macro(self, macro : Macro, is_local : bool = None, is_remote : bool
# Handle count repeat mode
if isinstance(macro.repeat, CountRepeat):
count = 0
- if verbose: syslog.info(f"\tMACRO: autorepeat id [{macro.id}]")
+ if verbose:
+ syslog.info(f"\tMACRO: autorepeat id [{macro.id}]")
while count < macro.repeat.count and self._flags[macro.id]:
for action in macro.sequence:
- if verbose: syslog.info(f"\tAction: {str(action)}")
+ if verbose:
+ syslog.info(f"\tAction: {str(action)}")
action(is_local, is_remote)
count += 1
time.sleep(delay)
@@ -530,33 +527,36 @@ def _execute_macro(self, macro : Macro, is_local : bool = None, is_remote : bool
action(is_local, is_remote)
time.sleep(delay)
-
# Handle simple one shot macros
else:
- if verbose:
+ if verbose:
msg = "".join(f"{str(a)} " for a in macro.sequence)
- syslog.info(f"\tMACRO: single shot: id: [{macro.id} {len(macro.sequence)} {msg}")
+ syslog.info(
+ f"\tMACRO: single shot: id: [{macro.id} {len(macro.sequence)} {msg}"
+ )
for action in macro.sequence:
action(is_local, is_remote, macro.force_remote)
-
- if verbose: syslog.info(f"MACRO: completed macro id [{macro.id}]")
+ if verbose:
+ syslog.info(f"MACRO: completed macro id [{macro.id}]")
# indicate the macro is done
if macro.completed_callback:
macro.completed_callback()
- self.el.macro_step_completed.emit(macro.id) # indicate the macro has been completed
-
+ self.el.macro_step_completed.emit(
+ macro.id
+ ) # indicate the macro has been completed
# Remove macro from active set, notify manager, and remove any
# potential callbacks
try:
-
if macro.id in self._active:
del self._active[macro.id]
else:
- syslog.error(f"MACRO: attempt to delete macro id [{macro.id}] that no longer exists in active macro list")
+ syslog.error(
+ f"MACRO: attempt to delete macro id [{macro.id}] that no longer exists in active macro list"
+ )
if macro.exclusive:
self._is_executing_exclusive = False
with self._flags_lock:
@@ -568,8 +568,6 @@ def _execute_macro(self, macro : Macro, is_local : bool = None, is_remote : bool
# trigger next step
self._schedule_event.set()
-
-
def _preprocess_macro(self, macro):
"""Inserts pauses as necessary into the macro."""
if macro._sequence:
@@ -584,27 +582,25 @@ def _preprocess_macro(self, macro):
class Macro:
-
"""Represents a macro which can be executed."""
# Unique identifier for each macro - bumps by one for each new macro
_next_macro_id = 0
- def __init__(self, is_local = None, is_remote = None, force_remote = None):
+ def __init__(self, is_local=None, is_remote=None, force_remote=None):
"""Creates a new macro instance.
-
+
:is_local: if set, sends the macro output to the local client
:is_remote: if set, sends the macro output to the remote client
:force_remote: if set, forces the output to remote only
-
+
"""
self._sequence = []
self._id = Macro._next_macro_id
Macro._next_macro_id += 1
self.repeat = None
self.exclusive = False
- self.completed_callback = None # callback called when macro completes
-
+ self.completed_callback = None # callback called when macro completes
# flag set if we're forcing remote mode execution
if force_remote:
@@ -618,7 +614,7 @@ def __init__(self, is_local = None, is_remote = None, force_remote = None):
else:
self._is_local = is_local
- # flag if macro runs remotely
+ # flag if macro runs remotely
if not is_remote:
self._is_remote = False
else:
@@ -626,32 +622,33 @@ def __init__(self, is_local = None, is_remote = None, force_remote = None):
@property
def id(self) -> int:
- ''' unique macro id'''
+ """unique macro id"""
return self._id
-
+
@property
def force_remote(self) -> bool:
return self._force_remote
-
+
@force_remote.setter
- def force_remote(self, value : bool):
+ def force_remote(self, value: bool):
self._force_remote = value
-
@property
def is_local(self) -> bool:
- ''' local control flag'''
+ """local control flag"""
return self._is_local
+
@is_local.setter
- def is_local(self, value : bool):
+ def is_local(self, value: bool):
self._is_local = value
@property
def is_remote(self) -> bool:
- ''' remote control flag'''
+ """remote control flag"""
return self._is_remote
+
@is_remote.setter
- def is_remote(self, value : bool):
+ def is_remote(self, value: bool):
self._is_remote = value
@property
@@ -662,7 +659,7 @@ def sequence(self) -> list:
"""
return self._sequence
- def add_action(self, action : MacroAbstractAction):
+ def add_action(self, action: MacroAbstractAction):
"""Adds an action to the list of actions to perform.
:param action the action to add
@@ -706,6 +703,7 @@ def action(self, key, is_pressed):
(True) or released (False)
"""
from gremlin.keyboard import Key, KeyMap
+
if isinstance(key, str):
key = KeyMap.find_by_name(key)
elif isinstance(key, Key):
@@ -713,33 +711,33 @@ def action(self, key, is_pressed):
elif isinstance(key, int):
key = KeyMap.find_virtual(key)
elif isinstance(key, tuple):
- key = KeyMap.find(key[0],key[1])
+ key = KeyMap.find(key[0], key[1])
else:
raise gremlin.error.KeyboardError("Invalid key specified")
self._sequence.append(KeyAction(key, is_pressed))
-class MacroAbstractAction():
-
+class MacroAbstractAction:
"""Base class for all macro action."""
- def __init__(self, data = None):
+ def __init__(self, data=None):
self._data = data
@property
def data(self):
return self._data
+
@data.setter
def data(self, value):
self._data = value
- def __call__(self, is_local = True, is_remote = False, force_remote = False):
+ def __call__(self, is_local=True, is_remote=False, force_remote=False):
raise gremlin.error.MissingImplementationError(
"AbstractAction.__call__ not implemented in derived class."
)
-
- def _update_flags(self, is_local = None, is_remote = None, force_remote = None):
+
+ def _update_flags(self, is_local=None, is_remote=None, force_remote=None):
# updates flags based on local/remote overrides
(state_is_local, state_is_remote) = gremlin.input_devices.remote_state.state
if is_local is None:
@@ -748,12 +746,11 @@ def _update_flags(self, is_local = None, is_remote = None, force_remote = None):
is_remote = state_is_remote
if force_remote is not None and force_remote:
is_local = False
- is_remote = True
+ is_remote = True
return (is_local, is_remote)
class JoystickAction(MacroAbstractAction):
-
"""Joystick input action for a macro."""
def __init__(self, device_guid, input_type, input_id, value, axis_type="absolute"):
@@ -770,9 +767,8 @@ def __init__(self, device_guid, input_type, input_id, value, axis_type="absolute
self.input_id = input_id
self.value = value
self.axis_type = axis_type
-
- def __call__(self, is_local = None, is_remote = None, force_remote = None):
+ def __call__(self, is_local=None, is_remote=None, force_remote=None):
"""Emits an Event instance through the EventListener system."""
el = gremlin.event_handler.EventListener()
if self.input_type == InputType.JoystickAxis:
@@ -781,7 +777,7 @@ def __call__(self, is_local = None, is_remote = None, force_remote = None):
device_guid=self.device_guid,
identifier=self.input_id,
value=self.value,
- force_remote = force_remote
+ force_remote=force_remote,
)
elif self.input_type == InputType.JoystickButton:
event = gremlin.event_handler.Event(
@@ -789,7 +785,7 @@ def __call__(self, is_local = None, is_remote = None, force_remote = None):
device_guid=self.device_guid,
identifier=self.input_id,
is_pressed=self.value,
- force_remote = force_remote
+ force_remote=force_remote,
)
elif self.input_type == InputType.JoystickHat:
event = gremlin.event_handler.Event(
@@ -797,16 +793,13 @@ def __call__(self, is_local = None, is_remote = None, force_remote = None):
device_guid=self.device_guid,
identifier=self.input_id,
value=self.value,
- force_remote = force_remote
+ force_remote=force_remote,
)
el.joystick_event.emit(event)
-
-
class KeyAction(MacroAbstractAction):
-
"""Key to press or release by a macro."""
def __init__(self, key, is_pressed):
@@ -822,27 +815,27 @@ def __init__(self, key, is_pressed):
self.key = key
self.is_pressed = is_pressed
- def __call__(self, is_local = None, is_remote = None, force_remote = None):
+ def __call__(self, is_local=None, is_remote=None, force_remote=None):
# ignore passed local/remote states
# syslog = logging.getLogger("system")
- verbose = gremlin.config.Configuration().verbose_mode_macro
+ verbose = gremlin.config.Configuration().verbose_mode_macro
is_local, is_remote = self._update_flags(is_local, is_remote, force_remote)
if self.is_pressed:
- if verbose: syslog.info(f"MACRO: send key make: {self.key}")
+ if verbose:
+ syslog.info(f"MACRO: send key make: {self.key}")
_send_key_down(self.key, is_local, is_remote, force_remote)
else:
- if verbose: syslog.info(f"MACRO: send key break: {self.key}")
+ if verbose:
+ syslog.info(f"MACRO: send key break: {self.key}")
_send_key_up(self.key, is_local, is_remote, force_remote)
def __str__(self):
if self.key:
return f"KeyAction: {self.key.debug_name}"
return "KeyAction: (no key)"
-
class MouseButtonAction(MacroAbstractAction):
-
"""Mouse button action."""
def __init__(self, button, is_pressed):
@@ -857,38 +850,43 @@ def __init__(self, button, is_pressed):
self.button = button
self.is_pressed = is_pressed
- def __call__(self, is_local = None, is_remote = None, force_remote = None):
+ def __call__(self, is_local=None, is_remote=None, force_remote=None):
# ignore passed local/remote states
# syslog = logging.getLogger("system")
- verbose = gremlin.config.Configuration().verbose_mode_macro
+ verbose = gremlin.config.Configuration().verbose_mode_macro
is_local, is_remote = self._update_flags(is_local, is_remote, force_remote)
if self.button == gremlin.types.MouseButton.WheelDown:
if is_local:
- if verbose: syslog.info(f"MACRO: mouse wheel up")
+ if verbose:
+ syslog.info("MACRO: mouse wheel up")
gremlin.sendinput.mouse_wheel(1)
if is_remote:
gremlin.input_devices.remote_client.send_mouse_wheel(1, force_remote)
elif self.button == gremlin.types.MouseButton.WheelUp:
if is_local:
- if verbose: syslog.info(f"MACRO: mouse wheel down")
+ if verbose:
+ syslog.info("MACRO: mouse wheel down")
gremlin.sendinput.mouse_wheel(-1)
if is_remote:
gremlin.input_devices.remote_client.send_mouse_wheel(-1, force_remote)
else:
if is_local:
if self.is_pressed:
- if verbose: syslog.info(f"MACRO: mouse press {self.button}")
+ if verbose:
+ syslog.info(f"MACRO: mouse press {self.button}")
gremlin.sendinput.mouse_press(self.button)
else:
- if verbose: syslog.info(f"MACRO: mouse release {self.button}")
+ if verbose:
+ syslog.info(f"MACRO: mouse release {self.button}")
gremlin.sendinput.mouse_release(self.button)
if is_remote:
- gremlin.input_devices.remote_client.send_mouse_button(self.button, self.is_pressed, force_remote)
+ gremlin.input_devices.remote_client.send_mouse_button(
+ self.button, self.is_pressed, force_remote
+ )
class MouseMotionAction(MacroAbstractAction):
-
"""Mouse motion action."""
def __init__(self, dx, dy):
@@ -900,23 +898,25 @@ def __init__(self, dx, dy):
self.dx = int(dx)
self.dy = int(dy)
- def __call__(self, is_local = None, is_remote = None, force_remote = None):
+ def __call__(self, is_local=None, is_remote=None, force_remote=None):
# ignore passed local/remote states
# syslog = logging.getLogger("system")
- verbose = gremlin.config.Configuration().verbose_mode_macro
+ verbose = gremlin.config.Configuration().verbose_mode_macro
is_local, is_remote = self._update_flags(is_local, is_remote, force_remote)
if is_local:
- if verbose: syslog.info(f"MACRO: mouse motion {self.dx}, {self.dy}")
+ if verbose:
+ syslog.info(f"MACRO: mouse motion {self.dx}, {self.dy}")
gremlin.sendinput.mouse_relative_motion(self.dx, self.dy)
if is_remote:
- gremlin.input_devices.remote_client.send_mouse_motion(self.dx, self.dy, force_remote)
+ gremlin.input_devices.remote_client.send_mouse_motion(
+ self.dx, self.dy, force_remote
+ )
class PauseAction(MacroAbstractAction):
-
"""Represents the pause in a macro between pressed."""
- def __init__(self, duration, duration_max = 0, is_random = False):
+ def __init__(self, duration, duration_max=0, is_random=False):
"""Creates a new Pause object for use in a macro.
:param duration the duration in seconds of the pause
@@ -925,10 +925,11 @@ def __init__(self, duration, duration_max = 0, is_random = False):
self.duration_max = duration_max
self.is_random = is_random
- def __call__(self, is_local = None, is_remote = None, force_remote = None):
+ def __call__(self, is_local=None, is_remote=None, force_remote=None):
import random
+
# syslog = logging.getLogger("system")
- verbose = gremlin.config.Configuration().verbose_mode_macro
+ verbose = gremlin.config.Configuration().verbose_mode_macro
# is_local, is_remote = self._update_flags(is_local, is_remote, force_remote)
if self.is_random:
# random pause
@@ -942,19 +943,22 @@ def __call__(self, is_local = None, is_remote = None, force_remote = None):
duration = random.uniform(0, duration_min)
else:
duration = self.duration
- if verbose: syslog.info(f"MACRO: Pause action for {duration}")
+ if verbose:
+ syslog.info(f"MACRO: Pause action for {duration}")
time.sleep(duration)
+
class GraphAction(MacroAbstractAction):
- ''' macro to execute an execution graph - used for sequencing/queuing functors in some containers '''
+ """macro to execute an execution graph - used for sequencing/queuing functors in some containers"""
+
def __init__(self, graph, event, value):
- '''
+ """
:param graph: the execution graph to run
:param event: the event to pass to the execution graph (will get cloned and stored)
:param value: the action value to pass to the execution graph (will get cloned and stored)
- '''
+ """
super().__init__()
- assert graph is not None,"GraphAction: graph cannot be null"
+ assert graph is not None, "GraphAction: graph cannot be null"
self._graph = graph
self._graph.graph_completed.connect(self._graph_completed)
@@ -971,34 +975,38 @@ def _profile_stop(self):
if self._complete_event is not None and not self._complete_event.is_set():
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_condition
- if verbose: syslog.info("GraphAction: profile stop received - stopping execution")
+ if verbose:
+ syslog.info("GraphAction: profile stop received - stopping execution")
self._complete_event.set()
time.sleep(0.01)
-
- def __call__(self, is_local = None, is_remote = None, force_remote = None):
+ def __call__(self, is_local=None, is_remote=None, force_remote=None):
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_condition
- if verbose: syslog.info(f"GraphAction: call {self.data}")
+ if verbose:
+ syslog.info(f"GraphAction: call {self.data}")
if self._graph is not None:
self._complete_event.clear()
- if verbose: syslog.info(f"GraphAction: execute {self.data}")
+ if verbose:
+ syslog.info(f"GraphAction: execute {self.data}")
self._graph.process_event(self._event, self._value)
self._complete_event.wait()
- if verbose: syslog.info(f"GraphAction: completed {self.data}")
-
+ if verbose:
+ syslog.info(f"GraphAction: completed {self.data}")
@QtCore.Slot(object)
def _graph_completed(self, graph):
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_condition
- if verbose: syslog.info(f"GraphAction: event: graph completed {self.data}")
+ if verbose:
+ syslog.info(f"GraphAction: event: graph completed {self.data}")
if graph == self._graph:
- if verbose: syslog.info(f"GraphAction: event: set {self.data}")
+ if verbose:
+ syslog.info(f"GraphAction: event: set {self.data}")
self._complete_event.set()
-class VJoyMacroAction(MacroAbstractAction):
+class VJoyMacroAction(MacroAbstractAction):
"""VJoy input action for a macro."""
def __init__(self, vjoy_id, input_type, input_id, value, axis_type="absolute"):
@@ -1016,9 +1024,9 @@ def __init__(self, vjoy_id, input_type, input_id, value, axis_type="absolute"):
self.value = value
self.axis_type = axis_type
- def __call__(self, is_local = None, is_remote = None, force_remote = None):
+ def __call__(self, is_local=None, is_remote=None, force_remote=None):
# ignore passed local/remote states
-
+
is_local, is_remote = self._update_flags(is_local, is_remote, force_remote)
vjoy = gremlin.joystick_handling.VJoyProxy()[self.vjoy_id]
if self.input_type == InputType.JoystickAxis:
@@ -1026,49 +1034,47 @@ def __call__(self, is_local = None, is_remote = None, force_remote = None):
if is_local:
vjoy.axis(self.input_id).value = self.value
if is_remote:
- gremlin.input_devices.remote_client.send_axis(self.vjoy_id, self.input_id, self.value, force_remote)
+ gremlin.input_devices.remote_client.send_axis(
+ self.vjoy_id, self.input_id, self.value, force_remote
+ )
elif self.axis_type == "relative":
-
if is_local:
vjoy.axis(self.input_id).value = max(
- -1.0,
- min(1.0, vjoy.axis(self.input_id).value + self.value)
+ -1.0, min(1.0, vjoy.axis(self.input_id).value + self.value)
)
if is_remote:
- gremlin.input_devices.remote_client.send_relative_axis(self.vjoy_id, self.input_id, self.value, force_remote)
+ gremlin.input_devices.remote_client.send_relative_axis(
+ self.vjoy_id, self.input_id, self.value, force_remote
+ )
elif self.input_type == InputType.JoystickButton:
if is_local:
vjoy.button(self.input_id).is_pressed = self.value
if is_remote:
- gremlin.input_devices.remote_client.send_button(self.vjoy_id, self.input_id, self.value, force_remote)
+ gremlin.input_devices.remote_client.send_button(
+ self.vjoy_id, self.input_id, self.value, force_remote
+ )
elif self.input_type == InputType.JoystickHat:
if is_local:
vjoy.hat(self.input_id).direction = self.value
if is_remote:
- gremlin.input_devices.remote_client.send_hat(self.vjoy_id, self.input_id, self.value, force_remote)
+ gremlin.input_devices.remote_client.send_hat(
+ self.vjoy_id, self.input_id, self.value, force_remote
+ )
class RemoteControlAction(MacroAbstractAction):
- ''' remote control actions for a macro '''
-
+ """remote control actions for a macro"""
def __init__(self):
- self.command = gremlin.input_devices.VjoyAction.VJoyEnableRemoteOnly
+ self.command = gremlin.input_devices.VjoyAction.VJoyEnableRemoteOnly
- def __call__(self, is_local = True, is_remote = False, force_remote= False):
- ''' execute the mode change '''
+ def __call__(self, is_local=True, is_remote=False, force_remote=False):
+ """execute the mode change"""
gremlin.input_devices.remote_state.mode = self.command
-
-
-
-
-
-
class AbstractRepeat:
-
"""Base class for all macro repeat modes."""
def __init__(self, delay):
@@ -1087,7 +1093,7 @@ def to_xml(self):
"AbstractRepeat.to_xml not implemented in subclass."
)
- def from_xml(self, node, data = None):
+ def from_xml(self, node, data=None):
"""Populates the instance's data from the provided XML node.
:param node XML node containing data with which to populate the instance
@@ -1098,7 +1104,6 @@ def from_xml(self, node, data = None):
class CountRepeat(AbstractRepeat):
-
"""Repeat mode which repeats the macro a fixed number of times."""
def __init__(self, count=1, delay=0.1):
@@ -1121,7 +1126,7 @@ def to_xml(self):
node.set("delay", str(self.delay))
return node
- def from_xml(self, node, data = None):
+ def from_xml(self, node, data=None):
"""Populates the instance's data from the provided XML node.
:param node XML node containing data with which to populate the instance
@@ -1131,7 +1136,6 @@ def from_xml(self, node, data = None):
class ToggleRepeat(AbstractRepeat):
-
"""Repeat mode which repeats the macro as long as it hasn't been toggled
off again after being toggled on."""
@@ -1152,7 +1156,7 @@ def to_xml(self):
node.set("delay", str(self.delay))
return node
- def from_xml(self, node, data = None):
+ def from_xml(self, node, data=None):
"""Populates the instance's data from the provided XML node.
:param node XML node containing data with which to populate the instance
@@ -1161,7 +1165,6 @@ def from_xml(self, node, data = None):
class HoldRepeat(AbstractRepeat):
-
"""Repeat mode which repeats the macro as long as the activation condition
is being fulfilled or held down."""
@@ -1182,10 +1185,9 @@ def to_xml(self):
node.set("delay", str(self.delay))
return node
- def from_xml(self, node, data = None):
+ def from_xml(self, node, data=None):
"""Populates the instance's data from the provided XML node.
:param node XML node containing data with which to populate the instance
"""
self.delay = float(node.get("delay"))
-
diff --git a/gremlin/macro_handler.py b/gremlin/macro_handler.py
index c848dfc5..ca0c5f1e 100644
--- a/gremlin/macro_handler.py
+++ b/gremlin/macro_handler.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -38,13 +38,12 @@
syslog = logging.getLogger("system")
-class MacroActionEditor(QtWidgets.QWidget):
+class MacroActionEditor(QtWidgets.QWidget):
"""Widget displaying macro action settings and permitting their change."""
ActionTypeData = collections.namedtuple(
- "ActionTypeData",
- ["name", "create_ui", "action_type"]
+ "ActionTypeData", ["name", "create_ui", "action_type"]
)
locked = False
@@ -62,39 +61,27 @@ def __init__(self, model, index, parent=None):
self.action_types = {
"Joystick": MacroActionEditor.ActionTypeData(
- "Joystick",
- self._joystick_ui,
- gremlin.macro.JoystickAction
+ "Joystick", self._joystick_ui, gremlin.macro.JoystickAction
),
"Keyboard": MacroActionEditor.ActionTypeData(
- "Keyboard",
- self._keyboard_ui,
- gremlin.macro.KeyAction
+ "Keyboard", self._keyboard_ui, gremlin.macro.KeyAction
),
"Mouse Button": MacroActionEditor.ActionTypeData(
- "Mouse Button",
- self._mouse_button_ui,
- gremlin.macro.MouseButtonAction
+ "Mouse Button", self._mouse_button_ui, gremlin.macro.MouseButtonAction
),
"Mouse Motion": MacroActionEditor.ActionTypeData(
- "Mouse Motion",
- self._mouse_motion_ui,
- gremlin.macro.MouseMotionAction
+ "Mouse Motion", self._mouse_motion_ui, gremlin.macro.MouseMotionAction
),
"Pause": MacroActionEditor.ActionTypeData(
- "Pause",
- self._pause_ui,
- gremlin.macro.PauseAction
+ "Pause", self._pause_ui, gremlin.macro.PauseAction
),
"vJoy": MacroActionEditor.ActionTypeData(
- "vJoy",
- self._vjoy_ui,
- gremlin.macro.VJoyMacroAction
+ "vJoy", self._vjoy_ui, gremlin.macro.VJoyMacroAction
),
"Remote Control": MacroActionEditor.ActionTypeData(
"Remote Control",
self._remote_control_ui,
- gremlin.macro.RemoteControlAction
+ gremlin.macro.RemoteControlAction,
),
}
@@ -108,7 +95,6 @@ def __init__(self, model, index, parent=None):
self.blank_label = QtWidgets.QLabel("Please add an action.")
self.main_layout.addWidget(self.blank_label)
-
self.ui_elements = {}
self._create_ui()
self._populate_ui()
@@ -118,7 +104,7 @@ def _create_ui(self):
if MacroActionEditor.locked:
return
-
+
try:
MacroActionEditor.locked = True
@@ -138,18 +124,17 @@ def _create_ui(self):
def _populate_ui(self):
"""Populate the UI elements with data from the model."""
-
# ensure there's a selected item in the model
if self.model.rowCount() == 0:
# no entries in the list
self.group_box.setVisible(False)
self.blank_label.setVisible(True)
return
-
+
# at least one entry in the list
self.group_box.setVisible(True)
self.blank_label.setVisible(False)
-
+
self.action_selector.currentTextChanged.disconnect(self._change_action)
entry = self.model.get_entry(self.index.row())
@@ -173,54 +158,32 @@ def _change_action(self, value):
# Update the model data to match the new type
if value == "Joystick":
self.model.set_entry(
- gremlin.macro.JoystickAction(
- 0,
- InputType.JoystickButton,
- 1,
- True
- ),
- self.index.row()
+ gremlin.macro.JoystickAction(0, InputType.JoystickButton, 1, True),
+ self.index.row(),
)
elif value == "Keyboard":
self.model.set_entry(
- gremlin.macro.KeyAction(
- key_from_name("enter"),
- True
- ),
- self.index.row()
+ gremlin.macro.KeyAction(key_from_name("enter"), True), self.index.row()
)
elif value == "Mouse Button":
self.model.set_entry(
- gremlin.macro.MouseButtonAction(
- gremlin.types.MouseButton.Left,
- True
- ),
- self.index.row()
+ gremlin.macro.MouseButtonAction(gremlin.types.MouseButton.Left, True),
+ self.index.row(),
)
elif value == "Mouse Motion":
self.model.set_entry(
- gremlin.macro.MouseMotionAction(0, 0),
- self.index.row()
+ gremlin.macro.MouseMotionAction(0, 0), self.index.row()
)
elif value == "Pause":
- self.model.set_entry(
- gremlin.macro.PauseAction(0.2),
- self.index.row()
- )
+ self.model.set_entry(gremlin.macro.PauseAction(0.2), self.index.row())
elif value == "vJoy":
self.model.set_entry(
- gremlin.macro.VJoyMacroAction(
- 1,
- InputType.JoystickButton,
- 1,
- True
- ),
- self.index.row()
+ gremlin.macro.VJoyMacroAction(1, InputType.JoystickButton, 1, True),
+ self.index.row(),
)
elif value == "Remote Control":
self.model.set_entry(gremlin.macro.RemoteControlAction(), self.index.row())
-
# Update the UI elements
self._update_model()
self.action_types[value].create_ui()
@@ -232,14 +195,17 @@ def _joystick_ui(self):
return
self.ui_elements["input_label"] = QtWidgets.QLabel("Input")
- self.ui_elements["input_button"] = \
- gremlin.ui.ui_common.NoKeyboardPushButton("Press Me")
+ self.ui_elements["input_button"] = gremlin.ui.ui_common.NoKeyboardPushButton(
+ "Press Me"
+ )
self.ui_elements["input_button"].clicked.connect(
- lambda: self._request_user_input([
- InputType.JoystickAxis,
- InputType.JoystickButton,
- InputType.JoystickHat
- ])
+ lambda: self._request_user_input(
+ [
+ InputType.JoystickAxis,
+ InputType.JoystickButton,
+ InputType.JoystickHat,
+ ]
+ )
)
self._create_joystick_inputs_ui(action)
@@ -255,8 +221,9 @@ def _keyboard_ui(self):
if action is None:
return
self.ui_elements["key_label"] = QtWidgets.QLabel("Key")
- self.ui_elements["key_input"] = \
- gremlin.ui.ui_common.NoKeyboardPushButton(action.key.name)
+ self.ui_elements["key_input"] = gremlin.ui.ui_common.NoKeyboardPushButton(
+ action.key.name
+ )
self.ui_elements["key_input"].clicked.connect(
lambda: self._request_user_input([InputType.Keyboard])
)
@@ -270,10 +237,14 @@ def _keyboard_ui(self):
self.ui_elements["key_press"].toggled.connect(self._modify_key_state)
self.ui_elements["key_release"].toggled.connect(self._modify_key_state)
- self.ui_elements["key_add_press"] = gremlin.ui.ui_common.QDataPushButton("Add Press", data = action)
+ self.ui_elements["key_add_press"] = gremlin.ui.ui_common.QDataPushButton(
+ "Add Press", data=action
+ )
self.ui_elements["key_add_press"].clicked.connect(self._add_key_press)
-
- self.ui_elements["key_add_release"] = gremlin.ui.ui_common.QDataPushButton("Add Release", data = action)
+
+ self.ui_elements["key_add_release"] = gremlin.ui.ui_common.QDataPushButton(
+ "Add Release", data=action
+ )
self.ui_elements["key_add_release"].clicked.connect(self._add_key_release)
delay_widget = gremlin.ui.ui_common.QIntLineEdit()
@@ -281,27 +252,29 @@ def _keyboard_ui(self):
delay_widget.setValue(config.macro_key_delay)
delay_widget.valueChanged.connect(self._key_delay_changed)
- add_widget = gremlin.ui.ui_common.QDataPushButton("Add Press/Delay/Release", data = action)
+ add_widget = gremlin.ui.ui_common.QDataPushButton(
+ "Add Press/Delay/Release", data=action
+ )
add_widget.clicked.connect(self._add_key_full)
-
- container, _ = gremlin.ui.ui_common.getHContainer((delay_widget, add_widget),"Delay (ms):")
+
+ container, _ = gremlin.ui.ui_common.getHContainer(
+ (delay_widget, add_widget), "Delay (ms):"
+ )
self.ui_elements["key_container"] = container
self.ui_elements["key_delay"] = delay_widget
-
self.action_layout.addWidget(self.ui_elements["key_label"])
self.action_layout.addWidget(self.ui_elements["key_input"])
self.action_layout.addWidget(self.ui_elements["key_press"])
self.action_layout.addWidget(self.ui_elements["key_release"])
-
- widget,_ = gremlin.ui.ui_common.getHContainer((self.ui_elements["key_add_press"], self.ui_elements["key_add_release"]))
+ widget, _ = gremlin.ui.ui_common.getHContainer(
+ (self.ui_elements["key_add_press"], self.ui_elements["key_add_release"])
+ )
self.action_layout.addWidget(widget)
self.action_layout.addWidget(container)
-
-
@QtCore.Slot()
def _key_delay_changed(self):
value = self.ui_elements["key_delay"].value()
@@ -314,10 +287,9 @@ def _mouse_button_ui(self):
return
self.ui_elements["mouse_label"] = QtWidgets.QLabel("Button")
- self.ui_elements["mouse_input"] = \
- gremlin.ui.ui_common.NoKeyboardPushButton(
- gremlin.types.MouseButton.to_string(action.button)
- )
+ self.ui_elements["mouse_input"] = gremlin.ui.ui_common.NoKeyboardPushButton(
+ gremlin.types.MouseButton.to_string(action.button)
+ )
self.ui_elements["mouse_input"].clicked.connect(
lambda: self._request_user_input([InputType.Mouse])
)
@@ -328,7 +300,7 @@ def _mouse_button_ui(self):
# are set to "press" with the inputs disabled
if action.button in [
gremlin.types.MouseButton.WheelDown,
- gremlin.types.MouseButton.WheelUp
+ gremlin.types.MouseButton.WheelUp,
]:
self.ui_elements["mouse_press"].setChecked(True)
self.ui_elements["mouse_press"].setEnabled(False)
@@ -340,12 +312,8 @@ def _mouse_button_ui(self):
else:
self.ui_elements["mouse_release"].setChecked(True)
- self.ui_elements["mouse_press"].toggled.connect(
- self._modify_mouse_button
- )
- self.ui_elements["mouse_release"].toggled.connect(
- self._modify_mouse_button
- )
+ self.ui_elements["mouse_press"].toggled.connect(self._modify_mouse_button)
+ self.ui_elements["mouse_release"].toggled.connect(self._modify_mouse_button)
self.action_layout.addWidget(self.ui_elements["mouse_label"])
self.action_layout.addWidget(self.ui_elements["mouse_input"])
@@ -372,12 +340,8 @@ def _mouse_motion_ui(self):
self.model.get_entry(self.index.row()).dy
)
- self.ui_elements["dx_spinbox"].valueChanged.connect(
- self._modify_mouse_motion
- )
- self.ui_elements["dy_spinbox"].valueChanged.connect(
- self._modify_mouse_motion
- )
+ self.ui_elements["dx_spinbox"].valueChanged.connect(self._modify_mouse_motion)
+ self.ui_elements["dy_spinbox"].valueChanged.connect(self._modify_mouse_motion)
self.action_layout.addWidget(self.ui_elements["dx_label"])
self.action_layout.addWidget(self.ui_elements["dx_spinbox"])
@@ -387,15 +351,21 @@ def _mouse_motion_ui(self):
def _pause_ui(self):
"""Creates and populates the PauseAction editor UI."""
self.ui_elements["duration_label"] = QtWidgets.QLabel("Duration")
- self.ui_elements["duration_spinbox"] = gremlin.ui.ui_common.DynamicDoubleSpinBox()
+ self.ui_elements["duration_spinbox"] = (
+ gremlin.ui.ui_common.DynamicDoubleSpinBox()
+ )
self.ui_elements["duration_spinbox"].setSingleStep(0.1)
self.ui_elements["duration_spinbox"].setMaximum(3600)
self.ui_elements["duration_is_random"] = QtWidgets.QCheckBox("Random")
- self.ui_elements["duration_max_label"] = QtWidgets.QLabel("Max duration (0 to disable)")
+ self.ui_elements["duration_max_label"] = QtWidgets.QLabel(
+ "Max duration (0 to disable)"
+ )
- self.ui_elements["duration_spinbox_max"] = gremlin.ui.ui_common.DynamicDoubleSpinBox()
+ self.ui_elements["duration_spinbox_max"] = (
+ gremlin.ui.ui_common.DynamicDoubleSpinBox()
+ )
self.ui_elements["duration_spinbox_max"].setSingleStep(0.1)
self.ui_elements["duration_spinbox_max"].setMaximum(3600)
@@ -403,7 +373,9 @@ def _pause_ui(self):
duration_max = 0
is_random = False
if self.model.get_entry(self.index.row()) is not None:
- model : gremlin.macro.PauseAction = self.model.get_entry(self.index.row()) # PauseAction model
+ model: gremlin.macro.PauseAction = self.model.get_entry(
+ self.index.row()
+ ) # PauseAction model
duration = model.duration
duration_max = model.duration_max
is_random = model.is_random
@@ -412,11 +384,14 @@ def _pause_ui(self):
self.ui_elements["duration_spinbox"].valueChanged.connect(self._update_pause)
self.ui_elements["duration_spinbox_max"].setValue(duration_max)
- self.ui_elements["duration_spinbox_max"].valueChanged.connect(self._update_pause_max)
+ self.ui_elements["duration_spinbox_max"].valueChanged.connect(
+ self._update_pause_max
+ )
self.ui_elements["duration_is_random"].setChecked(is_random)
- self.ui_elements["duration_is_random"].clicked.connect(self._update_pause_is_random)
-
+ self.ui_elements["duration_is_random"].clicked.connect(
+ self._update_pause_is_random
+ )
self.action_layout.addWidget(self.ui_elements["duration_is_random"])
self.action_layout.addWidget(self.ui_elements["duration_label"])
@@ -424,57 +399,51 @@ def _pause_ui(self):
self.action_layout.addWidget(self.ui_elements["duration_max_label"])
self.action_layout.addWidget(self.ui_elements["duration_spinbox_max"])
-
def _vjoy_ui(self):
"""Creates and populates the vJoyAction editor UI."""
if MacroActionEditor.locked:
return
-
+
action = self.model.get_entry(self.index.row())
if action is None:
return
-
- try:
+ try:
MacroActionEditor.locked = True
-
- if not "vjoy_selector" in self.ui_elements:
+
+ if "vjoy_selector" not in self.ui_elements:
# vJoy input selection
self.ui_elements["vjoy_selector"] = gremlin.ui.ui_common.VJoySelector(
self._modify_vjoy,
[
InputType.JoystickAxis,
InputType.JoystickButton,
- InputType.JoystickHat
- ]
+ InputType.JoystickHat,
+ ],
)
-
self.action_layout.addWidget(self.ui_elements["vjoy_selector"])
-
-
-
self.ui_elements["vjoy_selector"].set_selection(
- action.input_type,
- action.vjoy_id,
- action.input_id
+ action.input_type, action.vjoy_id, action.input_id
)
-
-
-
# Axis mode configuration
if action.input_type == InputType.JoystickAxis:
- if not "axis_type_layout" in self.ui_elements.keys():
+ if "axis_type_layout" not in self.ui_elements.keys():
self.ui_elements["axis_type_layout"] = QtWidgets.QHBoxLayout()
self.ui_elements["axis_value_layout"] = QtWidgets.QHBoxLayout()
self.ui_elements["axis_reverse"] = QtWidgets.QCheckBox("Reverse")
- self.ui_elements["axis_absolute"] = QtWidgets.QRadioButton("Absolute")
- self.ui_elements["axis_relative"] = QtWidgets.QRadioButton("Relative")
- self.ui_elements["axis_value"] = gremlin.ui.ui_common.QFloatLineEdit()
-
+ self.ui_elements["axis_absolute"] = QtWidgets.QRadioButton(
+ "Absolute"
+ )
+ self.ui_elements["axis_relative"] = QtWidgets.QRadioButton(
+ "Relative"
+ )
+ self.ui_elements["axis_value"] = (
+ gremlin.ui.ui_common.QFloatLineEdit()
+ )
if action.axis_type == "absolute":
self.ui_elements["axis_absolute"].setChecked(True)
@@ -482,7 +451,7 @@ def _vjoy_ui(self):
elif action.axis_type == "relative":
self.ui_elements["axis_absolute"].setChecked(False)
self.ui_elements["axis_relative"].setChecked(True)
-
+
self.ui_elements["axis_absolute"].clicked.connect(
self._modify_vjoy_axis
)
@@ -490,24 +459,26 @@ def _vjoy_ui(self):
self._modify_vjoy_axis
)
-
-
-
- self.ui_elements["axis_type_layout"].addWidget(self.ui_elements["axis_reverse"])
- self.ui_elements["axis_type_layout"].addWidget(self.ui_elements["axis_absolute"])
- self.ui_elements["axis_type_layout"].addWidget(self.ui_elements["axis_relative"])
- self.ui_elements["axis_type_layout"].addWidget(self.ui_elements["axis_value"])
+ self.ui_elements["axis_type_layout"].addWidget(
+ self.ui_elements["axis_reverse"]
+ )
+ self.ui_elements["axis_type_layout"].addWidget(
+ self.ui_elements["axis_absolute"]
+ )
+ self.ui_elements["axis_type_layout"].addWidget(
+ self.ui_elements["axis_relative"]
+ )
+ self.ui_elements["axis_type_layout"].addWidget(
+ self.ui_elements["axis_value"]
+ )
self.ui_elements["axis_type_layout"].addStretch()
-
+
self.action_layout.addLayout(self.ui_elements["axis_type_layout"])
-
-
self._create_joystick_inputs_ui(action)
finally:
MacroActionEditor.locked = False
-
def _create_joystick_inputs_ui(self, action):
# Handle display of value based on the actual input type
if action.input_type == InputType.JoystickAxis:
@@ -516,12 +487,14 @@ def _create_joystick_inputs_ui(self, action):
# self.ui_elements["axis_value"].setSingleStep(0.1)
# self.ui_elements["axis_value"].setDecimals(3)
self.ui_elements["axis_value"].setValue(action.value)
- self.ui_elements["axis_value"].valueChanged.connect(self._modify_axis_state)
+ self.ui_elements["axis_value"].valueChanged.connect(
+ self._modify_axis_state
+ )
self.action_layout.addWidget(self.ui_elements["axis_value"])
elif action.input_type == InputType.JoystickButton:
- if not "button_press" in self.ui_elements.keys():
+ if "button_press" not in self.ui_elements.keys():
self.ui_elements["button_press"] = QtWidgets.QRadioButton("Press")
self.ui_elements["button_release"] = QtWidgets.QRadioButton("Release")
if action.value:
@@ -539,11 +512,18 @@ def _create_joystick_inputs_ui(self, action):
self.action_layout.addWidget(self.ui_elements["button_release"])
elif action.input_type == InputType.JoystickHat:
- if not "hat_direction" in self.ui_elements.keys():
+ if "hat_direction" not in self.ui_elements.keys():
self.ui_elements["hat_direction"] = gremlin.ui.ui_common.QComboBox()
directions = [
- "Center", "North", "North East", "East", "South East",
- "South", "South West", "West", "North West"
+ "Center",
+ "North",
+ "North East",
+ "East",
+ "South East",
+ "South",
+ "South West",
+ "West",
+ "North West",
]
for val in directions:
self.ui_elements["hat_direction"].addItem(val)
@@ -558,27 +538,29 @@ def _create_joystick_inputs_ui(self, action):
)
self.action_layout.addWidget(self.ui_elements["hat_direction"])
-
def _remote_control_ui(self):
- self.ui_elements["remote_control_cb_label"] = QtWidgets.QLabel("Remote control command:")
+ self.ui_elements["remote_control_cb_label"] = QtWidgets.QLabel(
+ "Remote control command:"
+ )
cb = gremlin.ui.ui_common.QComboBox()
self.ui_elements["remote_control_cb"] = cb
self.ui_elements["remote_control_label"] = QtWidgets.QLabel()
commands = [
- VjoyAction.VJoyEnableLocalOnly,
+ VjoyAction.VJoyEnableLocalOnly,
VjoyAction.VJoyEnableRemoteOnly,
- VjoyAction.VJoyDisableLocal,
- VjoyAction.VJoyEnableLocal,
- VjoyAction.VJoyEnableRemote,
- VjoyAction.VJoyDisableRemote,
+ VjoyAction.VJoyDisableLocal,
+ VjoyAction.VJoyEnableLocal,
+ VjoyAction.VJoyEnableRemote,
+ VjoyAction.VJoyDisableRemote,
VjoyAction.VJoyEnableLocalAndRemote,
]
-
+
for cmd in commands:
cb.addItem(VjoyAction.to_name(cmd), cmd)
-
- self.ui_elements["remote_control_label"].setText(VjoyAction.to_description(commands[0]))
+ self.ui_elements["remote_control_label"].setText(
+ VjoyAction.to_description(commands[0])
+ )
cb.currentIndexChanged.connect(self._modify_remote_control)
self.action_layout.addWidget(self.ui_elements["remote_control_cb_label"])
@@ -603,8 +585,6 @@ def _modify_hat_state(self, state):
action.value = gremlin.common.direction_tuple_lookup[state]
self._update_model()
-
-
@QtCore.Slot(bool)
def _modify_key_state(self, state):
"""Updates the key activation state, i.e. press or release of a key.
@@ -621,7 +601,6 @@ def _modify_mouse_button(self, state):
action.is_pressed = self.ui_elements["mouse_press"].isChecked()
self._update_model()
-
def _modify_mouse_motion(self, _):
action = self.model.get_entry(self.index.row())
action.dx = self.ui_elements["dx_spinbox"].value()
@@ -641,7 +620,7 @@ def _update_pause_max(self, value):
:param value the pause max duration in seconds
"""
- self.model.get_entry(self.index.row()).duration_max= value
+ self.model.get_entry(self.index.row()).duration_max = value
self._update_model()
def _update_pause_is_random(self, data):
@@ -649,18 +628,20 @@ def _update_pause_is_random(self, data):
:param value the pause random function
"""
- self.model.get_entry(self.index.row()).is_random= self.ui_elements["duration_is_random"].isChecked()
+ self.model.get_entry(self.index.row()).is_random = self.ui_elements[
+ "duration_is_random"
+ ].isChecked()
self._update_model()
-
def _modify_remote_control(self, index):
- ''' occurs when the remote control command changes '''
+ """occurs when the remote control command changes"""
command = self.ui_elements["remote_control_cb"].itemData(index)
- self.ui_elements["remote_control_label"].setText(gremlin.input_devices.VjoyAction.to_description(command))
+ self.ui_elements["remote_control_label"].setText(
+ gremlin.input_devices.VjoyAction.to_description(command)
+ )
self.model.get_entry(self.index.row()).command = command
self._update_model()
-
def _update_model(self):
"""Forces an update of the model at the current index."""
self.model.update(self.index)
@@ -677,15 +658,13 @@ def _request_user_input(self, input_types):
callback = self._modify_joystick
if InputType.Keyboard in input_types:
- dialog = InputKeyboardDialog(parent = self, select_single=True, index = -1)
+ dialog = InputKeyboardDialog(parent=self, select_single=True, index=-1)
dialog.accepted.connect(self._keyboard_dialog_cb)
dialog.setModal(True)
dialog.showNormal()
else:
-
dialog = gremlin.ui.ui_common.InputListenerWidget(
- event_types = input_types,
- return_kb_event=True
+ event_types=input_types, return_kb_event=True
)
dialog.item_selected.connect(callback)
@@ -701,12 +680,12 @@ def _request_user_input(self, input_types):
int(geom.x() + geom.width() / 2 - 150),
int(geom.y() + geom.height() / 2 - 75),
300,
- 150
+ 150,
)
self.button_press_dialog.show()
def _keyboard_dialog_cb(self):
- ''' callled when the dialog completes '''
+ """callled when the dialog completes"""
# grab a new data index as this is a new entry
# index = self._keyboard_dialog.index
@@ -723,14 +702,12 @@ def _keyboard_dialog_cb(self):
def _add_key_press(self):
key = self.model.get_entry(self.index.row()).key
new_key = key.duplicate()
- entry = gremlin.macro.KeyAction(new_key,True)
+ entry = gremlin.macro.KeyAction(new_key, True)
self.model.add_entry(self.index.row(), entry)
-
@QtCore.Slot()
def _add_key_full(self):
key = self.model.get_entry(self.index.row()).key
-
# key press
new_key = key.duplicate()
@@ -739,7 +716,7 @@ def _add_key_full(self):
# pause
delay = gremlin.config.Configuration().macro_key_delay
- entry = gremlin.macro.PauseAction(delay/1000) # to ms
+ entry = gremlin.macro.PauseAction(delay / 1000) # to ms
self.model.add_entry(self.index.row(), entry)
# key release
@@ -747,25 +724,19 @@ def _add_key_full(self):
entry = gremlin.macro.KeyAction(new_key, True)
self.model.add_entry(self.index.row(), entry)
-
-
@QtCore.Slot()
def _add_key_release(self):
key = self.model.get_entry(self.index.row()).key
new_key = key.duplicate()
- entry = gremlin.macro.KeyAction(new_key,False)
+ entry = gremlin.macro.KeyAction(new_key, False)
self.model.add_entry(self.index.row(), entry)
-
def _modify_joystick(self, event):
self.model.set_entry(
gremlin.macro.JoystickAction(
- event.device_guid,
- event.event_type,
- event.identifier,
- event.value
+ event.device_guid, event.event_type, event.identifier, event.value
),
- self.index.row()
+ self.index.row(),
)
self._update_model()
gremlin.ui.ui_common.clear_layout(self.action_layout)
@@ -778,7 +749,9 @@ def _modify_key(self, event):
:param event the event containing information about the key to use
"""
- self.model.get_entry(self.index.row()).key = gremlin.keyboard.KeyMap.from_event(event)
+ self.model.get_entry(self.index.row()).key = gremlin.keyboard.KeyMap.from_event(
+ event
+ )
self._update_model()
gremlin.ui.ui_common.clear_layout(self.action_layout)
self.ui_elements = {}
@@ -825,26 +798,22 @@ def _modify_vjoy_value(self):
class MacroListModel(QtCore.QAbstractListModel):
-
"""Model representing a Macro.
This model supports model modification.
"""
- gfx_path = os.path.join(
- os.path.dirname(os.path.realpath(__file__)),
- "gfx"
- )
+ gfx_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "gfx")
icon_lookup = {}
-
value_format = {
- InputType.JoystickAxis:
- lambda entry: f"{entry.value:.3f}",
- InputType.JoystickButton:
- lambda entry: "pressed" if entry.value else "released",
- InputType.JoystickHat:
- lambda entry: gremlin.common.direction_tuple_lookup[entry.value]
+ InputType.JoystickAxis: lambda entry: f"{entry.value:.3f}",
+ InputType.JoystickButton: lambda entry: "pressed"
+ if entry.value
+ else "released",
+ InputType.JoystickHat: lambda entry: gremlin.common.direction_tuple_lookup[
+ entry.value
+ ],
}
def __init__(self, data_storage, parent=None):
@@ -855,11 +824,10 @@ def __init__(self, data_storage, parent=None):
QtCore.QAbstractListModel.__init__(self, parent)
from gremlin.util import load_icon
-
- MacroListModel.icon_lookup = {
+ MacroListModel.icon_lookup = {
"press": load_icon("press.svg"),
"release": load_icon("release.svg"),
- "pause": load_icon("pause.svg")
+ "pause": load_icon("pause.svg"),
}
self._data = data_storage
@@ -889,7 +857,7 @@ def data(self, index, role):
return ""
entry = self._data[idx]
-
+
if role == QtCore.Qt.SizeHintRole:
# size hint
return QtCore.QSize(200, 26)
@@ -910,19 +878,21 @@ def data(self, index, role):
for joy in gremlin.joystick_handling.joystick_devices():
if joy.device_guid == entry.device_guid:
device_name = joy.name
- display = f"{device_name} {InputType.to_string(entry.input_type).capitalize()} {entry.input_id} - {MacroListModel.value_format[entry.input_type](entry)}"
+ display = f"{device_name} {InputType.to_string(entry.input_type).capitalize()} {entry.input_id} - {MacroListModel.value_format[entry.input_type](entry)}"
elif isinstance(entry, gremlin.macro.KeyAction):
- display = f"{'Press' if entry.is_pressed else 'Release'} key {entry.key.name}"
+ display = (
+ f"{'Press' if entry.is_pressed else 'Release'} key {entry.key.name}"
+ )
elif isinstance(entry, gremlin.macro.MouseButtonAction):
if entry.button in [
gremlin.types.MouseButton.WheelDown,
gremlin.types.MouseButton.WheelUp,
]:
- display = f"{gremlin.types.MouseButton.to_string(entry.button)}"
+ display = f"{gremlin.types.MouseButton.to_string(entry.button)}"
else:
- display = f"{'Press' if entry.is_pressed else 'Release'} {gremlin.types.MouseButton.to_string(entry.button)} mouse button"
+ display = f"{'Press' if entry.is_pressed else 'Release'} {gremlin.types.MouseButton.to_string(entry.button)} mouse button"
elif isinstance(entry, gremlin.macro.MouseMotionAction):
- display = f"Move mouse by x: {entry.dx:d} y: {entry.dy:d}"
+ display = f"Move mouse by x: {entry.dx:d} y: {entry.dy:d}"
elif isinstance(entry, gremlin.macro.PauseAction):
msg = f"Pause for {entry.duration:.3f} s"
if entry.duration_max != 0:
@@ -931,17 +901,15 @@ def data(self, index, role):
msg += " (random)"
display = msg
-
elif isinstance(entry, gremlin.macro.VJoyMacroAction):
- display = f"vJoy {entry.vjoy_id} {InputType.to_string(entry.input_type).capitalize()} {entry.input_id} - {MacroListModel.value_format[entry.input_type](entry)}"
+ display = f"vJoy {entry.vjoy_id} {InputType.to_string(entry.input_type).capitalize()} {entry.input_id} - {MacroListModel.value_format[entry.input_type](entry)}"
elif isinstance(entry, gremlin.macro.RemoteControlAction):
display = f"Remote control: {VjoyAction.to_name(entry.command)}"
else:
raise gremlin.error.GremlinError("Unknown macro action")
-
-
- #syslog.debug(display)
+
+ # syslog.debug(display)
return display
elif role == QtCore.Qt.FontRole:
font = QtGui.QFont()
@@ -949,11 +917,11 @@ def data(self, index, role):
elif role == QtCore.Qt.ToolTipRole:
return "Macro entry"
elif role == QtCore.Qt.TextAlignmentRole:
- return QtCore.Qt.AlignmentFlag.AlignVCenter | QtCore.Qt.AlignmentFlag.AlignLeft
-
+ return (
+ QtCore.Qt.AlignmentFlag.AlignVCenter | QtCore.Qt.AlignmentFlag.AlignLeft
+ )
return None
-
def mimeTypes(self):
"""Returns the MIME types supported by this model for drag & drop.
@@ -972,7 +940,7 @@ def mimeData(self, index_list):
data = QtCore.QMimeData()
data.setData(
"data/macro-action",
- pickle.dumps((self._data[index_list[0].row()], index_list[0].row()))
+ pickle.dumps((self._data[index_list[0].row()], index_list[0].row())),
)
return data
@@ -1010,17 +978,21 @@ def flags(self, index):
# invalid indices are valid drop locations, i.e. in between existing
# entries.
if index.isValid():
- return super().flags(index) | \
- QtCore.Qt.ItemIsSelectable | \
- QtCore.Qt.ItemIsDragEnabled | \
- QtCore.Qt.ItemIsEnabled | \
- QtCore.Qt.ItemNeverHasChildren
+ return (
+ super().flags(index)
+ | QtCore.Qt.ItemIsSelectable
+ | QtCore.Qt.ItemIsDragEnabled
+ | QtCore.Qt.ItemIsEnabled
+ | QtCore.Qt.ItemNeverHasChildren
+ )
else:
- return QtCore.Qt.ItemIsSelectable | \
- QtCore.Qt.ItemIsDragEnabled | \
- QtCore.Qt.ItemIsDropEnabled | \
- QtCore.Qt.ItemIsEnabled | \
- QtCore.Qt.ItemNeverHasChildren
+ return (
+ QtCore.Qt.ItemIsSelectable
+ | QtCore.Qt.ItemIsDragEnabled
+ | QtCore.Qt.ItemIsDropEnabled
+ | QtCore.Qt.ItemIsEnabled
+ | QtCore.Qt.ItemNeverHasChildren
+ )
def supportedDropActions(self):
"""Return the drop actions supported by this model.
@@ -1036,9 +1008,7 @@ def get_entry(self, index):
:return entry stored at the given index
"""
if not 0 <= index < len(self._data):
- syslog.error(
- "Attempted to retrieve macro entry at invalid index"
- )
+ syslog.error("Attempted to retrieve macro entry at invalid index")
return None
return self._data[index]
@@ -1057,7 +1027,6 @@ def set_entry(self, entry, index):
self._data[index] = entry
-
def remove_entry(self, index):
"""Removes the entry at the provided index.
@@ -1089,8 +1058,7 @@ def swap(self, id1, id2):
:param id2 second index
"""
if -1 < id1 < len(self._data) and -1 < id2 < len(self._data):
- self._data[id1], self._data[id2] = \
- self._data[id2], self._data[id1]
+ self._data[id1], self._data[id2] = self._data[id2], self._data[id1]
self.dataChanged.emit(self.index(id1, 0), self.index(id2, 0))
def update(self, index):
@@ -1102,7 +1070,6 @@ def update(self, index):
class MacroListView(QtWidgets.QListView):
-
"""Implements a specialized list view.
The purpose of this class is to properly emit a "clicked" event when
@@ -1135,16 +1102,13 @@ def keyPressEvent(self, evt):
self.model().remove_entry(new_index.row())
if new_index.row() >= self.model().rowCount():
new_index = self.model().index(
- self.model().rowCount()-1,
- 0,
- QtCore.QModelIndex()
+ self.model().rowCount() - 1, 0, QtCore.QModelIndex()
)
self.setCurrentIndex(new_index)
self.clicked.emit(new_index)
class AbstractRepeatMacroWidget(QtWidgets.QWidget):
-
"""Abstract base class for all repeat UI widgets."""
def __init__(self, data, parent=None):
@@ -1169,20 +1133,17 @@ def _create_ui(self):
def _populate_ui(self):
"""Populates the UI components."""
raise gremlin.error.MissingImplementationError(
- "AbstractRepeatMacroWidget::_populate_ui not "
- "implemented in subclass"
+ "AbstractRepeatMacroWidget::_populate_ui not " "implemented in subclass"
)
def _update_data(self):
"""Updates the managed data based on the UI contents."""
raise gremlin.error.MissingImplementationError(
- "AbstractRepeatMacroWidget::_populate_ui not "
- "implemented in subclass"
+ "AbstractRepeatMacroWidget::_populate_ui not " "implemented in subclass"
)
class CountRepeatMacroWidget(AbstractRepeatMacroWidget):
-
"""Repeat UI to specify a number of times to repeat a macro."""
def __init__(self, data, parent=None):
@@ -1217,7 +1178,6 @@ def _update_data(self):
class ToggleRepeatMacroWidget(AbstractRepeatMacroWidget):
-
"""Repeat UI for a toggle repetition."""
def __init__(self, data, parent=None):
@@ -1241,7 +1201,6 @@ def _update_data(self):
class HoldRepeatMacroWidget(AbstractRepeatMacroWidget):
-
"""Repeat UI for a hold repetition."""
def __init__(self, data, parent=None):
@@ -1265,24 +1224,23 @@ def _update_data(self):
class MacroSettingsWidget(QtWidgets.QWidget):
-
"""Widget presenting macro settings."""
# Lookup tables mapping between display name and enum name
name_to_widget = {
"Count": CountRepeatMacroWidget,
"Toggle": ToggleRepeatMacroWidget,
- "Hold": HoldRepeatMacroWidget
+ "Hold": HoldRepeatMacroWidget,
}
name_to_storage = {
"Count": gremlin.macro.CountRepeat,
"Toggle": gremlin.macro.ToggleRepeat,
- "Hold": gremlin.macro.HoldRepeat
+ "Hold": gremlin.macro.HoldRepeat,
}
storage_to_name = {
gremlin.macro.CountRepeat: "Count",
gremlin.macro.ToggleRepeat: "Toggle",
- gremlin.macro.HoldRepeat: "Hold"
+ gremlin.macro.HoldRepeat: "Hold",
}
def __init__(self, action_data, parent=None):
@@ -1312,9 +1270,7 @@ def _create_ui(self):
self.repeat_dropdown.addItems(["None", "Count", "Toggle", "Hold"])
self.repeat_widget = None
if type(self.data.repeat) in MacroSettingsWidget.storage_to_name:
- mode_name = MacroSettingsWidget.storage_to_name[
- type(self.data.repeat)
- ]
+ mode_name = MacroSettingsWidget.storage_to_name[type(self.data.repeat)]
self.repeat_widget = MacroSettingsWidget.name_to_widget[mode_name](
self.data.repeat
)
@@ -1323,9 +1279,7 @@ def _create_ui(self):
self.exclusive_checkbox.setChecked(self.data.exclusive)
self.force_remote_checkbox.setChecked(self.data.force_remote)
if self.data.repeat is not None:
- mode_name = MacroSettingsWidget.storage_to_name[
- type(self.data.repeat)
- ]
+ mode_name = MacroSettingsWidget.storage_to_name[type(self.data.repeat)]
self.repeat_widget = MacroSettingsWidget.name_to_widget[mode_name](
self.data.repeat
)
@@ -1340,7 +1294,7 @@ def _create_ui(self):
widget = QtWidgets.QWidget()
box = QtWidgets.QHBoxLayout(widget)
box.addWidget(self.exclusive_checkbox)
- box.addWidget(self.force_remote_checkbox)
+ box.addWidget(self.force_remote_checkbox)
self.group_layout.addWidget(widget)
self.group_layout.addWidget(self.repeat_dropdown)
if self.repeat_widget is not None:
@@ -1356,12 +1310,10 @@ def _update_settings(self, value):
# Only create a new repeat widget if it changed
widget_type = MacroSettingsWidget.name_to_widget.get(
- self.repeat_dropdown.currentText(),
- None
+ self.repeat_dropdown.currentText(), None
)
storage_type = MacroSettingsWidget.name_to_storage.get(
- self.repeat_dropdown.currentText(),
- None
+ self.repeat_dropdown.currentText(), None
)
if widget_type is None and self.repeat_widget is not None:
self.data.repeat = None
@@ -1371,8 +1323,9 @@ def _update_settings(self, value):
if old_item is not None:
old_item.widget().hide()
old_item.widget().deleteLater()
- elif widget_type is not None and \
- not isinstance(self.repeat_widget, widget_type):
+ elif widget_type is not None and not isinstance(
+ self.repeat_widget, widget_type
+ ):
self.data.repeat = storage_type()
self.repeat_widget = widget_type(self.data.repeat)
@@ -1384,19 +1337,14 @@ def _update_settings(self, value):
class MacroWidget(gremlin.ui.input_item.AbstractActionWidget):
-
"""Widget which allows creating and editing of macros."""
-
from gremlin.util import get_icon_path
locked = False
# Path to graphics
- gfx_path = os.path.join(
- os.path.dirname(os.path.realpath(__file__)),
- "gfx"
- )
+ gfx_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "gfx")
def __init__(self, action_data, parent=None):
"""Creates a new UI widget.
@@ -1412,9 +1360,6 @@ def __init__(self, action_data, parent=None):
self._recording_times = {None: time.time()}
self._recording_values = {None: 0.0}
-
-
-
self._create_ui()
self._populate_ui()
@@ -1422,9 +1367,8 @@ def _create_ui(self):
"""Creates the UI of this widget."""
if MacroWidget.locked:
return
-
- try:
+ try:
MacroWidget.locked = True
self.model = MacroListModel(self.action_data.sequence)
@@ -1436,7 +1380,7 @@ def _create_ui(self):
self.editor_settings_layout = QtWidgets.QVBoxLayout()
self.buttons_layout = QtWidgets.QVBoxLayout()
- #self.delegate = MacroItemDelegate(self)
+ # self.delegate = MacroItemDelegate(self)
# Create list view for macro actions and setup drag & drop support
self.list_view = MacroListView()
@@ -1444,10 +1388,9 @@ def _create_ui(self):
self.list_view.setDefaultDropAction(QtCore.Qt.MoveAction)
self.list_view.setModel(self.model)
self.list_view.setModelColumn(0)
- #self.list_view.setCurrentIndex(self.model.index(0, 0))
+ # self.list_view.setCurrentIndex(self.model.index(0, 0))
self.list_view.clicked.connect(self._edit_action)
- #self.list_view.setItemDelegate(self.delegate)
-
+ # self.list_view.setItemDelegate(self.delegate)
# Create editor as well as settings place holder widgets
self.editor_widget = QtWidgets.QWidget()
@@ -1458,97 +1401,70 @@ def _create_ui(self):
# Create buttons used to modify and interact with the macro actions
self.button_new_entry = self._create_toolbutton(
- "list_add.svg",
- "Add a new action",
- False
+ "gfx/list_add.svg", "Add a new action", False
)
self.button_new_entry.clicked.connect(self._add_entry)
self.button_delete = self._create_toolbutton(
- "list_delete.svg",
- "Delete currently selected entry",
- False
+ "gfx/list_delete.svg", "Delete currently selected entry", False
)
self.button_delete.clicked.connect(self._delete_cb)
self.button_pause = self._create_toolbutton(
- "pause.svg",
- "Add pause after the currently selected entry",
- False
+ "pause.svg", "Add pause after the currently selected entry", False
)
self.button_pause.clicked.connect(self._pause_cb)
self.button_record = self._create_toolbutton(
- [
- "macro_record.svg",
- "macro_record_on.svg"
- ],
+ ["macro_record.svg", "macro_record_on.svg"],
"Record keyboard and joystick inputs",
True,
- False
+ False,
)
self.button_record.clicked.connect(self._record_cb)
self.record_time = self._create_toolbutton(
- [
- "time.svg",
- "time_on.svg"
- ],
+ ["time.svg", "time_on.svg"],
"Record pauses between actions",
True,
- False
+ False,
)
# Input type recording buttons
cfg = gremlin.config.Configuration()
self.record_axis = self._create_toolbutton(
- [
- "record_axis.svg",
- "record_axis_on.svg"
- ],
+ ["record_axis.svg", "record_axis_on.svg"],
"Record joystick axis events",
True,
- cfg.macro_record_axis
+ cfg.macro_record_axis,
)
self.record_axis.clicked.connect(self._update_record_settings)
self.record_button = self._create_toolbutton(
- [
- "record_button.svg",
- "record_button_on.svg"
- ],
+ ["record_button.svg", "record_button_on.svg"],
"Record joystick button events",
True,
- cfg.macro_record_button
+ cfg.macro_record_button,
)
self.record_button.clicked.connect(self._update_record_settings)
self.record_hat = self._create_toolbutton(
- [
- "record_hat.svg",
- "record_hat_on.svg"
- ],
+ ["record_hat.svg", "record_hat_on.svg"],
"Record joystick hat events",
True,
- cfg.macro_record_hat
+ cfg.macro_record_hat,
)
self.record_hat.clicked.connect(self._update_record_settings)
self.record_key = self._create_toolbutton(
- [
- "record_key.svg",
- "record_key_on.svg"
- ],
+ ["record_key.svg", "record_key_on.svg"],
"Record keyboard events",
True,
- cfg.macro_record_keyboard
+ cfg.macro_record_keyboard,
)
self.record_key.clicked.connect(self._update_record_settings)
self.record_mouse = self._create_toolbutton(
- [
- "record_mouse.svg",
- "record_mouse_on.svg"
- ],
+ ["record_mouse.svg", "record_mouse_on.svg"],
"Record mouse events",
True,
- cfg.macro_record_mouse
+ cfg.macro_record_mouse,
)
self.record_mouse.clicked.connect(self._update_record_settings)
@@ -1571,12 +1487,11 @@ def _create_ui(self):
self.toolbar.addWidget(self.record_key)
self.toolbar.addWidget(self.record_mouse)
- #required_height = self.toolbar.frameGeometry().height()
+ # required_height = self.toolbar.frameGeometry().height()
self.toolbar.setMinimumHeight(260)
# Assemble the entire widget
-
self.main_layout.addWidget(self.list_view)
self.main_layout.addWidget(self.toolbar)
@@ -1596,18 +1511,15 @@ def _create_toolbutton(self, icon_path, tooltip, is_checkable, default_on=True):
:param default_on whether or not to toggle the button by default
"""
from gremlin.util import load_pixmap, load_icon
+
button = QtWidgets.QToolButton()
-
+
if isinstance(icon_path, list):
pixmap_0 = load_pixmap(icon_path[0])
pixmap_1 = load_pixmap(icon_path[1])
icon = QtGui.QIcon()
icon.addPixmap(pixmap_0, QtGui.QIcon.Normal)
- icon.addPixmap(
- pixmap_1,
- QtGui.QIcon.Active,
- QtGui.QIcon.On
- )
+ icon.addPixmap(pixmap_1, QtGui.QIcon.Active, QtGui.QIcon.On)
button.setIcon(icon)
else:
button.setIcon(load_icon(icon_path))
@@ -1649,14 +1561,20 @@ def _refresh_editor_ui(self):
def _create_joystick_action(self, event):
# Check whether or not to record a specific type of input
- if event.event_type == InputType.JoystickAxis and \
- not self.record_axis.isChecked():
+ if (
+ event.event_type == InputType.JoystickAxis
+ and not self.record_axis.isChecked()
+ ):
return
- if event.event_type == InputType.JoystickButton and \
- not self.record_button.isChecked():
+ if (
+ event.event_type == InputType.JoystickButton
+ and not self.record_button.isChecked()
+ ):
return
- if event.event_type == InputType.JoystickHat and \
- not self.record_hat.isChecked():
+ if (
+ event.event_type == InputType.JoystickHat
+ and not self.record_hat.isChecked()
+ ):
return
# If this is an axis motion do some checks such that we don't spam
@@ -1664,28 +1582,29 @@ def _create_joystick_action(self, event):
add_new_entry = True
if event.event_type == InputType.JoystickAxis:
cur_index = self.list_view.currentIndex().row()
- entry = self.model.get_entry(cur_index)
+ self.model.get_entry(cur_index)
if event in self._recording_times:
if time.time() - self._recording_times[event] < self._polling_rate:
add_new_entry = False
- elif abs(event.value - self._recording_values[event]) < \
- self._minimum_change_amount:
+ elif (
+ abs(event.value - self._recording_values[event])
+ < self._minimum_change_amount
+ ):
add_new_entry = False
if add_new_entry:
if self.record_time.isChecked():
- self._append_entry(gremlin.macro.PauseAction(
- time.time() - max(self._recording_times.values())
- ))
+ self._append_entry(
+ gremlin.macro.PauseAction(
+ time.time() - max(self._recording_times.values())
+ )
+ )
value = event.is_pressed
if event.event_type != InputType.JoystickButton:
value = event.value
action = gremlin.macro.JoystickAction(
- event.device_guid,
- event.event_type,
- event.identifier,
- value
+ event.device_guid, event.event_type, event.identifier, value
)
self._recording_times[event] = time.time()
self._recording_values[event] = event.value
@@ -1701,15 +1620,13 @@ def _create_key_action(self, event):
return
if self.record_time.isChecked():
- self._append_entry(gremlin.macro.PauseAction(
- time.time() - max(self._recording_times.values())
- ))
+ self._append_entry(
+ gremlin.macro.PauseAction(
+ time.time() - max(self._recording_times.values())
+ )
+ )
action = gremlin.macro.KeyAction(
- key_from_code(
- event.identifier[0],
- event.identifier[1]
- ),
- event.is_pressed
+ key_from_code(event.identifier[0], event.identifier[1]), event.is_pressed
)
self._recording_times["keyboard"] = time.time()
self._append_entry(action)
@@ -1720,9 +1637,11 @@ def _create_mouse_action(self, event):
return
if self.record_time.isChecked():
- self._append_entry(gremlin.macro.PauseAction(
- time.time() - max(self._recording_times.values())
- ))
+ self._append_entry(
+ gremlin.macro.PauseAction(
+ time.time() - max(self._recording_times.values())
+ )
+ )
action = gremlin.macro.MouseButtonAction(event.identifier, event.is_pressed)
self._recording_times["mouse"] = time.time()
@@ -1762,7 +1681,6 @@ def _pause_cb(self):
self._insert_entry_at_current_index(gremlin.macro.PauseAction(0.250))
self._refresh_editor_ui()
-
def _add_entry(self):
self._pause_cb()
@@ -1784,7 +1702,7 @@ def _insert_entry_at_current_index(self, entry):
"""
cur_index = self.list_view.currentIndex().row()
self.model.add_entry(cur_index, entry)
- self.list_view.setCurrentIndex(self.model.index(cur_index+1, 0))
+ self.list_view.setCurrentIndex(self.model.index(cur_index + 1, 0))
self._refresh_editor_ui()
def _append_entry(self, entry):
@@ -1796,4 +1714,3 @@ def _append_entry(self, entry):
self.model.add_entry(index, entry)
self.list_view.setCurrentIndex(self.model.index(index + 1, 0))
self._refresh_editor_ui()
-
diff --git a/gremlin/plugin_manager.py b/gremlin/plugin_manager.py
index 2cdb21f2..0fe0c421 100644
--- a/gremlin/plugin_manager.py
+++ b/gremlin/plugin_manager.py
@@ -1,4 +1,4 @@
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,7 +17,6 @@
import importlib
import logging
import os
-import copy
from . import common, error
from gremlin.util import *
@@ -26,9 +25,9 @@
syslog = logging.getLogger("system")
+
@SingletonDecorator
class ContainerPlugins:
-
"""Handles discovery and handling of container plugins."""
def __init__(self):
@@ -36,7 +35,7 @@ def __init__(self):
self.reset()
def reset(self):
- ''' resets the plugins '''
+ """resets the plugins"""
self._plugins = {}
self._discover_plugins()
@@ -47,17 +46,16 @@ def reset(self):
self._create_maps()
- self._parent_widget_map = {} # map of item data to QT widget main UI container widget
- self._input_data_container_map = {} # map of item data to the actual containers created for it
-
+ self._parent_widget_map = {} # map of item data to QT widget main UI container widget
+ self._input_data_container_map = {} # map of item data to the actual containers created for it
def reset_functors(self):
- ''' clears functor tracking '''
+ """clears functor tracking"""
self._functors = []
def register_functor(self, functor):
- ''' registers a functor for latching purposes'''
- if not functor in self._functors:
+ """registers a functor for latching purposes"""
+ if functor not in self._functors:
self._functors.append(functor)
@property
@@ -73,28 +71,28 @@ def repository(self):
return self._plugins
def set_widget(self, item_data, widget):
- ''' sets the associated parent widget of a container for the specific input type'''
+ """sets the associated parent widget of a container for the specific input type"""
self._parent_widget_map[item_data] = widget
def get_widget(self, item_data):
- ''' gets the associated parent widget of a container for the specific input type '''
+ """gets the associated parent widget of a container for the specific input type"""
if item_data in self._parent_widget_map.keys():
return self._parent_widget_map[item_data]
return None
def set_container_data(self, item_data, container):
- if not item_data in self._input_data_container_map.keys():
+ if item_data not in self._input_data_container_map.keys():
self._input_data_container_map[item_data] = []
- if not container in self._input_data_container_map[item_data]:
+ if container not in self._input_data_container_map[item_data]:
self._input_data_container_map[item_data].append(container)
def get_container(self, item_data):
- if not item_data in self._input_data_container_map.keys():
+ if item_data not in self._input_data_container_map.keys():
return []
return self._input_data_container_map[item_data]
def get_parent_widget(self, container):
- ''' gets the parent widget of the given container '''
+ """gets the parent widget of the given container"""
for item_data, containers in self._input_data_container_map.items():
for container_item in containers:
if container == container_item:
@@ -102,7 +100,6 @@ def get_parent_widget(self, container):
# not found for this container
return None
-
@property
def tag_map(self):
"""Returns the mapping from a container tag to the container plugin.
@@ -118,14 +115,13 @@ def get_class(self, name):
:return class object corresponding to the provided name
"""
if name not in self._name_to_type_map:
- raise error.GremlinError(
- f"No container with name '{name}' exists"
- )
+ raise error.GremlinError(f"No container with name '{name}' exists")
return self._name_to_type_map[name]
def _discover_plugins(self):
"""Processes known plugin folders for action plugins."""
import gremlin.shared_state
+
plugin_folder = "container_plugins"
root_path = gremlin.shared_state.root_path
walk_path = os.path.join(root_path, plugin_folder)
@@ -134,7 +130,6 @@ def _discover_plugins(self):
raise error(f"Unable to find container plugins: {walk_path}")
for root, dirs, files in os.walk(walk_path):
-
for fname in [v for v in files if v == "__init__.py"]:
try:
folder, module = os.path.split(root)
@@ -144,13 +139,10 @@ def _discover_plugins(self):
# Attempt to load the file and if it looks like a proper
# action_plugins store it in the registry
- plugin = importlib.import_module(
- f"container_plugins.{module}"
- )
+ plugin = importlib.import_module(f"container_plugins.{module}")
if "version" in plugin.__dict__:
self._plugins[plugin.name] = plugin.create
- log_sys(f"\tLoaded container plugin: {plugin.name}"
- )
+ log_sys(f"\tLoaded container plugin: {plugin.name}")
else:
del plugin
except Exception as e:
@@ -166,14 +158,19 @@ def _create_maps(self):
self._tag_to_type_map[entry.tag] = entry
self._name_to_type_map[entry.name] = entry
- def duplicate(self, container, input_item = None):
- ''' duplicates a container '''
+ def duplicate(self, container, input_item=None):
+ """duplicates a container"""
# because containers can be quite complex - we'll just generate the xml and change IDs as needed and reload
# into a new container of the same type
from gremlin.base_profile import AbstractContainer, InputItem
from gremlin.util import get_guid
- assert isinstance(container, AbstractContainer),"Invalid container data for duplicate()"
- assert isinstance(input_item, InputItem),"Invalid input item tyhpe for duplicate()"
+
+ assert isinstance(
+ container, AbstractContainer
+ ), "Invalid container data for duplicate()"
+ assert isinstance(
+ input_item, InputItem
+ ), "Invalid input item tyhpe for duplicate()"
if input_item is None:
input_item = container.parent
@@ -185,7 +182,7 @@ def duplicate(self, container, input_item = None):
new_container = container_tag_map[container_type](input_item)
new_container.from_xml(node, input_item)
- #new_container = copy.deepcopy(container)
+ # new_container = copy.deepcopy(container)
for action_set in new_container.get_action_sets():
for action in action_set:
@@ -194,16 +191,8 @@ def duplicate(self, container, input_item = None):
return new_container
-
-
-
-
-
-
-
@SingletonDecorator
class ActionPlugins:
-
"""Handles discovery and handling of action plugins."""
def __init__(self):
@@ -211,7 +200,7 @@ def __init__(self):
self.reset()
def reset(self):
- ''' resets the plugins '''
+ """resets the plugins"""
self._plugins = {}
self._type_to_action_map = {}
self._type_to_name_map = {}
@@ -255,9 +244,7 @@ def get_class(self, name):
:return class object corresponding to the provided name
"""
if name not in self._name_to_type_map:
- raise error.GremlinError(
- f"No action with name '{name}' exists"
- )
+ raise error.GremlinError(f"No action with name '{name}' exists")
return self._name_to_type_map[name]
def plugins_requiring_parameter(self, param_name):
@@ -287,6 +274,7 @@ def _create_action_name_map(self):
def _discover_plugins(self):
"""Processes known plugin folders for action plugins."""
import gremlin.shared_state
+
plugin_folder = "action_plugins"
root_path = gremlin.shared_state.root_path
walk_path = os.path.join(root_path, plugin_folder)
@@ -306,9 +294,7 @@ def _discover_plugins(self):
# Attempt to load the file and if it looks like a proper
# action_plugins store it in the registry
- plugin = importlib.import_module(
- f"action_plugins.{module}"
- )
+ plugin = importlib.import_module(f"action_plugins.{module}")
if "version" in plugin.__dict__:
self._plugins[plugin.name] = plugin.create
log_sys(f"\tLoaded action plugin: {plugin.name}")
@@ -326,9 +312,8 @@ def _discover_plugins(self):
if error_count > 0:
log_sys_error(f"{error_count} plugin(s) failed to load")
-
- def duplicate(self, action, container, input_item = None):
- ''' duplicates an action and gives it a unique ID '''
+ def duplicate(self, action, container, input_item=None):
+ """duplicates an action and gives it a unique ID"""
from gremlin.util import get_guid
if input_item is None:
@@ -343,10 +328,11 @@ def duplicate(self, action, container, input_item = None):
return new_action
def fromClipboard(self, container):
- ''' grabs an action from the clipboard '''
+ """grabs an action from the clipboard"""
from lxml import etree
from gremlin.clipboard import Clipboard, ObjectEncoder, EncoderType
import gremlin.base_profile
+
clipboard = Clipboard()
if isinstance(clipboard.data, ObjectEncoder):
item = clipboard.data
@@ -354,7 +340,9 @@ def fromClipboard(self, container):
xml = item.data
if container is None:
# no container provided for the parent - can't duplicate
- syslog.error("FromClipboard: unable to instantiate action because no container is provided")
+ syslog.error(
+ "FromClipboard: unable to instantiate action because no container is provided"
+ )
return None
if container is not None:
node = etree.fromstring(xml)
@@ -364,5 +352,3 @@ def fromClipboard(self, container):
elif isinstance(clipboard.data, gremlin.base_profile.AbstractAction):
return action
return None
-
-
diff --git a/gremlin/process_monitor.py b/gremlin/process_monitor.py
index a35687ca..4283d3df 100644
--- a/gremlin/process_monitor.py
+++ b/gremlin/process_monitor.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -35,9 +35,9 @@
syslog = logging.getLogger("system")
+
@SingletonDecorator
class ProcessMonitor(QtCore.QObject):
-
"""Monitors the currently active window process.
This class continuously monitors the active window and whenever
@@ -48,9 +48,6 @@ class ProcessMonitor(QtCore.QObject):
# Signal emitted when the active window changes
process_changed = QtCore.Signal(str)
-
-
-
def __init__(self):
"""Creates a new instance."""
QtCore.QObject.__init__(self)
@@ -65,23 +62,22 @@ def __init__(self):
el = gremlin.event_handler.EventListener()
el.shutdown.connect(self.stop)
el.profile_start.connect(self.start)
- #el.profile_stop_toolbar.connect(self.stop) # stop listener only if manual toolbar button clicked
+ # el.profile_stop_toolbar.connect(self.stop) # stop listener only if manual toolbar button clicked
el.process_monitor_changed.connect(self._check_monitor)
-
@property
def enabled(self) -> bool:
return self._enabled
-
+
@enabled.setter
- def enabled(self, value : bool):
+ def enabled(self, value: bool):
self._enabled = value
if not value and self._running:
- # stop the profile auto
+ # stop the profile auto
self.stop()
def _check_monitor(self):
- ''' executes when process monitoring related actions change '''
+ """executes when process monitoring related actions change"""
config = gremlin.config.Configuration()
option_auto_load = config.autoload_profiles
option_auto_load_on_focus = config.activate_on_process_focus
@@ -90,8 +86,6 @@ def _check_monitor(self):
if option_auto_load_on_focus:
# start monitoring processes if auto activating based on processes
self.start()
-
-
def start(self):
"""Starts monitoring the current process."""
@@ -99,7 +93,7 @@ def start(self):
option_auto_load = config.autoload_profiles
option_auto_load_on_focus = config.activate_on_process_focus
syslog = logging.getLogger("system")
-
+
if option_auto_load or option_auto_load_on_focus:
self._enabled = True
if not self._running:
@@ -108,13 +102,12 @@ def start(self):
self._running = True
self._update_thread = threading.Thread(target=self._update, daemon=True)
self._update_thread.start()
-
def stop(self):
"""Stops monitoring the current process."""
if not self._running:
- return # nothing to do
-
+ return # nothing to do
+
self._running = False
# verbose = gremlin.config.Configuration().verbose_mode_process
syslog = logging.getLogger("system")
@@ -128,22 +121,19 @@ def _update(self):
"""Monitors the active process for changes."""
while self._running:
if self._enabled:
- _, pid = win32process.GetWindowThreadProcessId(win32gui.GetForegroundWindow())
+ _, pid = win32process.GetWindowThreadProcessId(
+ win32gui.GetForegroundWindow()
+ )
if pid != self._current_pid:
self._current_pid = pid
handle = self.kernel32.OpenProcess(
- PROCESS_QUERY_LIMITED_INFORMATION,
- False,
- pid
+ PROCESS_QUERY_LIMITED_INFORMATION, False, pid
)
self._buffer_size = ctypes.wintypes.DWORD(1024)
self.kernel32.QueryFullProcessImageNameA(
- handle,
- 0,
- self._buffer,
- ctypes.byref(self._buffer_size)
+ handle, 0, self._buffer, ctypes.byref(self._buffer_size)
)
self.kernel32.CloseHandle(handle)
@@ -169,7 +159,8 @@ def list_current_processes():
:return list of active process executable paths
"""
from win32com.client import GetObject
- wmi = GetObject('winmgmts:')
+
+ wmi = GetObject("winmgmts:")
processes = wmi.InstancesOf("Win32_Process")
process_list = []
for entry in processes:
diff --git a/gremlin/profile.py b/gremlin/profile.py
index ff2e1bf2..e2d9093d 100644
--- a/gremlin/profile.py
+++ b/gremlin/profile.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,38 +16,35 @@
# along with this program. If not, see .
from __future__ import annotations
-from abc import abstractmethod, ABCMeta
-import codecs
-import collections
import copy
import logging
import os
import shutil
import uuid
-#from xml.dom import minidom
+
+# from xml.dom import minidom
from lxml import etree as ElementTree
import gremlin.base_classes
import gremlin.base_profile
-from PySide6 import QtCore
import dinput
import gremlin.config
import gremlin.shared_state
import gremlin.actions
from gremlin.util import *
-from gremlin.input_types import InputType
from . import error, joystick_handling
syslog = logging.getLogger("system")
-def mode_list(profile = None):
+
+def mode_list(profile=None):
"""Returns a list of all modes based on the given node.
:param node a node from a profile tree
- :return list of modes in the profile
+ :return list of modes in the profile
"""
- profile : gremlin.base_profile.Profile
+ profile: gremlin.base_profile.Profile
if not profile:
profile = gremlin.shared_state.current_profile
if profile:
@@ -56,9 +53,7 @@ def mode_list(profile = None):
return []
-
class ProfileConverter:
-
"""Handle converting and checking profiles."""
# Current profile version number
@@ -74,15 +69,15 @@ def is_current(self, fname):
"""
if not os.path.isfile(fname):
return True
-
+
tree = ElementTree.parse(fname)
root = tree.getroot()
version = self._determine_version(root)
- return version == ProfileConverter.current_version # or version == 9
-
+ return version == ProfileConverter.current_version # or version == 9
+
def convert_to_ex(self, fname):
- ''' applies the options and converts the profile '''
+ """applies the options and converts the profile"""
import gremlin.util
try:
@@ -91,7 +86,7 @@ def convert_to_ex(self, fname):
new_root = self._convert_to_ex(root, fname)
tree = ElementTree.tostring(new_root)
- tree.write(fname, pretty_print=True,xml_declaration=True,encoding="utf-8")
+ tree.write(fname, pretty_print=True, xml_declaration=True, encoding="utf-8")
except:
gremlin.util.m
@@ -117,7 +112,7 @@ def convert_profile(self, fname):
6: self._convert_from_v6,
7: self._convert_from_v7,
8: self._convert_from_v8,
- 9: None
+ 9: None,
}
# Create a backup of the outdated profile
@@ -130,23 +125,27 @@ def convert_profile(self, fname):
while old_version < ProfileConverter.current_version:
if old_version in conversion_map:
convert = conversion_map[old_version]
- if convert:
+ if convert:
if new_root is None:
new_root = convert(root, fname=fname)
else:
new_root = convert(new_root, fname=fname)
converted = True
old_version += 1
-
+
else:
# syslog = logging.getLogger("system")
- syslog.warning(f"Unexpected version: {old_version} found in profile. Some unsupported features may not have loaded correctly.")
+ syslog.warning(
+ f"Unexpected version: {old_version} found in profile. Some unsupported features may not have loaded correctly."
+ )
if converted:
if new_root is not None:
# Save converted version
tree = ElementTree.tostring(new_root)
- tree.write(fname, pretty_print=True,xml_declaration=True,encoding="utf-8")
+ tree.write(
+ fname, pretty_print=True, xml_declaration=True, encoding="utf-8"
+ )
else:
raise error.ProfileError("Failed to convert profile")
@@ -161,9 +160,7 @@ def _determine_version(self, root):
elif root.tag == "profile":
return int(root.get("version"))
else:
- raise error.ProfileError(
- "Invalid profile version encountered"
- )
+ raise error.ProfileError("Invalid profile version encountered")
def _convert_from_v1(self, root, fname=None):
"""Converts v1 profiles to v2 profiles.
@@ -178,8 +175,7 @@ def _convert_from_v1(self, root, fname=None):
devices = ElementTree.Element("devices")
for node in root.iter("device"):
# Modify each node to include the correct type attribute
- if node.get("name") == "keyboard" and \
- int(node.get("windows_id")) == 0:
+ if node.get("name") == "keyboard" and int(node.get("windows_id")) == 0:
node.set("type", "keyboard")
else:
node.set("type", "joystick")
@@ -219,14 +215,14 @@ def _convert_from_v2(self, root, fname=None):
def _convert_from_v3(self, root, fname=None):
"""Converts v3 profiles to v4 profiles.
-
+
The following operations are performed in this conversion:
- embed all actions in individual BasicContainer containers
- remove button and keyboard conditions
- move hat and axis condition from actions to containers
- replace double macros for keyboard remaps with the new map to
keyboard action
-
+
:param root the v3 profile
:return v4 representation of the profile
"""
@@ -234,7 +230,6 @@ def _convert_from_v3(self, root, fname=None):
new_root.set("version", "4")
for mode in new_root.iter("mode"):
for input_item in mode:
-
# Check if macros are used to create what is now a
# "map to keyboard" action
press_and_release = [False, False]
@@ -243,11 +238,13 @@ def _convert_from_v3(self, root, fname=None):
if input_item.tag == "button":
if action.tag == "macro":
if "on-press" in action.keys():
- press_and_release[0] = press_and_release[0] or \
- parse_bool(action.get("on-press"))
+ press_and_release[0] = press_and_release[
+ 0
+ ] or parse_bool(action.get("on-press"))
if "on-release" in action.keys():
- press_and_release[1] = press_and_release[1] or \
- parse_bool(action.get("on-release"))
+ press_and_release[1] = press_and_release[
+ 1
+ ] or parse_bool(action.get("on-release"))
# If this widget is purely a map to keyboard action then
# replace the two macro widgets with a single one
@@ -255,9 +252,7 @@ def _convert_from_v3(self, root, fname=None):
container = ElementTree.Element("container")
container.set("type", "basic")
- container.append(
- self._p3_extract_map_to_keyboard(input_item)
- )
+ container.append(self._p3_extract_map_to_keyboard(input_item))
for action in input_item[:]:
input_item.remove(action)
input_item.append(container)
@@ -277,8 +272,7 @@ def _convert_from_v3(self, root, fname=None):
if input_item.tag == "axis":
copy_condition = False
if action.tag == "remap":
- if "button" in action.keys() or \
- "hat" in action.keys():
+ if "button" in action.keys() or "hat" in action.keys():
copy_condition = True
elif gremlin.base_profile._is_curve_tag(action.tag):
pass
@@ -311,7 +305,7 @@ def _convert_from_v3(self, root, fname=None):
("on-s", "south"),
("on-sw", "south-west"),
("on-w", "west"),
- ("on-nw", "north-west")
+ ("on-nw", "north-west"),
]
for names in keys:
if action.get(names[0]) == "True":
@@ -391,11 +385,12 @@ def _convert_from_v5(self, root, fname=None):
"""
new_root = copy.deepcopy(root)
new_root.set("version", "6")
- search_list = [".[@type='basic']//remap[@axis]",
- ".[@type='basic']//response-curve",
- ".[@type='basic']//response-curve-ex",
- ".[@type='basic']//curve-data"
- ]
+ search_list = [
+ ".[@type='basic']//remap[@axis]",
+ ".[@type='basic']//response-curve",
+ ".[@type='basic']//response-curve-ex",
+ ".[@type='basic']//curve-data",
+ ]
for axis in new_root.iter("axis"):
has_remap = False
has_curve = False
@@ -444,7 +439,9 @@ def _convert_from_v6(self, root, fname):
root.attrib["version"] = "7"
for module in root.findall("import/module"):
- module.attrib["name"] = os.path.normpath(f"{base_path}\\{module.attrib["name"]}.py")
+ module.attrib["name"] = os.path.normpath(
+ f"{base_path}\\{module.attrib["name"]}.py"
+ )
return root
@@ -479,9 +476,9 @@ def _convert_from_v7(self, root, fname=None):
node.set("motion_input", "True")
return root
-
- def _convert_to_ex(self, root, fname = None):
- ''' converts to the EX version '''
+
+ def _convert_to_ex(self, root, fname=None):
+ """converts to the EX version"""
root.attrib["version"] = "100"
# syslog = logging.getLogger("system")
@@ -490,16 +487,15 @@ def _convert_to_ex(self, root, fname = None):
convert_response_curve = config.convert_response_curve
convert_vjoy_remap = config.convert_vjoy_remap
-
# convert all response-curve to response-curve EX
- if convert_response_curve:
+ if convert_response_curve:
nodes = root.xpath("//response-curve")
nodes.extend(root.xpath("//curve-data"))
for node in nodes:
node.tag = "response-curve-ex"
# convert all remap to vjoy remap if configured in options
-
+
if convert_vjoy_remap:
nodes = root.xpath("//remap")
for node in nodes:
@@ -507,8 +503,8 @@ def _convert_to_ex(self, root, fname = None):
return root
- def _convert_from_noop(self, root, fname = None):
- ''' no op conversion '''
+ def _convert_from_noop(self, root, fname=None):
+ """no op conversion"""
pass
def _convert_from_v8(self, root, fname=None):
@@ -568,7 +564,6 @@ def _convert_from_v8(self, root, fname=None):
# syslog = logging.getLogger("system")
class GUIDConverter:
-
"""Simplifies conversion from old device identifiers to the new
GUID ones."""
@@ -642,15 +637,11 @@ def vjoy_lookup(self, vjoy_id):
try:
vjoy_id = int(vjoy_id)
except (ValueError, TypeError):
- syslog.warn(
- f"Cannot convert {vjoy_id} into a valid vjoy id"
- )
+ syslog.warn(f"Cannot convert {vjoy_id} into a valid vjoy id")
return f"{{{uuid.uuid4()}}}"
if vjoy_id not in self.vjoy_to_guid:
- syslog.warn(
- f"GUID for vjoy {vjoy_id} is unknown"
- )
+ syslog.warn(f"GUID for vjoy {vjoy_id} is unknown")
self.vjoy_to_guid[vjoy_id] = f"{{{uuid.uuid4()}}}"
return self.vjoy_to_guid[vjoy_id]
@@ -665,9 +656,8 @@ def vjoy_lookup(self, vjoy_id):
entry.set(
"device-guid",
uuid_converter.lookup(
- entry.attrib.get("id", None),
- entry.attrib.get("name", "")
- )
+ entry.attrib.get("id", None), entry.attrib.get("name", "")
+ ),
)
# Remove the now obsolete id and windows id attributes
@@ -677,17 +667,17 @@ def vjoy_lookup(self, vjoy_id):
for child in entry.findall("mode/axis"):
child.set(
"id",
- str(uuid_converter.axis_lookup(
- entry.attrib["device-guid"],
- int(child.attrib["id"])-1
- ))
+ str(
+ uuid_converter.axis_lookup(
+ entry.attrib["device-guid"], int(child.attrib["id"]) - 1
+ )
+ ),
)
for entry in root.findall("vjoy-devices/vjoy-device"):
entry.set("vjoy-id", entry.attrib["id"])
entry.set(
- "device-guid",
- uuid_converter.vjoy_lookup(int(entry.attrib["id"]))
+ "device-guid", uuid_converter.vjoy_lookup(int(entry.attrib["id"]))
)
del entry.attrib["id"]
del entry.attrib["windows_id"]
@@ -697,7 +687,7 @@ def vjoy_lookup(self, vjoy_id):
("scan_code", "scan-code"),
("range_low", "range-low"),
("range_high", "range-high"),
- ("device_name", "device-name")
+ ("device_name", "device-name"),
]
for rep in replacements:
if rep[0] in entry.keys():
@@ -706,7 +696,7 @@ def vjoy_lookup(self, vjoy_id):
if "device_id" in entry.keys():
entry.set(
"device-guid",
- uuid_converter.lookup(entry.attrib.get("device_id", None))
+ uuid_converter.lookup(entry.attrib.get("device_id", None)),
)
del entry.attrib["device_id"]
del entry.attrib["windows_id"]
@@ -720,7 +710,7 @@ def vjoy_lookup(self, vjoy_id):
for entry in root.findall(".//macro/actions/joystick"):
entry.set(
"device-guid",
- uuid_converter.lookup(entry.attrib.get("device_id", None))
+ uuid_converter.lookup(entry.attrib.get("device_id", None)),
)
entry.set("input-type", entry.attrib["input_type"])
entry.set("input-id", entry.attrib["input_id"])
@@ -748,8 +738,7 @@ def vjoy_lookup(self, vjoy_id):
for entry in root.findall(".//merge-axis/lower"):
entry.set(
- "device-guid",
- uuid_converter.lookup(entry.attrib.get("id", None))
+ "device-guid", uuid_converter.lookup(entry.attrib.get("id", None))
)
entry.set("axis-id", entry.attrib["axis"])
del entry.attrib["id"]
@@ -758,8 +747,7 @@ def vjoy_lookup(self, vjoy_id):
for entry in root.findall(".//merge-axis/upper"):
entry.set(
- "device-guid",
- uuid_converter.lookup(entry.attrib.get("id", None))
+ "device-guid", uuid_converter.lookup(entry.attrib.get("id", None))
)
entry.set("axis-id", entry.attrib["axis"])
del entry.attrib["id"]
@@ -830,7 +818,6 @@ def _p3_extract_map_to_keyboard(self, input_item):
class ProfileModifier:
-
"""Modifies profile contents and provides overview information."""
def __init__(self, profile):
@@ -845,7 +832,7 @@ def device_information_list(self):
:return list of devices used in the profile and information about them
"""
- from gremlin.base_classes import JoystickCondition
+
device_guids = []
device_names = {}
for guid, dev in self.profile.devices.items():
@@ -861,13 +848,15 @@ def device_information_list(self):
device_info = []
for device_guid in set(device_guids):
- device_info.append(gremlin.base_profile.ProfileDeviceInformation(
- device_guid,
- device_names.get(device_guid, "Unknown"),
- self.container_count(device_guid),
- self.condition_count(device_guid),
- self.merge_axis_count(device_guid)
- ))
+ device_info.append(
+ gremlin.base_profile.ProfileDeviceInformation(
+ device_guid,
+ device_names.get(device_guid, "Unknown"),
+ self.container_count(device_guid),
+ self.condition_count(device_guid),
+ self.merge_axis_count(device_guid),
+ )
+ )
return device_info
@@ -893,10 +882,10 @@ def condition_count(self, device_guid):
:return number of conditions associated with the given device
"""
from gremlin.base_conditions import JoystickCondition
+
count = 0
for cond in self.all_conditions():
- if isinstance(cond, JoystickCondition) and \
- cond.device_guid == device_guid:
+ if isinstance(cond, JoystickCondition) and cond.device_guid == device_guid:
count += 1
return count
@@ -924,9 +913,7 @@ def change_device_guid(self, source_guid, target_guid):
"""
if source_guid == target_guid:
- syslog.warning(
- "Swap devices: Source and target device are identical"
- )
+ syslog.warning("Swap devices: Source and target device are identical")
return
self.change_device_actions(source_guid, target_guid)
@@ -944,9 +931,7 @@ def change_device_actions(self, source_guid, target_guid):
# Can't move anything from a non-existent source device
if source_dev is None:
- syslog.warning(
- "Swap devices: Specified a source device that doesn't exist"
- )
+ syslog.warning("Swap devices: Specified a source device that doesn't exist")
return
# Retrieve target device information structure to get its name and
@@ -961,9 +946,7 @@ def change_device_actions(self, source_guid, target_guid):
# copying and deleting things.
if target_dev is None:
if target_hardware_device is None:
- syslog.warning(
- "Swap devices: Empty target device configuration found"
- )
+ syslog.warning("Swap devices: Empty target device configuration found")
return
source_dev.device_guid = target_guid
source_dev.name = target_hardware_device.name
@@ -994,8 +977,9 @@ def change_device_actions(self, source_guid, target_guid):
for container in input_item.containers:
container.parent = target_input_item
- target_mode.config[input_type] \
- [input_id].containers.append(container)
+ target_mode.config[input_type][input_id].containers.append(
+ container
+ )
# Remove all containers from the source device
input_item.containers.clear()
@@ -1003,7 +987,6 @@ def change_device_actions(self, source_guid, target_guid):
# Remove the device entry completely
del self.profile.devices[source_guid]
-
def change_conditions(self, source_guid, target_guid):
"""Modifies conditions to use the target device instead of the
source device.
@@ -1076,8 +1059,9 @@ def _get_device(self, device_guid):
return device
return None
+
def parse_guid(value):
# parses a GUID
from gremlin.util import parse_guid
- return parse_guid(value)
+ return parse_guid(value)
diff --git a/gremlin/repeater.py b/gremlin/repeater.py
index 87c2a6e6..f7e880f6 100644
--- a/gremlin/repeater.py
+++ b/gremlin/repeater.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -25,14 +25,12 @@
from . import common, event_handler, input_devices, joystick_handling
import logging
-from gremlin.input_types import InputType
-from gremlin.actions import Value
import gremlin.joystick_handling
syslog = logging.getLogger("system")
-class Repeater(QtCore.QObject):
+class Repeater(QtCore.QObject):
"""Responsible to repeatedly emit a set of given events.
The class receives a list of events that are to be emitted in
@@ -56,7 +54,9 @@ def __init__(self, events, update_func):
self._stop_timer = threading.Timer(5.0, self.stop)
self._update_func = update_func
self._timeout = time.time()
- self._vjoy_device_guids = [dev.device_guid for dev in joystick_handling.vjoy_devices()]
+ self._vjoy_device_guids = [
+ dev.device_guid for dev in joystick_handling.vjoy_devices()
+ ]
self._event_registry = {}
@property
@@ -97,8 +97,7 @@ def process_event(self, event):
"""
# Ignore VJoy events as well as events occurring when
# events are repeated
- if self.is_running or \
- event.device_guid in self._vjoy_device_guids:
+ if self.is_running or event.device_guid in self._vjoy_device_guids:
return
if not input_devices.JoystickInputSignificant().should_process(event):
@@ -107,30 +106,25 @@ def process_event(self, event):
event_list = []
if event.event_type in [
common.InputType.Keyboard,
- common.InputType.JoystickButton
+ common.InputType.JoystickButton,
]:
event_list = [event.clone(), event.clone()]
event_list[0].is_pressed = False
event_list[1].is_pressed = True
- event_list[0].value = True
+ event_list[0].value = True
event_list[1].value = False
-
+
elif event.event_type == common.InputType.JoystickAxis:
- event_list = [
- event.clone(),
- event.clone(),
- event.clone(),
- event.clone()
- ]
+ event_list = [event.clone(), event.clone(), event.clone(), event.clone()]
event_list[0].value = -0.75
event_list[1].value = 0.0
event_list[2].value = 0.75
event_list[3].value = 0.0
-
+
elif event.event_type == common.InputType.JoystickHat:
event_list = [event.clone(), event.clone()]
- event_list[0].value = (0,0)
-
+ event_list[0].value = (0, 0)
+
# mark events as repeater events so actions handle forced values correctly
for event in event_list:
event.is_repeater = True
@@ -161,19 +155,21 @@ def emit_events(self):
syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_outputs
-
-
# Repeatedly send events until the thread is interrupted
while self.is_running:
event = self._events[index]
if event.event_type == common.InputType.Keyboard:
- if verbose: syslog.info(f"REPEATER: send keyboard event: {str(event)}")
+ if verbose:
+ syslog.info(f"REPEATER: send keyboard event: {str(event)}")
el.keyboard_event.emit(event)
else:
- if verbose: syslog.info(f"REPEATER: send joystick event: {str(event)}")
+ if verbose:
+ syslog.info(f"REPEATER: send joystick event: {str(event)}")
el.joystick_event.emit(event)
- self._update_func(f"{common.InputType.to_string(event.event_type).capitalize()} {str(event.identifier)}")
+ self._update_func(
+ f"{common.InputType.to_string(event.event_type).capitalize()} {str(event.identifier)}"
+ )
index = (index + 1) % len(self._events)
time.sleep(0.25)
@@ -199,7 +195,6 @@ def emit_events(self):
self._event_registry = {}
self._update_func("Waiting for input")
-
# def _vjoy_process_event(self, event):
# ''' handles a vjoy output event '''
# value = event.value
@@ -208,7 +203,7 @@ def emit_events(self):
# syslog = logging.getLogger("system")
# syslog.warning(f"Device ID: {event.device_guid} is not a VJOY device")
# return
-
+
# input_id = event.identifier
# input_type = event.event_type
# if event.is_axis:
diff --git a/gremlin/sendinput.py b/gremlin/sendinput.py
index 18dcb105..375618dd 100644
--- a/gremlin/sendinput.py
+++ b/gremlin/sendinput.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -63,18 +63,18 @@
INPUT_KEYBOARD = 1
import logging
+
syslog = logging.getLogger("system")
-class MotionType(enum.Enum):
+class MotionType(enum.Enum):
"""Mouse motion types available."""
- Fixed = 1,
+ Fixed = (1,)
Accelerated = 2
class MouseMotion:
-
"""Base class of all mouse motion behaviours."""
# Time step between calls
@@ -133,7 +133,6 @@ def _compute_values(self, delta):
class FixedMouseMotion(MouseMotion):
-
"""Motion generation with fixed speed."""
def __init__(self, dx, dy):
@@ -162,7 +161,6 @@ def set_dy(self, value):
class AcceleratedMouseMotion(MouseMotion):
-
"""Motion generation with acceleration over time."""
def __init__(self, direction, min_speed, max_speed, time_to_max_speed):
@@ -185,8 +183,7 @@ def __init__(self, direction, min_speed, max_speed, time_to_max_speed):
self.acceleration = (max_speed - min_speed) / time_to_max_speed
self.current_velocity = self.min_velocity
- self.dx, self.dy = \
- self._decompose_xy(self.direction, self.current_velocity)
+ self.dx, self.dy = self._decompose_xy(self.direction, self.current_velocity)
self._tick_dx_value, self._tick_dx_time = self._compute_values(self.dx)
self._tick_dy_value, self._tick_dy_time = self._compute_values(self.dy)
@@ -196,8 +193,7 @@ def set_direction(self, direction):
:param direction new direction of travel
"""
self.direction = direction - 90.0
- self.dx, self.dy = \
- self._decompose_xy(self.direction, self.current_velocity)
+ self.dx, self.dy = self._decompose_xy(self.direction, self.current_velocity)
self._tick_dx_value, self._tick_dx_time = self._compute_values(self.dx)
self._tick_dy_value, self._tick_dy_time = self._compute_values(self.dy)
@@ -208,8 +204,9 @@ def _decompose_xy(self, direction, value):
positive y
:param value the length of the direction vector
"""
- return value * math.cos(deg2rad(direction)),\
- value * math.sin(deg2rad(direction))
+ return value * math.cos(deg2rad(direction)), value * math.sin(
+ deg2rad(direction)
+ )
def __call__(self):
"""Returns the change in x and y for this point in time.
@@ -222,10 +219,9 @@ def __call__(self):
# Apply acceleration to obtain next integration step values
self.current_velocity = min(
self.max_velocity,
- self.current_velocity + self.acceleration * MouseMotion.delta_t
+ self.current_velocity + self.acceleration * MouseMotion.delta_t,
)
- self.dx, self.dy = \
- self._decompose_xy(self.direction, self.current_velocity)
+ self.dx, self.dy = self._decompose_xy(self.direction, self.current_velocity)
self._tick_dx_value, self._tick_dx_time = self._compute_values(self.dx)
self._tick_dy_value, self._tick_dy_time = self._compute_values(self.dy)
@@ -235,7 +231,6 @@ def __call__(self):
@SingletonDecorator
class MouseController:
-
"""Centralizes sending mouse events in a organized manner."""
def __init__(self):
@@ -262,16 +257,11 @@ def set_absolute_motion(self, dx=None, dy=None):
else:
self._motion_type = MotionType.Fixed
self._delta_generator = FixedMouseMotion(
- dx if dx is not None else 0,
- dy if dy is not None else 0
+ dx if dx is not None else 0, dy if dy is not None else 0
)
def set_accelerated_motion(
- self,
- direction,
- min_speed,
- max_speed,
- time_to_max_speed
+ self, direction, min_speed, max_speed, time_to_max_speed
):
"""Configures a motion using acceleration.
@@ -284,10 +274,7 @@ def set_accelerated_motion(
self._delta_generator.set_direction(direction)
else:
self._delta_generator = AcceleratedMouseMotion(
- direction,
- min_speed,
- max_speed,
- time_to_max_speed
+ direction, min_speed, max_speed, time_to_max_speed
)
self._motion_type = MotionType.Accelerated
@@ -315,7 +302,6 @@ def _control_loop(self):
class _MOUSEINPUT(ctypes.Structure):
-
"""Defines the MOUSEINPUT structure.
https://msdn.microsoft.com/en-us/library/ms646273(v=VS.85).aspx
@@ -332,7 +318,6 @@ class _MOUSEINPUT(ctypes.Structure):
class _KEYBDINPUT(ctypes.Structure):
-
"""Defines the KEYBDINPUT structure.
https://msdn.microsoft.com/en-us/library/ms646271(v=vs.85).aspx
@@ -343,51 +328,43 @@ class _KEYBDINPUT(ctypes.Structure):
("wScan", ctypes.wintypes.WORD),
("dwFlags", ctypes.wintypes.DWORD),
("time", ctypes.wintypes.DWORD),
- ("wExtraInfo", ctypes.POINTER(ctypes.wintypes.ULONG))
+ ("wExtraInfo", ctypes.POINTER(ctypes.wintypes.ULONG)),
)
def _keyboard_input(virtual_key, scan_code, flags):
return _INPUT(
INPUT_KEYBOARD,
- _INPUTunion(ki=_KEYBDINPUT(virtual_key, scan_code, flags, 0, None))
+ _INPUTunion(ki=_KEYBDINPUT(virtual_key, scan_code, flags, 0, None)),
)
-class _INPUTunion(ctypes.Union):
+class _INPUTunion(ctypes.Union):
"""Defines the INPUT union type.
https://msdn.microsoft.com/en-us/library/ms646270(v=vs.85).aspx
"""
- _fields_ = (
- ("mi", _MOUSEINPUT),
- ("ki", _KEYBDINPUT)
- )
+ _fields_ = (("mi", _MOUSEINPUT), ("ki", _KEYBDINPUT))
class _INPUT(ctypes.Structure):
-
"""Defines the INPUT structure.
https://msdn.microsoft.com/en-us/library/ms646270(v=vs.85).aspx
"""
- _fields_ = (
- ("type", ctypes.wintypes.DWORD),
- ("union", _INPUTunion)
- )
+ _fields_ = (("type", ctypes.wintypes.DWORD), ("union", _INPUTunion))
def mouse_relative_motion(dx, dy):
- _send_input(
- _mouse_input(MOUSEEVENTF_MOVE, dx, dy)
- )
+ _send_input(_mouse_input(MOUSEEVENTF_MOVE, dx, dy))
def mouse_press(button):
- ''' sends a single click'''
+ """sends a single click"""
from gremlin.types import MouseButton
+
if button == MouseButton.Left:
_send_input(_mouse_input(MOUSEEVENTF_LEFTDOWN))
elif button == MouseButton.Right:
@@ -399,9 +376,11 @@ def mouse_press(button):
elif button == MouseButton.Forward:
_send_input(_mouse_input(MOUSEEVENTF_XDOWN, data=XBUTTON2))
-def mouse_press_double_click(button, delay = 0.05):
- ''' sends a double click (also releases the mouse) '''
+
+def mouse_press_double_click(button, delay=0.05):
+ """sends a double click (also releases the mouse)"""
from gremlin.types import MouseButton
+
if button == MouseButton.Left:
_send_input(_mouse_input(MOUSEEVENTF_LEFTDOWN))
time.sleep(delay)
@@ -434,7 +413,7 @@ def mouse_press_double_click(button, delay = 0.05):
_send_input(_mouse_input(MOUSEEVENTF_XDOWN, data=XBUTTON1))
time.sleep(delay)
_send_input(_mouse_input(MOUSEEVENTF_XUP, data=XBUTTON1))
-
+
elif button == MouseButton.Forward:
_send_input(_mouse_input(MOUSEEVENTF_XDOWN, data=XBUTTON2))
time.sleep(delay)
@@ -445,9 +424,9 @@ def mouse_press_double_click(button, delay = 0.05):
_send_input(_mouse_input(MOUSEEVENTF_XUP, data=XBUTTON2))
-
def mouse_release(button):
from gremlin.types import MouseButton
+
if button == MouseButton.Left:
_send_input(_mouse_input(MOUSEEVENTF_LEFTUP))
elif button == MouseButton.Right:
@@ -462,31 +441,27 @@ def mouse_release(button):
def mouse_wheel(motion):
# vertical mouse wheel
- _send_input(_mouse_input(MOUSEEVENTF_WHEEL, data=-motion*WHEEL_DELTA))
+ _send_input(_mouse_input(MOUSEEVENTF_WHEEL, data=-motion * WHEEL_DELTA))
+
def mouse_h_wheel(motion):
# horizontal mouse wheel
- import logging
- syslog.info(f"send h wheel direction {motion}")
- _send_input(_mouse_input(MOUSEEVENTF_HWHEEL, data=-motion*WHEEL_DELTA))
+ syslog.info(f"send h wheel direction {motion}")
+ _send_input(_mouse_input(MOUSEEVENTF_HWHEEL, data=-motion * WHEEL_DELTA))
def _mouse_input(flags, dx=0, dy=0, data=0):
return _INPUT(
- INPUT_MOUSE,
- _INPUTunion(mi=_MOUSEINPUT(dx, dy, data, flags, 0, None))
+ INPUT_MOUSE, _INPUTunion(mi=_MOUSEINPUT(dx, dy, data, flags, 0, None))
)
-
-
def send_key(virtual_code, scan_code, flags):
- ''' sends a key message via send input '''
+ """sends a key message via send input"""
_send_input(_keyboard_input(virtual_code, scan_code, flags))
-
def _send_input(*inputs):
# https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput
nInputs = len(inputs)
diff --git a/gremlin/shared_state.py b/gremlin/shared_state.py
index 780fd583..1c08e863 100644
--- a/gremlin/shared_state.py
+++ b/gremlin/shared_state.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -22,28 +22,27 @@
import gremlin.joystick_handling
-from gremlin.input_types import InputType
import gremlin.shared_state
from gremlin.types import DeviceType
import logging
-from PySide6 import QtWidgets, QtCore, QtGui
+from PySide6 import QtCore
import gremlin.util
syslog = logging.getLogger("system")
+
def module_property(func):
"""Decorator to turn module functions into properties.
Function names must be prefixed with an underscore."""
module = sys.modules[func.__module__]
def base_getattr(name):
- raise AttributeError(
- f"module '{module.__name__}' has no attribute '{name}'")
+ raise AttributeError(f"module '{module.__name__}' has no attribute '{name}'")
- old_getattr = getattr(module, '__getattr__', base_getattr)
+ old_getattr = getattr(module, "__getattr__", base_getattr)
def new_getattr(name):
- if f'_{name}' == func.__name__:
+ if f"_{name}" == func.__name__:
return func()
else:
return old_getattr(name)
@@ -58,10 +57,10 @@ def new_getattr(name):
This is ugly but the only sane way to do this at the moment.
"""
-root_path = None # root path
+root_path = None # root path
-vjoy_enabled = True # assume vjoy enabled
-is_dark_theme = False # true if windows is in dark theme
+vjoy_enabled = True # assume vjoy enabled
+is_dark_theme = False # true if windows is in dark theme
# Flag indicating whether or not input highlighting should be
# prevented even if it is enabled by the user
@@ -71,9 +70,9 @@ def new_getattr(name):
# Timer used to disable input highlighting with a delay
_suspend_timer = None
-application_version = "0.0" # application version (set at runtime)
+application_version = "0.0" # application version (set at runtime)
-abort = False # global abort flag - used to mark a profile start should abort - used along with the abort signal
+abort = False # global abort flag - used to mark a profile start should abort - used along with the abort signal
# key of the global mode used internally for some global mappings
# global_mode = "__internal_global__"
@@ -87,7 +86,7 @@ def new_getattr(name):
ui = None
# width of a single chart (this can be expensive to compute so we store it here once)
-char_width = 10
+char_width = 10
# true if a profile is running
@@ -100,26 +99,27 @@ def new_getattr(name):
_virtual_device_guid_to_name_map = {}
# UUID of the plugins tab
-plugins_tab_guid = gremlin.util.parse_guid('dbce0add-460c-480f-9912-31f905a84247')
+plugins_tab_guid = gremlin.util.parse_guid("dbce0add-460c-480f-9912-31f905a84247")
# UUID of the settings tab
-settings_tab_guid = gremlin.util.parse_guid('5b70b5ba-bded-41a8-bd91-d8a209b8e981')
+settings_tab_guid = gremlin.util.parse_guid("5b70b5ba-bded-41a8-bd91-d8a209b8e981")
# UUID of the MIDI tab
-midi_tab_guid = gremlin.util.parse_guid('1b56ecf7-0624-4049-b7b3-8d9b7d8ed7e0')
+midi_tab_guid = gremlin.util.parse_guid("1b56ecf7-0624-4049-b7b3-8d9b7d8ed7e0")
# UUID of the OSC tab
-osc_tab_guid = gremlin.util.parse_guid('ccb486e8-808e-4b3f-abe7-bcb380f39aa4')
+osc_tab_guid = gremlin.util.parse_guid("ccb486e8-808e-4b3f-abe7-bcb380f39aa4")
# UUID of the keyboard tab
-keyboard_tab_guid = gremlin.util.parse_guid('6f1d2b61-d5a0-11cf-bfc7-444553540000')
+keyboard_tab_guid = gremlin.util.parse_guid("6f1d2b61-d5a0-11cf-bfc7-444553540000")
# UUID of the mode tab
-mode_tab_guid = gremlin.util.parse_guid('b3b159a0-4d06-4bd6-93f9-7583ec08b877')
+mode_tab_guid = gremlin.util.parse_guid("b3b159a0-4d06-4bd6-93f9-7583ec08b877")
# holds the current selected device guid (string) for a tab
current_tab_device_guid = None
+
def isDeviceTabActive(device_guid):
- ''' compares the given device and returns True if it's the current selected tab
+ """compares the given device and returns True if it's the current selected tab
:param device_guid: what to look for, GUID or str
-
- '''
+
+ """
global current_tab_device_guid
return gremlin.util.compare_guid(device_guid, current_tab_device_guid)
@@ -130,15 +130,17 @@ def isDeviceTabActive(device_guid):
(settings_tab_guid, DeviceType.NotSet),
(midi_tab_guid, DeviceType.Midi),
(osc_tab_guid, DeviceType.Osc),
- (mode_tab_guid, DeviceType.ModeControl)
+ (mode_tab_guid, DeviceType.ModeControl),
]
virtual_device_guid = None
+
# setup default device names that are not hardware devices
def _init_special_device_guids():
- ''' setup the non HID hardware device name maps '''
+ """setup the non HID hardware device name maps"""
import dinput
+
global _virtual_device_guid_to_name_map, virtual_device_guid
_virtual_device_guid_to_name_map[str(keyboard_tab_guid).casefold()] = "Keyboard"
_virtual_device_guid_to_name_map[str(osc_tab_guid).casefold()] = "OSC"
@@ -150,16 +152,13 @@ def _init_special_device_guids():
virtual_device_guid = str(dinput.GUID_Virtual).casefold()
_virtual_device_guid_to_name_map[virtual_device_guid] = "(VirtualButton)"
_virtual_device_guid_to_name_map[str(dinput.GUID_Invalid).casefold()] = "(Invalid)"
-
-
_init_special_device_guids()
-
def get_virtual_device_name(device_guid):
- ''' gets a device name - expect a string or a GUID'''
+ """gets a device name - expect a string or a GUID"""
if not isinstance(device_guid, str):
device_guid = str(device_guid)
device_guid = device_guid.casefold()
@@ -167,8 +166,9 @@ def get_virtual_device_name(device_guid):
return _virtual_device_guid_to_name_map[device_guid]
return None
+
def get_device_name(device_guid):
- ''' gets the name corresponding to a hardware or virtual device '''
+ """gets the name corresponding to a hardware or virtual device"""
if not isinstance(device_guid, str):
device_guid = str(device_guid)
device_name = gremlin.joystick_handling.device_name_from_guid(device_guid)
@@ -182,21 +182,27 @@ def get_device_name(device_guid):
_simconnect_enabled = None
+
def getSimConnectEnabled():
- ''' gets the simconnect enabled flag '''
+ """gets the simconnect enabled flag"""
global _simconnect_enabled
if _simconnect_enabled is None:
from action_plugins.map_to_simconnect import MapToSimConnect
+
ec = gremlin.execution_graph.ExecutionContext()
enabled = len(ec.findActionPlugin(MapToSimConnect.name)) > 0
syslog = logging.getLogger("system")
- syslog.info(f"State: SimConnect usage {'is' if enabled else 'not'} detected. SimConnect is {'enabled' if enabled else 'disabled'}.")
+ syslog.info(
+ f"State: SimConnect usage {'is' if enabled else 'not'} detected. SimConnect is {'enabled' if enabled else 'disabled'}."
+ )
_simconnect_enabled = enabled
return _simconnect_enabled
+
# map of device type to hardware GUID (DeviceType enum)
device_type_map = {}
+
def reload_device_map():
# setup device types
global device_type_map
@@ -212,7 +218,6 @@ def reload_device_map():
device_profile_map = {}
-
# map of device widgets by hardware GUID (widget)
device_widget_map = {}
@@ -235,29 +240,29 @@ def reload_device_map():
# previous runtime mode
previous_runtime_mode = None
+
@module_property
def _current_mode() -> str:
if is_running:
- #print(f"current mode is: runtime {runtime_mode}")
+ # print(f"current mode is: runtime {runtime_mode}")
return runtime_mode
- #print(f"current mode is: edit {edit_mode}")
+ # print(f"current mode is: edit {edit_mode}")
return edit_mode
+
def resetState():
device_profile_map.clear()
- current_profile = None
- runtime_mode = None
- edit_mode = None
- previous_runtime_mode = None
-
+
def ui_keyinput_suspended():
global _suspend_ui_keyinput
return _suspend_ui_keyinput > 0
+
def push_suspend_ui_keyinput():
- ''' suspends keyboard input to the UI'''
+ """suspends keyboard input to the UI"""
import gremlin.event_handler
+
global _suspend_ui_keyinput
if _suspend_ui_keyinput == 0:
@@ -267,11 +272,10 @@ def push_suspend_ui_keyinput():
_suspend_ui_keyinput += 1
-
-
def pop_suspend_ui_keyinput():
- ''' restores keyboard input to the UI'''
+ """restores keyboard input to the UI"""
import gremlin.event_handler
+
global _suspend_ui_keyinput
if _suspend_ui_keyinput > 0:
_suspend_ui_keyinput -= 1
@@ -279,13 +283,18 @@ def pop_suspend_ui_keyinput():
eh = gremlin.event_handler.EventListener()
eh.suspend_keyboard_input.emit(False)
+
def is_highlighting_suspended():
"""Returns whether or not input highlighting is suspended.
:return True if input highlighting is SUSPENDED
"""
global _suspend_input_highlighting, _suspend_input_highlighting_enabled
- suspended = not ui_ready and _suspend_input_highlighting or _suspend_input_highlighting_enabled > 0
+ suspended = (
+ not ui_ready
+ and _suspend_input_highlighting
+ or _suspend_input_highlighting_enabled > 0
+ )
return suspended
@@ -301,21 +310,20 @@ def _set_input_highlighting_state(value):
_suspend_input_highlighting = value
-
def push_suspend_highlighting():
- ''' push a suspend state '''
+ """push a suspend state"""
global _suspend_input_highlighting_enabled
if _suspend_input_highlighting_enabled == 0:
_set_input_highlighting_state(False)
_suspend_input_highlighting_enabled += 1
-
-def pop_suspend_highlighting(force = False):
- ''' pops a suspend state
-
+
+def pop_suspend_highlighting(force=False):
+ """pops a suspend state
+
:param: force = forces a reset (enables)
-
- '''
+
+ """
global _suspend_input_highlighting_enabled
if _suspend_input_highlighting_enabled > 0:
_suspend_input_highlighting_enabled -= 1
@@ -323,34 +331,33 @@ def pop_suspend_highlighting(force = False):
_suspend_input_highlighting_enabled = 0
if _suspend_input_highlighting_enabled == 0:
_set_input_highlighting_state(False)
-
-
-
def delayed_input_highlighting_suspension():
"""Disables input highlighting with a delay."""
global _suspend_timer
if _suspend_timer is not None:
_suspend_timer.cancel()
- _suspend_timer = threading.Timer(
- 2,
- lambda: pop_suspend_highlighting()
- )
+ _suspend_timer = threading.Timer(2, lambda: pop_suspend_highlighting())
_suspend_timer.start()
+
# true if tabs are loading
-is_tab_loading = False
+is_tab_loading = False
+
def set_last_input_id(device_guid, input_type, input_id):
if not is_tab_loading:
import gremlin.config
+
config = gremlin.config.Configuration()
config.set_last_input(device_guid, input_type, input_id)
+
def get_last_input_id():
import gremlin.config
+
config = gremlin.config.Configuration()
device_guid = config.get_last_device_guid()
if device_guid:
@@ -359,10 +366,13 @@ def get_last_input_id():
def last_input_id(device_guid):
- ''' retrieves the last input id for a given input guid (input_type, input_id) of the last selection for this device '''
+ """retrieves the last input id for a given input guid (input_type, input_id) of the last selection for this device"""
import gremlin.config
+
if device_guid:
- device_guid, input_type, input_id = gremlin.config.Configuration().get_last_input(device_guid)
+ device_guid, input_type, input_id = (
+ gremlin.config.Configuration().get_last_input(device_guid)
+ )
return (input_type, input_id)
return (None, None)
@@ -371,11 +381,13 @@ def last_input_id(device_guid):
_pickle_data = {}
+
def save_state(data):
id = str(uuid.uuid4())
_pickle_data[id] = data
return id
+
def load_state(id):
if id in _pickle_data.keys():
data = _pickle_data[id]
@@ -386,15 +398,25 @@ def load_state(id):
# simconnect community folders
community_folder = None
+
+
def _get_simconnect_community_folder():
# Steam version
- #self._community_folder = r"C:\Microsoft Flight Simulator\Community"
+ # self._community_folder = r"C:\Microsoft Flight Simulator\Community"
# Microsoft store version MSFS 2024: %appdata%\Local\Packages\Microsoft.Limitless_8wekyb3d8bbwe\LocalCache\Packages\Community
import os
+
app_data = os.getenv("LOCALAPPDATA")
global community_folder
# C:\Users\XXXXXX\AppData\Local\Packages\Microsoft.Limitless_8wekyb3d8bbwe\LocalCache\Packages\Community
- community_folder = os.path.join(app_data, "Packages","Microsoft.Limitless_8wekyb3d8bbwe","LocalCache","Packages","Community")
+ community_folder = os.path.join(
+ app_data,
+ "Packages",
+ "Microsoft.Limitless_8wekyb3d8bbwe",
+ "LocalCache",
+ "Packages",
+ "Community",
+ )
_get_simconnect_community_folder()
@@ -402,20 +424,21 @@ def _get_simconnect_community_folder():
_icon_path_cache = {}
+
def _get_root_path():
- ''' gets the root path of the application '''
+ """gets the root path of the application"""
import sys
import pathlib
import os
- if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
+ if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
# as exe via pyinstallaler
application_path = sys._MEIPASS
# other installer
- #application_path = os.path.dirname(sys.executable)
+ # application_path = os.path.dirname(sys.executable)
else:
- #app = QtWidgets.QApplication.instance()
+ # app = QtWidgets.QApplication.instance()
# application_path = app.applicationDirPath()
# as script (because common is a subfolder, return the parent folder)
application_path = pathlib.Path(os.path.dirname(__file__)).parent
@@ -423,84 +446,93 @@ def _get_root_path():
root_path = application_path
return application_path
-class ProfileStateMonitor():
- ''' monitors various state related settings '''
+
+class ProfileStateMonitor:
+ """monitors various state related settings"""
+
def __init__(self):
import gremlin.event_handler
+
el = gremlin.event_handler.EventListener()
el.profile_start.connect(self._profile_start)
el.profile_stop.connect(self._profile_stop)
@QtCore.Slot()
def _profile_start(self):
- gremlin.shared_state._simconnect_enabled = None # force an update
-
+ gremlin.shared_state._simconnect_enabled = None # force an update
@QtCore.Slot()
def _profile_stop(self):
- gremlin.shared_state._simconnect_enabled = None # force an udpate
+ gremlin.shared_state._simconnect_enabled = None # force an udpate
_log_nesting_level = 0
-# log nesting level management
+# log nesting level management
def pushLog():
global _log_nesting_level
- _log_nesting_level +=1
+ _log_nesting_level += 1
-def popLog(reset = False):
+
+def popLog(reset=False):
global _log_nesting_level
if _log_nesting_level > 0:
- _log_nesting_level -=1
+ _log_nesting_level -= 1
if reset:
_log_nesting_level = 0
-def logTabs(showLevel = False):
- ''' gets the log prefix tab based on nesting levels '''
+
+def logTabs(showLevel=False):
+ """gets the log prefix tab based on nesting levels"""
global _log_nesting_level
- tabs ="\t" * _log_nesting_level
+ tabs = "\t" * _log_nesting_level
if showLevel:
return f"Nesting [{_log_nesting_level}]{tabs}"
return tabs
+
_joystick_suspend_count = 0
_input_selection_suspend_count = 0
+
def push_joystick():
- ''' suspends joystick input '''
+ """suspends joystick input"""
global _joystick_suspend_count
_joystick_suspend_count += 1
+
def pop_joystick():
- ''' restores joystick input '''
+ """restores joystick input"""
global _joystick_suspend_count
if _joystick_suspend_count > 0:
- _joystick_suspend_count -= 1
+ _joystick_suspend_count -= 1
+
def push_input_selection():
global _input_selection_suspend_count
_input_selection_suspend_count += 1
-def pop_input_selection(reset = False):
+
+def pop_input_selection(reset=False):
global _input_selection_suspend_count
if reset:
_input_selection_suspend_count = 0
return
if _input_selection_suspend_count > 0:
- _input_selection_suspend_count -= 1
+ _input_selection_suspend_count -= 1
+
@module_property
-def _is_joystick_suspended()->bool:
+def _is_joystick_suspended() -> bool:
global _joystick_suspend_count
return _joystick_suspend_count > 0
-
+
+
@module_property
-def _is_input_selection_suspended()->bool:
+def _is_input_selection_suspended() -> bool:
global _input_selection_suspend_count
return _input_selection_suspend_count > 0
_get_root_path()
-
-
diff --git a/gremlin/signal.py b/gremlin/signal.py
index 6c2d6114..4769db12 100644
--- a/gremlin/signal.py
+++ b/gremlin/signal.py
@@ -19,12 +19,11 @@
from PySide6 import QtCore
from PySide6.QtCore import Signal
-from gremlin import common
from gremlin.singleton_decorator import SingletonDecorator
+
@SingletonDecorator
class Signal(QtCore.QObject):
-
reloadUi = Signal()
diff --git a/gremlin/singleton_decorator.py b/gremlin/singleton_decorator.py
index 8529c6da..4977024d 100644
--- a/gremlin/singleton_decorator.py
+++ b/gremlin/singleton_decorator.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,9 +16,7 @@
# along with this program. If not, see .
-
class SingletonDecorator:
-
"""Decorator turning a class into a singleton."""
def __init__(self, klass):
diff --git a/gremlin/spline.py b/gremlin/spline.py
index c97a7a74..3b2dfb3f 100644
--- a/gremlin/spline.py
+++ b/gremlin/spline.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -25,9 +25,8 @@
class CubicSpline:
-
"""Creates a new cubic spline based interpolation.
-
+
The methods requires a set of control points which are used to
create a C2 spline which passes through all of them.
"""
@@ -40,9 +39,6 @@ def __init__(self, points):
# Order the points by increasing x coordinate to guarantee proper
# functioning of the spline code
-
-
-
ordered_points = sorted(list(set(points)), key=lambda x: x[0])
self.x = [v[0] for v in ordered_points]
@@ -53,7 +49,7 @@ def __init__(self, points):
def _fit(self):
"""Computes the second derivatives for the control points."""
- n = len(self.x)-1
+ n = len(self.x) - 1
if n < 2:
return
@@ -64,21 +60,21 @@ def _fit(self):
v = [0] * n
for i in range(n):
- h[i] = self.x[i+1] - self.x[i]
+ h[i] = self.x[i + 1] - self.x[i]
if h[i] == 0:
- b[i] = (self.y[i+1] - self.y[i])
+ b[i] = self.y[i + 1] - self.y[i]
else:
- b[i] = (self.y[i+1] - self.y[i]) / h[i]
+ b[i] = (self.y[i + 1] - self.y[i]) / h[i]
u[1] = 2 * (h[0] + h[1])
v[1] = 6 * (b[1] - b[0])
for i in range(2, n):
- u[i] = 2 * (h[i] + h[i-1]) - h[i-1]**2 / u[i-1]
- v[i] = 6 * (b[i] - b[i-1]) - (h[i-1] * v[i-1]) / u[i-1]
+ u[i] = 2 * (h[i] + h[i - 1]) - h[i - 1] ** 2 / u[i - 1]
+ v[i] = 6 * (b[i] - b[i - 1]) - (h[i - 1] * v[i - 1]) / u[i - 1]
self.z[n] = 0.0
- for i in range(n-1, 0, -1):
- self.z[i] = (v[i] - h[i] * self.z[i+1]) / u[i]
+ for i in range(n - 1, 0, -1):
+ self.z[i] = (v[i] - h[i] * self.z[i + 1]) / u[i]
self.z[0] = 0.0
def __call__(self, x):
@@ -90,21 +86,24 @@ def __call__(self, x):
n = len(self.x)
i = 0
- for i in range(n-1):
- if self.x[i] <= x <= self.x[i+1]:
+ for i in range(n - 1):
+ if self.x[i] <= x <= self.x[i + 1]:
break
- h = self.x[i+1] - self.x[i]
- tmp = (self.z[i] / 2.0) + (x - self.x[i]) * \
- (self.z[i+1] - self.z[i]) / (6 * h)
- tmp = -(h/6.0) * (self.z[i+1] + 2 * self.z[i]) + \
- (self.y[i+1] - self.y[i]) / h + (x - self.x[i]) * tmp
+ h = self.x[i + 1] - self.x[i]
+ tmp = (self.z[i] / 2.0) + (x - self.x[i]) * (self.z[i + 1] - self.z[i]) / (
+ 6 * h
+ )
+ tmp = (
+ -(h / 6.0) * (self.z[i + 1] + 2 * self.z[i])
+ + (self.y[i + 1] - self.y[i]) / h
+ + (x - self.x[i]) * tmp
+ )
return self.y[i] + (x - self.x[i]) * tmp
class CubicBezierSpline:
-
"""Implementation of cubic Bezier splines."""
def __init__(self, points):
@@ -132,7 +131,7 @@ def _generate_lookup(self):
TPoint2D(self.x[offset], self.y[offset]),
TPoint2D(self.x[offset + 1], self.y[offset + 1]),
TPoint2D(self.x[offset + 2], self.y[offset + 2]),
- TPoint2D(self.x[offset + 3], self.y[offset + 3])
+ TPoint2D(self.x[offset + 3], self.y[offset + 3]),
]
# Get t -> coordinate mappings
@@ -157,13 +156,13 @@ def _value_at_t(self, points, t):
return TPoint2D(
points[0].x * mt3
- + 3 * points[1].x * mt2 * t
- + 3 * points[2].x * mt * t2
- + points[3].x * t3,
+ + 3 * points[1].x * mt2 * t
+ + 3 * points[2].x * mt * t2
+ + points[3].x * t3,
points[0][1] * mt3
- + 3 * points[1].y * mt2 * t
- + 3 * points[2].y * mt * t2
- + points[3].y * t3
+ + 3 * points[1].y * mt2 * t
+ + 3 * points[2].y * mt * t2
+ + points[3].y * t3,
)
def __call__(self, x):
@@ -180,12 +179,12 @@ def __call__(self, x):
if self.knots[0][0] > x:
index = 0
elif self.knots[-1][0] <= x:
- index = len(self._lookup)-1
+ index = len(self._lookup) - 1
else:
segment_count = int((len(self.x) - 4) / 3) + 1
for i in range(segment_count):
offset = i * 3
- if self.x[offset] <= x <= self.x[offset+3]:
+ if self.x[offset] <= x <= self.x[offset + 3]:
index = i
break
diff --git a/gremlin/threading.py b/gremlin/threading.py
index 1acb35cf..1469eb3c 100644
--- a/gremlin/threading.py
+++ b/gremlin/threading.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -20,31 +20,27 @@
class AbortableThread(threading.Thread):
- ''' killable thread '''
+ """killable thread"""
def __init__(self, *args, **kwargs):
-
super().__init__(*args, **kwargs)
-
+
import gremlin.event_handler
eh = gremlin.event_handler.EventListener()
eh.shutdown.connect(self.stop)
-
- #self._stop_event = threading.Event()
- self._shutdown_requested = False
+ # self._stop_event = threading.Event()
+ self._shutdown_requested = False
def reset(self):
- ''' reset the thread'''
+ """reset the thread"""
self._shutdown_requested = False
def stop(self):
- #self._stop_event.set()
+ # self._stop_event.set()
self._shutdown_requested = True
def stopped(self):
return self._shutdown_requested
- #return self._stop_event.is_set()
-
-
+ # return self._stop_event.is_set()
diff --git a/gremlin/tree.py b/gremlin/tree.py
index 766da589..f798267e 100644
--- a/gremlin/tree.py
+++ b/gremlin/tree.py
@@ -23,17 +23,12 @@
class TreeNode:
-
"""Represents a single node in a tree.
Supports basic functionaolity for tree construction and modification.
"""
- def __init__(
- self,
- value: Optional[Any] = None,
- parent: Optional[TreeNode] = None
- ):
+ def __init__(self, value: Optional[Any] = None, parent: Optional[TreeNode] = None):
"""Creates a new tree instance.
Args:
@@ -72,9 +67,7 @@ def append_sibling(self, other: TreeNode) -> None:
other: the node to add as sibling
"""
if self.parent is None:
- raise error.GremlinError(
- "Cannot add sibling node to root node."
- )
+ raise error.GremlinError("Cannot add sibling node to root node.")
other.parent = self.parent
self.parent.children.append(other)
@@ -86,13 +79,11 @@ def insert_sibling_after(self, other: TreeNode) -> None:
other: the node to add as sibling
"""
if self.parent is None:
- raise error.GremlinError(
- "Cannot add sibling node to root node."
- )
+ raise error.GremlinError("Cannot add sibling node to root node.")
other.parent = self.parent
index = self.parent.children.index(self)
- self.parent.children.insert(index+1, other)
+ self.parent.children.insert(index + 1, other)
def insert_sibling_before(self, other: TreeNode) -> None:
"""Inserts a new sibling before this node.
@@ -101,9 +92,7 @@ def insert_sibling_before(self, other: TreeNode) -> None:
other: the node to add as sibling
"""
if self.parent is None:
- raise error.GremlinError(
- "Cannot add sibling node to root node."
- )
+ raise error.GremlinError("Cannot add sibling node to root node.")
other.parent = self.parent
index = self.parent.children.index(self)
@@ -118,9 +107,7 @@ def set_parent(self, other: TreeNode) -> None:
# Check for direct cycles. If any are present resolve them and log
# a warning message as this could be a sign of unintended behavior
if other.is_descendant(self) or self.is_descendant(other):
- raise error.GremlinError(
- "Setting parent would cause a cycle, aborting"
- )
+ raise error.GremlinError("Setting parent would cause a cycle, aborting")
if self.parent is not None:
self.parent.remove_child(self)
diff --git a/gremlin/tts.py b/gremlin/tts.py
index ddad3703..57235a85 100644
--- a/gremlin/tts.py
+++ b/gremlin/tts.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -27,19 +27,19 @@
import gremlin.shared_state
import threading
import gremlin.threading
-from . import event_handler, util
+from . import util
import pyttsx3
import gremlin.singleton_decorator
from PySide6 import QtCore
syslog = logging.getLogger("system")
+
@gremlin.singleton_decorator.SingletonDecorator
class TextToSpeech:
-
- rate_playback = 100 # default playback rate
- rate_offset_min = 50 # max slow
- rate_offset_max = 300 # max fast
+ rate_playback = 100 # default playback rate
+ rate_offset_min = 50 # max slow
+ rate_offset_max = 300 # max fast
def __init__(self):
"""Creates a new instance."""
@@ -49,12 +49,14 @@ def __init__(self):
el.shutdown.connect(self.end)
self._lock = threading.Lock()
- self._current_rate = 100 # default rate (global)
+ self._current_rate = 100 # default rate (global)
try:
self.engine = pyttsx3.init()
- self.voices = self.engine.getProperty('voices')
- self.default_voice = next((voice for voice in self.voices if "David Desktop" in voice.name), None)
+ self.voices = self.engine.getProperty("voices")
+ self.default_voice = next(
+ (voice for voice in self.voices if "David Desktop" in voice.name), None
+ )
self._started = False
self.valid = True
self._tts_thread = None
@@ -63,32 +65,27 @@ def __init__(self):
verbose = gremlin.config.Configuration().verbose
if verbose:
- syslog.info(f"TTS voice listing:")
+ syslog.info("TTS voice listing:")
for voice in self.voices:
syslog.info(f"\t{voice.name} (id: {voice.id})")
if self.default_voice:
- syslog.info(f"TTS default voice: {self.default_voice.name} (id: {self.default_voice.id})")
-
+ syslog.info(
+ f"TTS default voice: {self.default_voice.name} (id: {self.default_voice.id})"
+ )
self.start()
-
except Exception as err:
syslog.error(f"TTS: unable to initialize TTS: {err}")
-
-
-
-
-
def getVoices(self):
- ''' gets a list of defined voices'''
+ """gets a list of defined voices"""
if self.valid:
- return self.voices
+ return self.voices
return []
-
+
def set_voice(self, voice):
- ''' sets the voice'''
+ """sets the voice"""
if not self.valid:
return
try:
@@ -103,47 +100,48 @@ def set_voice(self, voice):
except Exception as err:
syslog.error(f"TTS: unable to activate TTS: {err}")
-
- def speak(self, text, rate = 100, threaded = True):
+ def speak(self, text, rate=100, threaded=True):
if not self.valid:
return
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose
- if verbose: syslog.info(f"TTS: SPEAK add to queue: {text}")
+ if verbose:
+ syslog.info(f"TTS: SPEAK add to queue: {text}")
self._lock.acquire_lock()
self._queue.clear()
- self._queue.append(lambda : self._speak(text, rate))
+ self._queue.append(lambda: self._speak(text, rate))
self._lock.release_lock()
- def _speak(self, text, rate = None):
- ''' speaks the text'''
-
+ def _speak(self, text, rate=None):
+ """speaks the text"""
+
try:
text = self.text_substitution(text)
if rate is None:
rate = self._current_rate
- new_rate = self.rate_playback + int(util.clamp(rate, self.rate_offset_min, self.rate_offset_max))
- self.engine.setProperty('rate', new_rate)
+ new_rate = self.rate_playback + int(
+ util.clamp(rate, self.rate_offset_min, self.rate_offset_max)
+ )
+ self.engine.setProperty("rate", new_rate)
self.engine.say(text)
-
except Exception as err:
- logging.getLogger(f"system").error(f"Error in TTS: {err}")
+ logging.getLogger("system").error(f"Error in TTS: {err}")
- def speak_single(self, text, rate = None, threaded = True):
+ def speak_single(self, text, rate=None, threaded=True):
if text:
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose
- if verbose: syslog.info(f"TTS: SPEAK SINGLE add to queue: {text}")
+ if verbose:
+ syslog.info(f"TTS: SPEAK SINGLE add to queue: {text}")
self._lock.acquire_lock()
self._queue.clear()
- self._queue.append(lambda : self._speak_single(text, rate))
+ self._queue.append(lambda: self._speak_single(text, rate))
self._lock.release_lock()
-
- def _speak_single(self, text, rate = None):
- ''' speaks the test as a single event (don't use this inside an event loop)'''
+ def _speak_single(self, text, rate=None):
+ """speaks the test as a single event (don't use this inside an event loop)"""
if not self.valid:
return
try:
@@ -152,8 +150,10 @@ def _speak_single(self, text, rate = None):
if rate is None:
rate = self._current_rate
- new_rate = self.rate_playback + int(util.clamp(rate, self.rate_offset_min, self.rate_offset_max))
- self.engine.setProperty('rate', new_rate)
+ new_rate = self.rate_playback + int(
+ util.clamp(rate, self.rate_offset_min, self.rate_offset_max)
+ )
+ self.engine.setProperty("rate", new_rate)
self.engine.say(text)
try:
@@ -162,13 +162,11 @@ def _speak_single(self, text, rate = None):
except:
pass
-
except Exception as err:
syslog.error(f"Error in TTS: {err}")
-
def stop(self):
- ''' stops any speech '''
+ """stops any speech"""
if not self.valid:
return
try:
@@ -178,21 +176,25 @@ def stop(self):
self._lock.release_lock()
except Exception as err:
- logging.getLogger(f"system").error(f"Error in TTS: {err}")
+ logging.getLogger("system").error(f"Error in TTS: {err}")
def start(self):
- ''' starts the loop '''
+ """starts the loop"""
if not self.valid:
return
if not self._started:
- self._tts_thread = gremlin.threading.AbortableThread(target = self._tts_runner)
+ self._tts_thread = gremlin.threading.AbortableThread(
+ target=self._tts_runner
+ )
self._tts_thread.start()
- self._queue_thread = gremlin.threading.AbortableThread(target= self._queue_runner)
+ self._queue_thread = gremlin.threading.AbortableThread(
+ target=self._queue_runner
+ )
self._queue_thread.start()
self._started = True
def _tts_runner(self):
- ''' runner thread for the TTS engine '''
+ """runner thread for the TTS engine"""
if not self.valid:
return
threading.current_thread().reset()
@@ -200,12 +202,11 @@ def _tts_runner(self):
while not self._tts_thread.stopped():
time.sleep(0.1)
self.engine.iterate()
-
- self.engine.endLoop()
+ self.engine.endLoop()
def _queue_runner(self):
- ''' processes the speech queue '''
+ """processes the speech queue"""
# syslog = logging.getLogger("system")
threading.current_thread().reset()
verbose = gremlin.config.Configuration().verbose
@@ -214,21 +215,21 @@ def _queue_runner(self):
self._lock.acquire_lock()
functor = self._queue.pop(0)
self._lock.release_lock()
- if verbose: syslog.info("TTS: POP queue")
+ if verbose:
+ syslog.info("TTS: POP queue")
functor()
time.sleep(0.1)
# terminate any remaining queue items
self._queue.clear()
-
@QtCore.Slot()
def end(self):
- ''' ends the loop '''
-
+ """ends the loop"""
+
if not self.valid:
return
-
+
if self._started:
# syslog = logging.getLogger("system")
syslog.info("TTS: shutdown")
@@ -245,7 +246,6 @@ def end(self):
except:
pass
self._started = False
-
def set_volume(self, value):
"""Sets the volume anywhere between 0 and 100.
@@ -255,7 +255,9 @@ def set_volume(self, value):
if not self.valid:
return
volume = int(util.clamp(value, 0, 100))
- self.engine.setProperty('volume', volume / 100) # value is 0 to 1 floating point
+ self.engine.setProperty(
+ "volume", volume / 100
+ ) # value is 0 to 1 floating point
def set_rate(self, value):
"""Sets the speaking speed between -10 and 10.
@@ -268,11 +270,11 @@ def set_rate(self, value):
# default is 200 words per minute
if not self.valid:
return
- rate = self.rate_playback + int(util.clamp(value, self.rate_offset_min, self.rate_offset_max))
+ rate = self.rate_playback + int(
+ util.clamp(value, self.rate_offset_min, self.rate_offset_max)
+ )
self._current_rate = rate
- self.engine.setProperty('rate', rate )
-
-
+ self.engine.setProperty("rate", rate)
def text_substitution(self, text):
"""Returns the provided text after running text substitution on it.
diff --git a/gremlin/types.py b/gremlin/types.py
index 5e85a8ce..d85fb4e1 100644
--- a/gremlin/types.py
+++ b/gremlin/types.py
@@ -25,27 +25,28 @@
import gremlin.error
import logging
+
syslog = logging.getLogger("system")
-class VisualizationType(IntEnum):
+class VisualizationType(IntEnum):
"""Enumeration of possible visualization types."""
AxisTemporal = 1
AxisCurrent = 2
ButtonHat = 3
- Keyboard = 4
+ Keyboard = 4
+
class KeyboardOutputMode(Enum):
- Both = 0 # keyboard make and break (press/release) (pulse mode)
- Press = 1 # keyboard make only
- Release = 2 # keyboard release only
- Hold = 3 # press while held (default GremlinEx behavior)
- AutoRepeat = 4 # repeated pulse mode - key pulses while the input is held
+ Both = 0 # keyboard make and break (press/release) (pulse mode)
+ Press = 1 # keyboard make only
+ Release = 2 # keyboard release only
+ Hold = 3 # press while held (default GremlinEx behavior)
+ AutoRepeat = 4 # repeated pulse mode - key pulses while the input is held
-
-class ActivationRule(Enum):
+class ActivationRule(Enum):
"""Activation rules for collections of conditions.
All requires all the conditions in a collection to evaluate to True while
@@ -56,9 +57,7 @@ class ActivationRule(Enum):
Any = 2
-
class AxisNames(Enum):
-
"""Names associated with axis indices."""
X = 1
@@ -75,7 +74,9 @@ def to_string(value: AxisNames) -> str:
try:
return _AxisNames_to_string_lookup[value]
except KeyError:
- syslog.error(f"AxisNames: Don't know how to convert axis to string: '{value}' to a string - defaulting to X (1)")
+ syslog.error(
+ f"AxisNames: Don't know how to convert axis to string: '{value}' to a string - defaulting to X (1)"
+ )
return "X"
@staticmethod
@@ -83,9 +84,11 @@ def to_enum(value: str) -> AxisNames:
try:
return _AxisNames_to_enum_lookup[value]
except KeyError:
- syslog.error(f"AxisNames: Don't know how to convert axis to enum: '{value}' to a string - defaulting to X (1)")
+ syslog.error(
+ f"AxisNames: Don't know how to convert axis to enum: '{value}' to a string - defaulting to X (1)"
+ )
return AxisNames.X
-
+
@staticmethod
def to_list():
return [axis for axis in AxisNames]
@@ -99,7 +102,7 @@ def to_list():
AxisNames.RY: "Y Rotation (5)",
AxisNames.RZ: "Z Rotation (6)",
AxisNames.SLIDER: "Slider (7)",
- AxisNames.DIAL: "Dial (8)"
+ AxisNames.DIAL: "Dial (8)",
}
_AxisNames_to_enum_lookup = {
"X Axis (1)": AxisNames.X,
@@ -109,18 +112,17 @@ def to_list():
"Y Rotation (5)": AxisNames.RY,
"Z Rotation (6)": AxisNames.RZ,
"Slider (7)": AxisNames.SLIDER,
- "Dial (8)": AxisNames.DIAL
+ "Dial (8)": AxisNames.DIAL,
}
class AxisButtonDirection(Enum):
-
"""Possible activation directions for axis button instances."""
Anywhere = 1
Below = 2
- Above = 3,
-
+ Above = (3,)
+
@staticmethod
def to_string(value: AxisButtonDirection) -> str:
try:
@@ -144,8 +146,6 @@ def to_enum(value: str) -> AxisButtonDirection:
AxisButtonDirection.Anywhere: "anywhere",
AxisButtonDirection.Above: "above",
AxisButtonDirection.Below: "below",
-
-
}
_AxisButtonDirection_to_enum_lookup = {
"anywhere": AxisButtonDirection.Anywhere,
@@ -155,7 +155,6 @@ def to_enum(value: str) -> AxisButtonDirection:
class MouseButton(Enum):
-
"""Enumeration of all possible mouse buttons."""
Left = 1
@@ -200,6 +199,7 @@ def to_enum(value: str) -> MouseButton:
"Wheel Down": MouseButton.WheelDown,
}
+
class xIntEnum(IntEnum):
def __eq__(self, other):
if type(self).__name__ == type(other).__name__:
@@ -207,21 +207,21 @@ def __eq__(self, other):
if other is int:
return self.value == other
return False
-
+
def __hash__(self) -> int:
return hash(self.value)
-
-class DeviceType(IntEnum):
+class DeviceType(IntEnum):
"""Enumeration of the different possible input types."""
- NotSet = 0 # not set
- Keyboard = 1 # keyboard
- Joystick = 2 # game controller
- VJoy = 3 # vjoy (virtual)
- Midi = 4 # midi
- Osc = 5 # open source control
- ModeControl = 6 # mode control
+
+ NotSet = 0 # not set
+ Keyboard = 1 # keyboard
+ Joystick = 2 # game controller
+ VJoy = 3 # vjoy (virtual)
+ Midi = 4 # midi
+ Osc = 5 # open source control
+ ModeControl = 6 # mode control
@staticmethod
def to_string(value):
@@ -236,12 +236,11 @@ def to_enum(value):
return _DeviceType_to_enum_lookup[value]
except KeyError:
raise gremlin.error.GremlinError("Invalid type in lookup")
-
+
@staticmethod
def to_display_name(value):
return _DeviceType_to_display_name[value]
-
-
+
_DeviceType_to_display_name = {
DeviceType.NotSet: "(invalid)",
@@ -250,7 +249,7 @@ def to_display_name(value):
DeviceType.VJoy: "VJoy",
DeviceType.Midi: "MIDI",
DeviceType.Osc: "OSC",
- DeviceType.ModeControl: "Mode Control"
+ DeviceType.ModeControl: "Mode Control",
}
_DeviceType_to_string_lookup = {
@@ -272,12 +271,10 @@ def to_display_name(value):
"midi": DeviceType.Midi,
"osc": DeviceType.Osc,
"mode": DeviceType.ModeControl,
-
}
class PluginVariableType(xIntEnum):
-
"""Enumeration of all supported variable types."""
Int = 1
@@ -293,23 +290,17 @@ class PluginVariableType(xIntEnum):
def to_string(value: PluginVariableType) -> str:
try:
v = value.value
- data = next((item for item in PluginVariableType if item.value == v),None)
+ data = next((item for item in PluginVariableType if item.value == v), None)
return _PluginVariableType_to_string_lookup[data]
except KeyError:
- raise gremlin.error.GremlinError(
- "Invalid PluginVariableType in lookup"
- )
+ raise gremlin.error.GremlinError("Invalid PluginVariableType in lookup")
@staticmethod
def to_enum(value: str) -> PluginVariableType:
try:
return _PluginVariableType_to_enum_lookup[value]
except KeyError:
- raise gremlin.error.GremlinError(
- "Invalid PluginVariableType in lookup"
- )
-
-
+ raise gremlin.error.GremlinError("Invalid PluginVariableType in lookup")
_PluginVariableType_to_string_lookup = {
@@ -320,7 +311,7 @@ def to_enum(value: str) -> PluginVariableType:
PluginVariableType.PhysicalInput: "PhysicalInput",
PluginVariableType.VirtualInput: "VirtualInput",
PluginVariableType.Mode: "Mode",
- PluginVariableType.Selection: "Selection"
+ PluginVariableType.Selection: "Selection",
}
_PluginVariableType_to_enum_lookup = {
"Int": PluginVariableType.Int,
@@ -330,12 +321,11 @@ def to_enum(value: str) -> PluginVariableType:
"PhysicalInput": PluginVariableType.PhysicalInput,
"VirtualInput": PluginVariableType.VirtualInput,
"Mode": PluginVariableType.Mode,
- "Selection": PluginVariableType.Selection
+ "Selection": PluginVariableType.Selection,
}
class MergeAxisOperation(Enum):
-
"""Possible merge axis operation modes."""
Average = 1
@@ -348,36 +338,31 @@ def to_string(value: MergeAxisOperation) -> str:
try:
return _MergeAxisOperation_to_string_lookup[value]
except KeyError:
- raise gremlin.error.GremlinError(
- "Invalid MergeAxisOperation in lookup"
- )
+ raise gremlin.error.GremlinError("Invalid MergeAxisOperation in lookup")
@staticmethod
def to_enum(value: str) -> MergeAxisOperation:
try:
return _MergeAxisOperation_to_enum_lookup[value.lower()]
except KeyError:
- raise gremlin.error.GremlinError(
- "Invalid MergeAxisOperation in lookup"
- )
+ raise gremlin.error.GremlinError("Invalid MergeAxisOperation in lookup")
_MergeAxisOperation_to_string_lookup = {
MergeAxisOperation.Average: "average",
MergeAxisOperation.Minimum: "minimum",
MergeAxisOperation.Maximum: "maximum",
- MergeAxisOperation.Sum: "sum"
+ MergeAxisOperation.Sum: "sum",
}
_MergeAxisOperation_to_enum_lookup = {
"average": MergeAxisOperation.Average,
"minimum": MergeAxisOperation.Minimum,
"maximum": MergeAxisOperation.Maximum,
- "sum": MergeAxisOperation.Sum
+ "sum": MergeAxisOperation.Sum,
}
class PropertyType(Enum):
-
"""Enumeration of all known property types."""
String = 1
@@ -402,18 +387,15 @@ def to_string(value: PropertyType) -> str:
try:
return _PropertyType_to_string_lookup[value]
except KeyError:
- raise gremlin.error.GremlinError(
- "Invalid PropertyType in lookup"
- )
+ raise gremlin.error.GremlinError("Invalid PropertyType in lookup")
@staticmethod
def to_enum(value: str) -> PropertyType:
try:
return _PropertyType_to_enum_lookup[value.lower()]
except KeyError:
- raise gremlin.error.GremlinError(
- "Invalid PropertyType in lookup"
- )
+ raise gremlin.error.GremlinError("Invalid PropertyType in lookup")
+
_PropertyType_to_string_lookup = {
PropertyType.String: "string",
@@ -449,12 +431,11 @@ def to_enum(value: str) -> PropertyType:
"uuid": PropertyType.UUID,
"axis_mode": PropertyType.AxisMode,
"hat_direction": PropertyType.HatDirection,
- "list": PropertyType.List
+ "list": PropertyType.List,
}
class AxisMode(Enum):
-
Absolute = 1
Relative = 2
@@ -463,31 +444,27 @@ def to_string(value: AxisMode) -> str:
try:
return _AxisMode_to_string_lookup[value]
except KeyError:
- raise gremlin.error.GremlinError(
- "Invalid AxisMode in lookup"
- )
+ raise gremlin.error.GremlinError("Invalid AxisMode in lookup")
@staticmethod
def to_enum(value: str) -> AxisMode:
try:
return _AxisMode_to_enum_lookup[value.lower()]
except KeyError:
- raise gremlin.error.GremlinError(
- "Invalid AxisMode in lookup"
- )
+ raise gremlin.error.GremlinError("Invalid AxisMode in lookup")
+
_AxisMode_to_string_lookup = {
AxisMode.Absolute: "absolute",
- AxisMode.Relative: "relative"
+ AxisMode.Relative: "relative",
}
_AxisMode_to_enum_lookup = {
"absolute": AxisMode.Absolute,
- "relative": AxisMode.Relative
+ "relative": AxisMode.Relative,
}
class HatDirection(Enum):
-
"""Represents the possible directions a hat can take on."""
Center = (0, 0)
@@ -505,9 +482,7 @@ def to_string(value: HatDirection) -> str:
try:
return _HatDirection_to_string_lookup[value]
except KeyError:
- raise gremlin.error.GremlinError(
- "Invalid HatDirection in lookup"
- )
+ raise gremlin.error.GremlinError("Invalid HatDirection in lookup")
@staticmethod
def to_enum(value: Union[str, Tuple[int, int]]) -> HatDirection:
@@ -519,14 +494,13 @@ def to_enum(value: Union[str, Tuple[int, int]]) -> HatDirection:
else:
return _HatDirection_to_enum_lookup[value]
except KeyError:
- raise gremlin.error.GremlinError(
- "Invalid HatDirection in lookup"
- )
-
+ raise gremlin.error.GremlinError("Invalid HatDirection in lookup")
+
@staticmethod
def to_display_name(value: HatDirection) -> str:
return value.name
+
_HatDirection_to_string_lookup = {
HatDirection.Center: "center",
HatDirection.North: "north",
@@ -563,9 +537,7 @@ def to_display_name(value: HatDirection) -> str:
}
-
class LogicalOperator(Enum):
-
"""Enumeration of possible condition combinations."""
Any = 1
@@ -573,10 +545,7 @@ class LogicalOperator(Enum):
@staticmethod
def to_display(instance: LogicalOperator) -> str:
- lookup = {
- LogicalOperator.Any: "Any",
- LogicalOperator.All: "All"
- }
+ lookup = {LogicalOperator.Any: "Any", LogicalOperator.All: "All"}
value = lookup.get(instance, None)
if value is None:
raise gremlin.error.GremlinError(
@@ -586,10 +555,7 @@ def to_display(instance: LogicalOperator) -> str:
@staticmethod
def to_string(instance: LogicalOperator) -> str:
- lookup = {
- LogicalOperator.Any: "any",
- LogicalOperator.All: "all"
- }
+ lookup = {LogicalOperator.Any: "any", LogicalOperator.All: "all"}
value = lookup.get(instance, None)
if value is None:
raise gremlin.error.GremlinError(
@@ -599,10 +565,7 @@ def to_string(instance: LogicalOperator) -> str:
@staticmethod
def to_enum(string: str) -> LogicalOperator:
- lookup = {
- "any": LogicalOperator.Any,
- "all": LogicalOperator.All
- }
+ lookup = {"any": LogicalOperator.Any, "all": LogicalOperator.All}
value = lookup.get(string, None)
if value is None:
raise gremlin.error.GremlinError(
@@ -612,7 +575,6 @@ def to_enum(string: str) -> LogicalOperator:
class ConditionType(Enum):
-
"""Enumeration of possible condition types."""
Joystick = 1
@@ -662,30 +624,29 @@ def to_enum(string: str) -> ConditionType:
return value
-
class MouseClickMode(Enum):
- Normal = 0 # click on/off
- Press = 1 # press only
- Release = 2 # release only
- DoubleClick = 3 # double click
+ Normal = 0 # click on/off
+ Press = 1 # press only
+ Release = 2 # release only
+ DoubleClick = 3 # double click
@staticmethod
def to_string(mode):
return mode.name
-
+
def __str__(self):
return str(self.value)
-
+
@classmethod
def _missing_(cls, name):
for item in cls:
if item.name.lower() == name.lower():
return item
return cls.Normal
-
+
@staticmethod
def from_string(str):
- ''' converts from a string representation (text or numeric) to the enum, not case sensitive'''
+ """converts from a string representation (text or numeric) to the enum, not case sensitive"""
str = str.casefold().strip()
if str.isnumeric():
mode = int(str)
@@ -695,10 +656,10 @@ def from_string(str):
return item
return None
-
+
@staticmethod
def to_description(action):
- ''' returns a descriptive string for the action '''
+ """returns a descriptive string for the action"""
if action == MouseClickMode.Normal:
return "Normal Click"
elif action == MouseClickMode.Press:
@@ -708,11 +669,10 @@ def to_description(action):
elif action == MouseClickMode.DoubleClick:
return "Double Click"
return f"Unknown {action}"
-
-
+
@staticmethod
def to_name(action):
- ''' returns the name from the action '''
+ """returns the name from the action"""
if action == MouseClickMode.Normal:
return "Normal Click"
elif action == MouseClickMode.Press:
@@ -722,32 +682,33 @@ def to_name(action):
elif action == MouseClickMode.DoubleClick:
return "Double click"
return f"Unknown {action}"
-
+
+
class MouseAction(Enum):
- MouseButton = 0 # output a mouse button
- MouseMotion = 1 # output a mouse motion
- MouseWiggleOnLocal = 2 # enable mouse wiggle - local machine only
- MouseWiggleOffLocal = 3 # disable mouse wiggle - locla machine only
- MouseWiggleOnRemote = 4 # enable mouse wiggle - remote machines only
- MouseWiggleOffRemote = 5 # disable mouse wiggle - remote machines only
+ MouseButton = 0 # output a mouse button
+ MouseMotion = 1 # output a mouse motion
+ MouseWiggleOnLocal = 2 # enable mouse wiggle - local machine only
+ MouseWiggleOffLocal = 3 # disable mouse wiggle - locla machine only
+ MouseWiggleOnRemote = 4 # enable mouse wiggle - remote machines only
+ MouseWiggleOffRemote = 5 # disable mouse wiggle - remote machines only
@staticmethod
def to_string(mode):
return mode.name
-
+
def __str__(self):
return str(self.value)
-
+
@classmethod
def _missing_(cls, name):
for item in cls:
if item.name.lower() == name.lower():
return item
return cls.MouseButton
-
+
@staticmethod
def from_string(str):
- ''' converts from a string representation (text or numeric) to the enum, not case sensitive'''
+ """converts from a string representation (text or numeric) to the enum, not case sensitive"""
str = str.lower().strip()
if str.isnumeric():
mode = int(str)
@@ -757,10 +718,10 @@ def from_string(str):
return item
return None
-
+
@staticmethod
def to_description(action):
- ''' returns a descriptive string for the action '''
+ """returns a descriptive string for the action"""
if action == MouseAction.MouseButton:
return "Maps a mouse button"
elif action == MouseAction.MouseMotion:
@@ -775,10 +736,10 @@ def to_description(action):
return "Turns wiggle mode on (remote only)"
return f"Unknown {action}"
-
+
@staticmethod
def to_name(action):
- ''' returns the name from the action '''
+ """returns the name from the action"""
if action == MouseAction.MouseButton:
return "Mouse button"
elif action == MouseAction.MouseMotion:
@@ -792,11 +753,10 @@ def to_name(action):
elif action == MouseAction.MouseWiggleOnRemote:
return "Wiggle Enable (remote)"
-
return f"Unknown {action}"
-
-class MouseButton(Enum):
+
+class MouseButton(Enum):
"""Enumeration of all possible mouse buttons."""
Left = 1
@@ -824,15 +784,15 @@ def to_enum(value):
return _MouseButton_to_enum_lookup[value]
except KeyError:
raise gremlin.error.GremlinError("Invalid type in lookup")
-
+
@staticmethod
def to_lookup_string(value):
- ''' mouse button to key lookup name'''
+ """mouse button to key lookup name"""
try:
return _MouseButton_to_lookup_string_lookup[value]
except KeyError:
raise gremlin.error.GremlinError("Invalid type in lookup")
-
+
@staticmethod
def lookup_to_enum(value):
if isinstance(value, int):
@@ -843,7 +803,6 @@ def lookup_to_enum(value):
raise gremlin.error.GremlinError("Invalid type in lookup")
-
_MouseButton_to_string_lookup = {
MouseButton.Left: "Mouse Left",
MouseButton.Right: "Mouse Right",
@@ -853,7 +812,7 @@ def lookup_to_enum(value):
MouseButton.WheelUp: "Wheel Up",
MouseButton.WheelDown: "Wheel Down",
MouseButton.WheelLeft: "Wheel Left",
- MouseButton.WheelRight: "Wheel Right"
+ MouseButton.WheelRight: "Wheel Right",
}
_MouseButton_to_lookup_string_lookup = {
@@ -865,11 +824,10 @@ def lookup_to_enum(value):
MouseButton.WheelUp: "wheel_up",
MouseButton.WheelDown: "wheel_down",
MouseButton.WheelLeft: "wheel_left",
- MouseButton.WheelRight: "wheel_right"
+ MouseButton.WheelRight: "wheel_right",
}
-
_MouseButton_to_enum_lookup = {
"Mouse Left": MouseButton.Left,
"Mouse Right": MouseButton.Right,
@@ -886,7 +844,7 @@ def lookup_to_enum(value):
"Wheel Up": MouseButton.WheelUp,
"Wheel Down": MouseButton.WheelDown,
"Wheel Left": MouseButton.WheelLeft,
- "Wheel Right": MouseButton.WheelRight
+ "Wheel Right": MouseButton.WheelRight,
}
_MouseButton_lookup_to_button_lookup = {
@@ -898,39 +856,56 @@ def lookup_to_enum(value):
"wheel_up": MouseButton.WheelUp,
"wheel_down": MouseButton.WheelDown,
"wheel_left": MouseButton.WheelLeft,
- "wheel_right": MouseButton.WheelRight
+ "wheel_right": MouseButton.WheelRight,
}
@unique
class VerboseMode(IntFlag):
NotSet = 0
- Keyboard = auto() # keyboard input only
- Joystick = auto() # joystick input
- Inputs = auto() # list inputs
- Mouse = auto() # mouse input
- SimConnect = auto() # simconnect interface
- Details = auto() # user interface details
- Condition = auto() # conditions diagnostics / execution graph
- OSC = auto() # OSC data
- Process = auto() # process changes
- Exec = auto() # execution trees
- Midi = auto() # midi mode
- Device = auto() # device change modes
- Macro = auto() # macro
- Gate = auto() # auto
- Outputs = auto() # list outputs
- UI = auto() # UI mode
-
- All = Keyboard | Joystick | Inputs | Mouse | Details | SimConnect | Condition | Process | Exec | Midi | Device | Macro | Gate | Outputs | UI
+ Keyboard = auto() # keyboard input only
+ Joystick = auto() # joystick input
+ Inputs = auto() # list inputs
+ Mouse = auto() # mouse input
+ SimConnect = auto() # simconnect interface
+ Details = auto() # user interface details
+ Condition = auto() # conditions diagnostics / execution graph
+ OSC = auto() # OSC data
+ Process = auto() # process changes
+ Exec = auto() # execution trees
+ Midi = auto() # midi mode
+ Device = auto() # device change modes
+ Macro = auto() # macro
+ Gate = auto() # auto
+ Outputs = auto() # list outputs
+ UI = auto() # UI mode
+
+ All = (
+ Keyboard
+ | Joystick
+ | Inputs
+ | Mouse
+ | Details
+ | SimConnect
+ | Condition
+ | Process
+ | Exec
+ | Midi
+ | Device
+ | Macro
+ | Gate
+ | Outputs
+ | UI
+ )
def __contains__(self, item):
- return (self.value & item.value) == item.value
+ return (self.value & item.value) == item.value
@unique
class TabDeviceType(int, Enum):
- ''' types of devices shown on device tabs '''
+ """types of devices shown on device tabs"""
+
NotSet = 0
Joystick = 1
Keyboard = 2
@@ -944,7 +919,8 @@ class TabDeviceType(int, Enum):
class GamePadOutput(Enum):
- ''' outputs for gamepads '''
+ """outputs for gamepads"""
+
NotSet = auto()
LeftStickX = auto()
LeftStickY = auto()
@@ -971,17 +947,18 @@ class GamePadOutput(Enum):
@staticmethod
def to_string(value):
return _gamepad_output_to_string[value]
-
+
@staticmethod
def to_enum(value):
return _gamepad_output_to_enum[value]
-
+
@staticmethod
def to_display_name(value):
return _gamepad_output_to_display_name[value]
-
+
+
_gamepad_output_to_string = {
- GamePadOutput.NotSet : "none",
+ GamePadOutput.NotSet: "none",
GamePadOutput.LeftStickX: "left_x",
GamePadOutput.LeftStickY: "left_y",
GamePadOutput.RightStickX: "right_x",
@@ -989,24 +966,24 @@ def to_display_name(value):
GamePadOutput.LeftTrigger: "left_trigger",
GamePadOutput.RightTrigger: "right_trigger",
GamePadOutput.ButtonA: "button_a",
- GamePadOutput.ButtonB:"button_b",
+ GamePadOutput.ButtonB: "button_b",
GamePadOutput.ButtonX: "button_x",
- GamePadOutput.ButtonY:"button_y",
- GamePadOutput.ButtonStart:"button_start",
- GamePadOutput.ButtonBack:"button_back",
- GamePadOutput.ButtonThumbLeft:"button_thumb_left",
- GamePadOutput.ButtonThumbRight:"button_thumb_right",
- GamePadOutput.ButtonGuide:"button_guide",
- GamePadOutput.ButtonShoulderLeft:"button_shoulder_left",
- GamePadOutput.ButtonShoulderRight:"button_shoulder_right",
- GamePadOutput.ButtonDpadUp:"button_dpad_up",
- GamePadOutput.ButtonDpadDown:"button_dpad_down",
- GamePadOutput.ButtonDpadLeft:"button_dpad_left",
- GamePadOutput.ButtonDpadRight:"button_dpad_right",
+ GamePadOutput.ButtonY: "button_y",
+ GamePadOutput.ButtonStart: "button_start",
+ GamePadOutput.ButtonBack: "button_back",
+ GamePadOutput.ButtonThumbLeft: "button_thumb_left",
+ GamePadOutput.ButtonThumbRight: "button_thumb_right",
+ GamePadOutput.ButtonGuide: "button_guide",
+ GamePadOutput.ButtonShoulderLeft: "button_shoulder_left",
+ GamePadOutput.ButtonShoulderRight: "button_shoulder_right",
+ GamePadOutput.ButtonDpadUp: "button_dpad_up",
+ GamePadOutput.ButtonDpadDown: "button_dpad_down",
+ GamePadOutput.ButtonDpadLeft: "button_dpad_left",
+ GamePadOutput.ButtonDpadRight: "button_dpad_right",
}
_gamepad_output_to_display_name = {
- GamePadOutput.NotSet : "N/A",
+ GamePadOutput.NotSet: "N/A",
GamePadOutput.LeftStickX: "Left Stick X",
GamePadOutput.LeftStickY: "Left Stick Y",
GamePadOutput.RightStickX: "Right Stick X",
@@ -1014,27 +991,27 @@ def to_display_name(value):
GamePadOutput.LeftTrigger: "Left Trigger",
GamePadOutput.RightTrigger: "Right Trigger",
GamePadOutput.ButtonA: "A",
- GamePadOutput.ButtonB:"B",
+ GamePadOutput.ButtonB: "B",
GamePadOutput.ButtonX: "X",
- GamePadOutput.ButtonY:"Y",
- GamePadOutput.ButtonStart:"Start",
- GamePadOutput.ButtonBack:"Back",
- GamePadOutput.ButtonThumbLeft:"Thumb Left",
- GamePadOutput.ButtonThumbRight:"Thumb Right",
- GamePadOutput.ButtonGuide:"Guide",
- GamePadOutput.ButtonShoulderLeft:"Shoulder Left",
- GamePadOutput.ButtonShoulderRight:"Shoulder Right",
- GamePadOutput.ButtonDpadUp:"Dpad Up",
- GamePadOutput.ButtonDpadDown:"Dpad Down",
- GamePadOutput.ButtonDpadLeft:"Dpad Left",
- GamePadOutput.ButtonDpadRight:"Dpad Right",
+ GamePadOutput.ButtonY: "Y",
+ GamePadOutput.ButtonStart: "Start",
+ GamePadOutput.ButtonBack: "Back",
+ GamePadOutput.ButtonThumbLeft: "Thumb Left",
+ GamePadOutput.ButtonThumbRight: "Thumb Right",
+ GamePadOutput.ButtonGuide: "Guide",
+ GamePadOutput.ButtonShoulderLeft: "Shoulder Left",
+ GamePadOutput.ButtonShoulderRight: "Shoulder Right",
+ GamePadOutput.ButtonDpadUp: "Dpad Up",
+ GamePadOutput.ButtonDpadDown: "Dpad Down",
+ GamePadOutput.ButtonDpadLeft: "Dpad Left",
+ GamePadOutput.ButtonDpadRight: "Dpad Right",
}
_gamepad_output_to_enum = {
- "none": GamePadOutput.NotSet ,
- "left_x" : GamePadOutput.LeftStickX,
- "left_y": GamePadOutput.LeftStickY ,
- "right_x" : GamePadOutput.RightStickX,
+ "none": GamePadOutput.NotSet,
+ "left_x": GamePadOutput.LeftStickX,
+ "left_y": GamePadOutput.LeftStickY,
+ "right_x": GamePadOutput.RightStickX,
"right_y": GamePadOutput.RightStickY,
"left_trigger": GamePadOutput.LeftTrigger,
"right_trigger": GamePadOutput.RightTrigger,
@@ -1057,7 +1034,6 @@ def to_display_name(value):
class ActivationRule(Enum):
-
"""Activation rules for collections of conditions.
All requires all the conditions in a collection to evaluate to True while
diff --git a/gremlin/ui/__init__.py b/gremlin/ui/__init__.py
index 0468910b..6b42754a 100644
--- a/gremlin/ui/__init__.py
+++ b/gremlin/ui/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/gremlin/ui/axis_calibration.py b/gremlin/ui/axis_calibration.py
index 2d631119..beb80e95 100644
--- a/gremlin/ui/axis_calibration.py
+++ b/gremlin/ui/axis_calibration.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -26,7 +26,6 @@
import gremlin.ui.ui_common
import gremlin.event_handler
import gremlin.shared_state
-from gremlin.util import axis_calibration, create_calibration_function
from gremlin.ui.qsliderwidget import QSliderWidget
from PySide6.QtGui import QColor
from lxml import etree
@@ -36,8 +35,8 @@
syslog = logging.getLogger("system♂")
-class CalibrationUi(ui_common.BaseDialogUi):
+class CalibrationUi(ui_common.BaseDialogUi):
"""Dialog to calibrate joystick axes."""
def __init__(self, parent=None):
@@ -71,9 +70,7 @@ def _create_ui(self):
# Device selection drop down
self.device_dropdown = gremlin.ui.ui_common.QComboBox()
- self.device_dropdown.currentIndexChanged.connect(
- self._create_axes
- )
+ self.device_dropdown.currentIndexChanged.connect(self._create_axes)
for device in self.devices:
self.device_dropdown.addItem(device.name)
@@ -136,7 +133,7 @@ def _save_calibration(self):
cfg = gremlin.config.Configuration()
cfg.set_calibration(
self.devices[self.current_selection_id].device_guid,
- [axis.limits for axis in self.axes]
+ [axis.limits for axis in self.axes],
)
gremlin.event_handler.EventListener().reload_calibrations()
@@ -158,13 +155,14 @@ def _handle_event(self, event):
:param event the event to process
"""
- if event.device_guid == self.devices[self.current_selection_id].device_guid \
- and event.event_type == InputType.JoystickAxis:
+ if (
+ event.device_guid == self.devices[self.current_selection_id].device_guid
+ and event.event_type == InputType.JoystickAxis
+ ):
axis_id = gremlin.joystick_handling.linear_axis_index(
- self.devices[self.current_selection_id].axis_map,
- event.identifier
+ self.devices[self.current_selection_id].axis_map, event.identifier
)
- self.axes[axis_id-1].set_current(event.raw_value)
+ self.axes[axis_id - 1].set_current(event.raw_value)
def closeEvent(self, event):
"""Closes the calibration window.
@@ -180,7 +178,6 @@ def closeEvent(self, event):
class AxisCalibrationWidget(QtWidgets.QWidget):
-
"""Widget displaying calibration information about a single axis."""
def __init__(self, parent=None):
@@ -246,31 +243,30 @@ def _update_labels(self):
class CalibrationData:
def __init__(self):
- self.device_guid = None # axis device guid this data applies to
- self.input_id = None # axis input id this data applies to
- self._is_centered = True # true if the axis is centered (has a center calibration value)
+ self.device_guid = None # axis device guid this data applies to
+ self.input_id = None # axis input id this data applies to
+ self._is_centered = (
+ True # true if the axis is centered (has a center calibration value)
+ )
self.reset()
-
def reset(self):
- ''' resets calibration data to defaults '''
+ """resets calibration data to defaults"""
# do not reset center option
self._calibrated_min = -1.0
self._calibrated_max = 1.0
- self._calibrated_center = 0.0 # used only if the stick is centered
+ self._calibrated_center = 0.0 # used only if the stick is centered
self._deadzone_min = -1.0
self._deadzone_max = 1.0
- self._deadzone_center_min = 0.0 # deadzone center left
- self._deadzone_center_max = 0.0 # deadzone center right
- self._inverted = False # true if inverted
-
-
+ self._deadzone_center_min = 0.0 # deadzone center left
+ self._deadzone_center_max = 0.0 # deadzone center right
+ self._inverted = False # true if inverted
def startCalibrate(self):
- ''' starts a recalibration'''
+ """starts a recalibration"""
self._calibrated_min = 0.0
self._calibrated_max = 0.0
- self._calibrated_center = 0.0 # used only if the stick is centered
+ self._calibrated_center = 0.0 # used only if the stick is centered
def stopCalibrate(self):
self._calibrate = False
@@ -280,56 +276,61 @@ def calibrating(self) -> bool:
@property
def hasData(self) -> bool:
- ''' True if the calibration data is non default '''
- return self._calibrated_min != -1.0 or \
- self._calibrated_max != 1.0 or \
- self._calibrated_center != 0.0 or \
- self._deadzone_min != -1.0 or \
- self._deadzone_max != 1.0 or \
- self._deadzone_center_min != 0.0 or \
- self._deadzone_center_max != 0.0 or \
- self._inverted
-
-
+ """True if the calibration data is non default"""
+ return (
+ self._calibrated_min != -1.0
+ or self._calibrated_max != 1.0
+ or self._calibrated_center != 0.0
+ or self._deadzone_min != -1.0
+ or self._deadzone_max != 1.0
+ or self._deadzone_center_min != 0.0
+ or self._deadzone_center_max != 0.0
+ or self._inverted
+ )
+
def _update(self):
- ''' updates data flag '''
+ """updates data flag"""
# let the UI know the data changed
el = gremlin.event_handler.EventListener()
el.calibration_changed.emit(self)
- def compare(self, other : CalibrationData) -> bool:
- ''' compares two calibration objects to see if they map to the same object '''
+ def compare(self, other: CalibrationData) -> bool:
+ """compares two calibration objects to see if they map to the same object"""
if other is None:
return False
return self.device_guid == other.device_guid and self.input_id == other.input_id
-
@property
- def inverted(self)-> bool:
+ def inverted(self) -> bool:
return self._inverted
+
@inverted.setter
- def inverted(self, value : bool):
+ def inverted(self, value: bool):
self._inverted = value
self._update()
-
-
@property
- def centered(self)-> bool:
+ def centered(self) -> bool:
return self._is_centered
+
@centered.setter
- def centered(self, value : bool):
+ def centered(self, value: bool):
self._is_centered = value
self._update()
@property
def deadzone(self) -> list:
if self._is_centered:
- return [self._deadzone_min, self._deadzone_center_min, self._deadzone_center_max, self.deadzone_max]
+ return [
+ self._deadzone_min,
+ self._deadzone_center_min,
+ self._deadzone_center_max,
+ self.deadzone_max,
+ ]
return [self._deadzone_min, self._deadzone_max]
-
+
@deadzone.setter
- def deadzone(self, value : list):
+ def deadzone(self, value: list):
if len(value) == 2:
d_start, d_end = value
self.deadzone_min = d_start
@@ -341,12 +342,11 @@ def deadzone(self, value : list):
self.deadzone_center_min = d_left
self.deadzone_center_max = d_right
self._update()
-
-
@property
def deadzone_min(self):
return self._deadzone_min
+
@deadzone_min.setter
def deadzone_min(self, value):
if self._deadzone_min != value:
@@ -356,6 +356,7 @@ def deadzone_min(self, value):
@property
def deadzone_max(self):
return self._deadzone_max
+
@deadzone_max.setter
def deadzone_max(self, value):
if self._deadzone_max:
@@ -365,6 +366,7 @@ def deadzone_max(self, value):
@property
def deadzone_center_min(self):
return self._deadzone_center_min
+
@deadzone_center_min.setter
def deadzone_center_min(self, value):
if self._deadzone_center_min != value:
@@ -374,18 +376,17 @@ def deadzone_center_min(self, value):
@property
def deadzone_center_max(self):
return self._deadzone_center_max
+
@deadzone_center_max.setter
def deadzone_center_max(self, value):
if self._deadzone_center_max != value:
- self._deadzone_center_max = value + 0.0
+ self._deadzone_center_max = value + 0.0
self._update()
-
-
-
@property
def calibrated_min(self):
return self._calibrated_min
+
@calibrated_min.setter
def calibrated_min(self, value):
if self._calibrated_min != value:
@@ -395,6 +396,7 @@ def calibrated_min(self, value):
@property
def calibrated_center(self):
return self._calibrated_center
+
@calibrated_center.setter
def calibrated_center(self, value):
if self._calibrated_center != value:
@@ -403,85 +405,104 @@ def calibrated_center(self, value):
@property
def calibrated_max(self):
- return self._calibrated_max
+ return self._calibrated_max
+
@calibrated_max.setter
def calibrated_max(self, value):
if self._calibrated_max != value:
self._calibrated_max = value + 0.0
self._update()
-
- def getValue(self, raw_value, normalize = True):
- ''' gets the deadzoned, calibrated value for the input value -1.0 to +1.0 - if normalized is enabled, expects a dinput range value, if not, expects a -1 to +1 value'''
+ def getValue(self, raw_value, normalize=True):
+ """gets the deadzoned, calibrated value for the input value -1.0 to +1.0 - if normalized is enabled, expects a dinput range value, if not, expects a -1 to +1 value"""
if normalize:
- normalized_value = gremlin.util.scale_to_range(raw_value, source_min = -32768, source_max = 32767, invert = self.inverted)
+ normalized_value = gremlin.util.scale_to_range(
+ raw_value, source_min=-32768, source_max=32767, invert=self.inverted
+ )
elif self.inverted:
- normalized_value = gremlin.util.scale_to_range(raw_value, invert = self.inverted) # just handle the inversion
+ normalized_value = gremlin.util.scale_to_range(
+ raw_value, invert=self.inverted
+ ) # just handle the inversion
else:
normalized_value = raw_value
if self._is_centered:
# account for center calibration left/right
- value = gremlin.util.axis_calibration(normalized_value, self._calibrated_min, self.calibrated_center, self._calibrated_max)
+ value = gremlin.util.axis_calibration(
+ normalized_value,
+ self._calibrated_min,
+ self.calibrated_center,
+ self._calibrated_max,
+ )
else:
- value = gremlin.util.slider_calibration(normalized_value, self._calibrated_min, self._calibrated_max)
+ value = gremlin.util.slider_calibration(
+ normalized_value, self._calibrated_min, self._calibrated_max
+ )
if self._is_centered:
if value > self.deadzone_center_min and value < self.deadzone_center_max:
value = 0.0
elif value <= self.deadzone_center_min:
# center deadzone set - update the range as it's been reduced
- value = gremlin.util.scale_to_range(value, source_min = self.deadzone_min, source_max = self.deadzone_center_min, target_max = 0)
+ value = gremlin.util.scale_to_range(
+ value,
+ source_min=self.deadzone_min,
+ source_max=self.deadzone_center_min,
+ target_max=0,
+ )
elif value >= self.deadzone_center_max:
- value = gremlin.util.scale_to_range(value, source_min = self.deadzone_center_max, source_max = self.deadzone_max, target_min = 0)
+ value = gremlin.util.scale_to_range(
+ value,
+ source_min=self.deadzone_center_max,
+ source_max=self.deadzone_max,
+ target_min=0,
+ )
else:
- value = gremlin.util.scale_to_range(value, source_min=self.deadzone_min, source_max=self.deadzone_max)
-
-
-
+ value = gremlin.util.scale_to_range(
+ value, source_min=self.deadzone_min, source_max=self.deadzone_max
+ )
return value + 0.0
-
-
+ def from_xml(self, node, data=None):
+ """reads data from XML"""
- def from_xml(self, node, data = None):
- ''' reads data from XML'''
-
- if not "device-guid" in node.attrib:
- return # no calibration data
+ if "device-guid" not in node.attrib:
+ return # no calibration data
device_guid = node.get("device-guid")
- if not device_guid or device_guid == 'None':
- return # no calibration data
+ if not device_guid or device_guid == "None":
+ return # no calibration data
self.device_guid = parse_guid(device_guid)
- input_id = safe_read(node,"input-id", str, "")
+ input_id = safe_read(node, "input-id", str, "")
if input_id and input_id.isnumeric():
self.input_id = int(input_id)
else:
self.input_id = input_id
-
- self.inverted = safe_read(node,"inverted",bool, False)
-
- self.centered = safe_read(node,"centered",bool)
- self.calibrated_min = safe_read(node,"calibrate-min", float,-1.0)
-
- self.calibrated_max = safe_read(node,"calibrate-max", float, 1.0)
- self.deadzone_min = safe_read(node,"deadzone-min", float, -1.0)
- self.deadzone_max = safe_read(node,"deadzone-max", float, 1.0)
+
+ self.inverted = safe_read(node, "inverted", bool, False)
+
+ self.centered = safe_read(node, "centered", bool)
+ self.calibrated_min = safe_read(node, "calibrate-min", float, -1.0)
+
+ self.calibrated_max = safe_read(node, "calibrate-max", float, 1.0)
+ self.deadzone_min = safe_read(node, "deadzone-min", float, -1.0)
+ self.deadzone_max = safe_read(node, "deadzone-max", float, 1.0)
if self.centered:
- self.calibrated_center = safe_read(node,"calibrate-center", float, 0.0)
- self.deadzone_center_min = safe_read(node,"deadzone-center-min", float, 0.0)
- self.deadzone_center_max = safe_read(node,"deadzone-center-max", float, 0.0)
-
+ self.calibrated_center = safe_read(node, "calibrate-center", float, 0.0)
+ self.deadzone_center_min = safe_read(
+ node, "deadzone-center-min", float, 0.0
+ )
+ self.deadzone_center_max = safe_read(
+ node, "deadzone-center-max", float, 0.0
+ )
def to_xml(self):
-
node = etree.Element("calibration")
if self.device_guid is None:
return node
node.set("device-guid", str(self.device_guid))
- node.set("input-id",safe_format(self.input_id, int))
+ node.set("input-id", safe_format(self.input_id, int))
node.set("inverted", safe_format(self.inverted, bool))
node.set("centered", safe_format(self.centered, bool))
node.set("calibrate-min", safe_format(self.calibrated_min, float))
@@ -490,18 +511,22 @@ def to_xml(self):
node.set("deadzone-max", safe_format(self.deadzone_max, float))
if self.centered:
node.set("calibrate-center", safe_format(self.calibrated_center, float))
- node.set("deadzone-center-min", safe_format(self.deadzone_center_min, float))
- node.set("deadzone-center-max", safe_format(self.deadzone_center_max, float))
+ node.set(
+ "deadzone-center-min", safe_format(self.deadzone_center_min, float)
+ )
+ node.set(
+ "deadzone-center-max", safe_format(self.deadzone_center_max, float)
+ )
return node
-
+
def clone(self):
- ''' duplicates '''
+ """duplicates"""
import copy
+
return copy.deepcopy(self)
-
def _loadLegacy(self):
- ''' loads legacy calibration data '''
+ """loads legacy calibration data"""
config = gremlin.config.Configuration()
data = config.get_calibration(self.device_guid, self.input_id)
v1, v2, v3 = data
@@ -513,103 +538,101 @@ def _loadLegacy(self):
self.calibrated_center = v2
self.calibrated_max = v3
+
@gremlin.singleton_decorator.SingletonDecorator
-class CalibrationManager():
- ''' manages calibration data '''
+class CalibrationManager:
+ """manages calibration data"""
def __init__(self):
current_profile_folder = gremlin.util.userprofile_path().lower()
- self.calibration_file = os.path.join(current_profile_folder,"calibration.xml")
+ self.calibration_file = os.path.join(current_profile_folder, "calibration.xml")
self.calibration_map = {}
self._load()
-
def getCalibration(self, device_guid, input_id) -> CalibrationData:
- ''' gets calibration data for a given device/axis '''
- if not device_guid in self.calibration_map:
+ """gets calibration data for a given device/axis"""
+ if device_guid not in self.calibration_map:
self.calibration_map[device_guid] = {}
- if not input_id in self.calibration_map[device_guid]:
+ if input_id not in self.calibration_map[device_guid]:
calibration = CalibrationData()
calibration.device_guid = device_guid
calibration.input_id = input_id
- calibration._loadLegacy() # load old data if present
+ calibration._loadLegacy() # load old data if present
self.calibration_map[device_guid][input_id] = calibration
return self.calibration_map[device_guid][input_id]
-
- def saveCalibration(self, calibration : CalibrationData):
+
+ def saveCalibration(self, calibration: CalibrationData):
device_guid = calibration.device_guid
input_id = calibration.input_id
- if not device_guid in self.calibration_map:
+ if device_guid not in self.calibration_map:
self.calibration_map[device_guid] = {}
-
+
self.calibration_map[device_guid][input_id] = calibration
self._save()
-
-
-
-
-
def _load(self):
- ''' loads calibration data '''
+ """loads calibration data"""
if os.path.isfile(self.calibration_file):
parser = etree.XMLParser(remove_comments=True, remove_blank_text=True)
try:
tree = etree.parse(self.calibration_file, parser=parser)
- nodes = tree.findall(f".//calibration")
+ nodes = tree.findall(".//calibration")
for node in nodes:
data = CalibrationData()
data.from_xml(node)
device_guid = data.device_guid
input_id = data.input_id
- if not device_guid in self.calibration_map:
+ if device_guid not in self.calibration_map:
self.calibration_map[device_guid] = {}
self.calibration_map[device_guid][input_id] = data
except Exception as ex:
syslog.error(f"Error loading calibration data: {ex}")
- return False
-
- def _save(self):
-
- root = etree.Element("root")
- for device_guid in self.calibration_map.keys():
- for input_id in self.calibration_map[device_guid]:
- data = self.calibration_map[device_guid][input_id]
- node = data.to_xml()
- root.append(node)
+ return False
- try:
- tree = etree.ElementTree(root)
- tree.write(self.calibration_file, pretty_print=True,xml_declaration=True,encoding="utf-8")
- syslog.info(f"Calibration data saved.")
- except Exception as ex:
- syslog.error(f"Error saving calibration: {ex}")
+ def _save(self):
+ root = etree.Element("root")
+ for device_guid in self.calibration_map.keys():
+ for input_id in self.calibration_map[device_guid]:
+ data = self.calibration_map[device_guid][input_id]
+ node = data.to_xml()
+ root.append(node)
+
+ try:
+ tree = etree.ElementTree(root)
+ tree.write(
+ self.calibration_file,
+ pretty_print=True,
+ xml_declaration=True,
+ encoding="utf-8",
+ )
+ syslog.info("Calibration data saved.")
+ except Exception as ex:
+ syslog.error(f"Error saving calibration: {ex}")
class CalibrationDialogEx(gremlin.ui.ui_common.QRememberDialog):
- ''' gremlinex single input calibration window '''
- def __init__(self, device_guid, input_id, parent = None):
- '''
+ """gremlinex single input calibration window"""
+
+ def __init__(self, device_guid, input_id, parent=None):
+ """
Setup single real hardware axis calibration data
Arguments:
device_guid -- device guid
input_id -- axis number
-
- '''
- super().__init__(self.__class__.__name__, parent = parent)
+
+ """
+ super().__init__(self.__class__.__name__, parent=parent)
from gremlin.curve_handler import DeadzoneWidget
self.setModal(True)
-
-
- self.mgr : CalibrationManager = CalibrationManager()
+ self.mgr: CalibrationManager = CalibrationManager()
self.main_layout = QtWidgets.QVBoxLayout(self)
self.device = gremlin.joystick_handling.device_info_from_guid(device_guid)
@@ -617,20 +640,29 @@ def __init__(self, device_guid, input_id, parent = None):
self.cloned_action_data = self.action_data.clone()
self.action_data.device_guid = device_guid
self.action_data.input_id = input_id
-
self.setWindowTitle("Input Axis Calibration")
info = gremlin.joystick_handling.device_info_from_guid(device_guid)
-
- self.main_layout.addWidget(QtWidgets.QLabel(f"{info.name} Axis: {info.axis_names[input_id-1]}"))
- self.main_layout.addWidget(QtWidgets.QLabel("Note: Calibration options will apply to the computed input data value before any other parts of GremlinEx process the input."))
+
+ self.main_layout.addWidget(
+ QtWidgets.QLabel(f"{info.name} Axis: {info.axis_names[input_id-1]}")
+ )
+ self.main_layout.addWidget(
+ QtWidgets.QLabel(
+ "Note: Calibration options will apply to the computed input data value before any other parts of GremlinEx process the input."
+ )
+ )
# device options
self._options_container_repeater_widget = QtWidgets.QWidget()
- self._options_container_repeater_layout = QtWidgets.QHBoxLayout(self._options_container_repeater_widget)
+ self._options_container_repeater_layout = QtWidgets.QHBoxLayout(
+ self._options_container_repeater_widget
+ )
self._centered_widget = QtWidgets.QCheckBox("Centered axis")
- self._centered_widget.setToolTip("Enabled if the input calibration should take into account a center location, usually the case for centered axes, and disabled for a slider.")
+ self._centered_widget.setToolTip(
+ "Enabled if the input calibration should take into account a center location, usually the case for centered axes, and disabled for a slider."
+ )
self._centered_widget.setChecked(self.action_data.centered)
self._centered_widget.clicked.connect(self._centered_changed)
@@ -640,20 +672,25 @@ def __init__(self, device_guid, input_id, parent = None):
self._inverted_widget.clicked.connect(self._inverted_changed)
self._reset_widget = QtWidgets.QPushButton("Reset")
- self._reset_widget.setToolTip("Resets the calibration information to default and removes any filtering.")
+ self._reset_widget.setToolTip(
+ "Resets the calibration information to default and removes any filtering."
+ )
self._reset_widget.clicked.connect(self._reset_calibration)
self._calibrate_widget = QtWidgets.QPushButton("Calibrate")
- self._calibrate_widget.setToolTip("Sets the calibration endpoints to center. After pressing this, move the input axis to its maximum travel positions to automatically set the calibration data.")
+ self._calibrate_widget.setToolTip(
+ "Sets the calibration endpoints to center. After pressing this, move the input axis to its maximum travel positions to automatically set the calibration data."
+ )
self._calibrate_widget.clicked.connect(self._start_calibration)
self._auto_calibrate_widget = QtWidgets.QCheckBox("Auto Calibrate")
self._auto_calibrate_widget.setChecked(True)
self._auto_calibrate_widget.clicked.connect(self._update)
-
self._center_widget = QtWidgets.QPushButton("Set Center")
- self._center_widget.setToolTip("Sets the center calibration at the current input value. This is helpful for inputs that do not report the midpoint value while in their center detent or to shift it.")
+ self._center_widget.setToolTip(
+ "Sets the center calibration at the current input value. This is helpful for inputs that do not report the midpoint value while in their center detent or to shift it."
+ )
self._center_widget.clicked.connect(self._set_center_calibration)
self._options_container_repeater_layout.addWidget(self._centered_widget)
@@ -665,18 +702,23 @@ def __init__(self, device_guid, input_id, parent = None):
self._options_container_repeater_layout.addStretch()
-
# raw axis input repeater
self._raw_container_repeater_widget = QtWidgets.QWidget()
- self._raw_container_repeater_layout = QtWidgets.QHBoxLayout(self._raw_container_repeater_widget)
+ self._raw_container_repeater_layout = QtWidgets.QHBoxLayout(
+ self._raw_container_repeater_widget
+ )
# calibrated axis repeater
self._calibrated_container_repeater_widget = QtWidgets.QWidget()
- self._calibrated_container_repeater_layout = QtWidgets.QHBoxLayout(self._calibrated_container_repeater_widget)
+ self._calibrated_container_repeater_layout = QtWidgets.QHBoxLayout(
+ self._calibrated_container_repeater_widget
+ )
self._slider = QSliderWidget()
self._slider.valueChanged.connect(self._slider_changed)
- self._slider.setToolTip("Calibration slider.
The endpoints represent the minimum/maxium values possible for this axis, and the position of the center detent for centered devices.
Sliders will not have a center detent.
Move the input to the maximum travel positions to set the enpoints.")
+ self._slider.setToolTip(
+ "Calibration slider.
The endpoints represent the minimum/maxium values possible for this axis, and the position of the center detent for centered devices.
Sliders will not have a center detent.
Move the input to the maximum travel positions to set the enpoints."
+ )
self._repeater = QSliderWidget()
self._repeater.setReadOnly(True)
@@ -684,43 +726,50 @@ def __init__(self, device_guid, input_id, parent = None):
self._repeater.desired_height = 20
self._repeater.handleColor = QColor("#d6ae3e")
self._repeater.setToolTip("Calibrated output value")
-
self._raw_value_widget = ui_common.QFloatLineEdit()
self._raw_value_widget.setReadOnly(True)
self._calibrated_value_widget = ui_common.QFloatLineEdit()
self._calibrated_value_widget.setReadOnly(True)
- self._calibrated_value_widget.setToolTip("Computed output value based on the current calibration settings.")
+ self._calibrated_value_widget.setToolTip(
+ "Computed output value based on the current calibration settings."
+ )
self._calibrated_min_widget = ui_common.QFloatLineEdit()
self._calibrated_min_widget.valueChanged.connect(self._calibrated_min_changed)
- self._calibrated_min_widget.setToolTip("Minimum value of the axis possible input travel.
Move the input to the minimum travel position to set this value after pressing the calibrate button.
Can also be set manually.")
-
+ self._calibrated_min_widget.setToolTip(
+ "Minimum value of the axis possible input travel.
Move the input to the minimum travel position to set this value after pressing the calibrate button.
Can also be set manually."
+ )
self._calibrated_max_widget = ui_common.QFloatLineEdit()
self._calibrated_max_widget.valueChanged.connect(self._calibrated_max_changed)
- self._calibrated_max_widget.setToolTip("Maximum value of the axis possible input travel.
Move the input to the maximum travel position to set this value after pressing the calibrate button.
Can also be set manually.")
-
+ self._calibrated_max_widget.setToolTip(
+ "Maximum value of the axis possible input travel.
Move the input to the maximum travel position to set this value after pressing the calibrate button.
Can also be set manually."
+ )
self._calibrated_center_widget = ui_common.QFloatLineEdit()
- self._calibrated_center_widget.valueChanged.connect(self._calibrated_center_changed)
- self._calibrated_center_widget.setToolTip("For centered inputs, this is the position of the input when it is at the center detent or midpoint of travel.
Press the center button to set this value when the axis is in the center position.
can also be set manually.")
-
+ self._calibrated_center_widget.valueChanged.connect(
+ self._calibrated_center_changed
+ )
+ self._calibrated_center_widget.setToolTip(
+ "For centered inputs, this is the position of the input when it is at the center detent or midpoint of travel.
Press the center button to set this value when the axis is in the center position.
can also be set manually."
+ )
self._calibration_container_widget = QtWidgets.QWidget()
- self._calibration_container_layout = QtWidgets.QGridLayout(self._calibration_container_widget)
-
-
+ self._calibration_container_layout = QtWidgets.QGridLayout(
+ self._calibration_container_widget
+ )
- self._calibration_container_layout.addWidget(QtWidgets.QLabel("Min:"),0,0)
- self._calibration_container_layout.addWidget(self._calibrated_min_widget, 0,1)
+ self._calibration_container_layout.addWidget(QtWidgets.QLabel("Min:"), 0, 0)
+ self._calibration_container_layout.addWidget(self._calibrated_min_widget, 0, 1)
self._center_label = QtWidgets.QLabel("Center:")
- self._calibration_container_layout.addWidget(self._center_label,0,2)
- self._calibration_container_layout.addWidget(self._calibrated_center_widget, 0,3)
- self._calibration_container_layout.addWidget(QtWidgets.QLabel("Max:"),0,4)
- self._calibration_container_layout.addWidget(self._calibrated_max_widget, 0,5)
-
+ self._calibration_container_layout.addWidget(self._center_label, 0, 2)
+ self._calibration_container_layout.addWidget(
+ self._calibrated_center_widget, 0, 3
+ )
+ self._calibration_container_layout.addWidget(QtWidgets.QLabel("Max:"), 0, 4)
+ self._calibration_container_layout.addWidget(self._calibrated_max_widget, 0, 5)
self._raw_container_repeater_layout.addWidget(QtWidgets.QLabel("Input:"))
self._raw_container_repeater_layout.addWidget(self._raw_value_widget)
@@ -728,20 +777,16 @@ def __init__(self, device_guid, input_id, parent = None):
self._raw_container_repeater_layout.addWidget(self._calibrated_value_widget)
self._raw_container_repeater_layout.addStretch()
-
-
self._deadzone_widget = DeadzoneWidget(self.action_data)
self._deadzone_widget.isCentered = self.action_data.centered
self._deadzone_widget.changed.connect(self._deadzone_changed)
-
self.main_layout.addWidget(self._options_container_repeater_widget)
self.main_layout.addWidget(self._slider)
self.main_layout.addWidget(self._repeater)
self.main_layout.addWidget(self._calibration_container_widget)
self.main_layout.addWidget(self._raw_container_repeater_widget)
self.main_layout.addWidget(self._calibrated_container_repeater_widget)
-
self.main_layout.addWidget(self._deadzone_widget)
self.main_layout.addStretch()
@@ -755,7 +800,7 @@ def __init__(self, device_guid, input_id, parent = None):
self._update()
def closeEvent(self, event):
- ''' save data on dialog close '''
+ """save data on dialog close"""
if not os.path.isfile(self.mgr.calibration_file):
# never saved
self.mgr.saveCalibration(self.action_data)
@@ -765,12 +810,9 @@ def closeEvent(self, event):
self.mgr.saveCalibration(self.action_data)
return super().closeEvent(event)
-
-
-
@QtCore.Slot()
def _slider_changed(self, handle, value):
- ''' slider changed '''
+ """slider changed"""
match handle:
case 0:
@@ -783,7 +825,7 @@ def _slider_changed(self, handle, value):
else:
if value <= self.action_data.calibrated_min:
value = self.action_data.calibrated_min + 0.001
- self.action_data.calibrated_max = value
+ self.action_data.calibrated_max = value
case 2:
if value <= self.action_data.calibrated_min:
value = self.action_data.calibrated_min + 0.001
@@ -792,27 +834,27 @@ def _slider_changed(self, handle, value):
@QtCore.Slot()
def _reset_calibration(self):
- ''' reset calibration for the axis '''
+ """reset calibration for the axis"""
self.action_data.reset()
self._update()
@QtCore.Slot()
def _set_center_calibration(self):
- ''' reset calibration for the axis '''
- raw_value = gremlin.joystick_handling.get_axis(self.action_data.device_guid, self.action_data.input_id)
+ """reset calibration for the axis"""
+ raw_value = gremlin.joystick_handling.get_axis(
+ self.action_data.device_guid, self.action_data.input_id
+ )
self.action_data.calibrated_center = raw_value
self._update()
@QtCore.Slot()
def _start_calibration(self):
- ''' resets the calibration data enpoints'''
+ """resets the calibration data enpoints"""
self.action_data._calibrated_min = 0
self.action_data._calibrated_max = 0
self.action_data._update()
self._update()
-
-
@QtCore.Slot()
def _calibrated_min_changed(self):
value = self._calibrated_min_widget.value()
@@ -836,71 +878,65 @@ def _calibrated_center_changed(self):
value = self._calibrated_center_widget.value()
self.action_data._calibrated_center = value
self._update()
-
@QtCore.Slot()
def _deadzone_changed(self):
- ''' deadzone widget changed '''
+ """deadzone widget changed"""
values = self._deadzone_widget.values()
self.action_data.deadzone = values
-
@QtCore.Slot(bool)
def _centered_changed(self, checked):
self.action_data.centered = checked
self._update()
-
@QtCore.Slot(bool)
def _inverted_changed(self, checked):
self.action_data.inverted = checked
self._update()
- def _getCalibratedValue(self, value : float):
+ def _getCalibratedValue(self, value: float):
return self.action_data.getValue(value)
-
+
def _joystick_event_handler(self, event):
- ''' handles joystick events in the UI (functor handles the output when profile is running) so we see the output at design time '''
+ """handles joystick events in the UI (functor handles the output when profile is running) so we see the output at design time"""
if gremlin.shared_state.is_running:
- return
+ return
if not event.is_axis:
- return
-
+ return
+
if event.device_guid != self.action_data.device_guid:
return
-
+
if event.identifier != self.action_data.input_id:
return
self._update()
-
-
def _update(self):
- ''' updates the data '''
+ """updates the data"""
is_centered = self.action_data.centered
auto_calibrate = self._auto_calibrate_widget.isChecked()
-
self._deadzone_widget.isCentered = is_centered
self._center_label.setVisible(is_centered)
self._calibrated_center_widget.setVisible(is_centered)
- raw_value = gremlin.joystick_handling.get_axis(self.action_data.device_guid, self.action_data.input_id) # raw value from dinput
+ raw_value = gremlin.joystick_handling.get_axis(
+ self.action_data.device_guid, self.action_data.input_id
+ ) # raw value from dinput
self._raw_value_widget.setValue(raw_value)
-
+
# calibration mode = push the calibration to min/max
if auto_calibrate:
if raw_value < self.action_data.calibrated_min:
self.action_data.calibrated_min = raw_value
if raw_value > self.action_data.calibrated_max:
self.action_data.calibrated_max = raw_value
-
-
- calibrated_value = self.action_data.getValue(raw_value, normalize = False)
+ calibrated_value = self.action_data.getValue(raw_value, normalize=False)
with QtCore.QSignalBlocker(self._calibrated_min_widget):
self._calibrated_min_widget.setValue(self.action_data.calibrated_min)
@@ -911,24 +947,33 @@ def _update(self):
with QtCore.QSignalBlocker(self._calibrated_value_widget):
self._calibrated_value_widget.setValue(calibrated_value)
-
+
with QtCore.QSignalBlocker(self._deadzone_widget):
self._deadzone_widget.isCentered = is_centered
self._deadzone_widget.setValues(self.action_data.deadzone)
-
- self._update_axis_widget(raw_value,
- calibrated_value,
- self.action_data.calibrated_min,
- self.action_data.calibrated_center if is_centered else None,
- self.action_data.calibrated_max)
+ self._update_axis_widget(
+ raw_value,
+ calibrated_value,
+ self.action_data.calibrated_min,
+ self.action_data.calibrated_center if is_centered else None,
+ self.action_data.calibrated_max,
+ )
- def _update_axis_widget(self, raw_value : float, calibrated_value : float, min_value : float, center_value : float, max_value : float):
+ def _update_axis_widget(
+ self,
+ raw_value: float,
+ calibrated_value: float,
+ min_value: float,
+ center_value: float,
+ max_value: float,
+ ):
with QtCore.QSignalBlocker(self._slider):
self._slider.setValue([min_value, center_value, max_value])
self._slider.setMarkerValue(raw_value)
self._repeater.setValue(calibrated_value)
- #print (f"{raw_value} -> {calibrated_value:0.3f}")
+ # print (f"{raw_value} -> {calibrated_value:0.3f}")
+
-_calibration_manager = CalibrationManager()
\ No newline at end of file
+_calibration_manager = CalibrationManager()
diff --git a/gremlin/ui/backend.py b/gremlin/ui/backend.py
index 378f86d3..827782c8 100644
--- a/gremlin/ui/backend.py
+++ b/gremlin/ui/backend.py
@@ -24,13 +24,20 @@
from PySide6 import QtCore
from PySide6.QtCore import Property, Signal, Slot
-from gremlin import code_runner, common, config, error, plugin_manager, \
- profile, shared_state, types
+from gremlin import (
+ code_runner,
+ common,
+ config,
+ error,
+ plugin_manager,
+ profile,
+ shared_state,
+ types,
+)
from gremlin.signal import signal
from gremlin.ui.device import InputIdentifier
-from gremlin.ui.profile import ActionNodeModel, InputItemBindingModel, \
- InputItemModel
+from gremlin.ui.profile import ActionNodeModel, InputItemBindingModel, InputItemModel
syslog = logging.getLogger("system")
@@ -41,13 +48,12 @@
types.PropertyType.List,
[],
"List of recently opened profiles",
- False
+ False,
)
@common.SingletonDecorator
class Backend(QtCore.QObject):
-
"""Allows interfacing between the QML frontend and the Python backend."""
windowTitleChanged = Signal()
@@ -56,7 +62,6 @@ class Backend(QtCore.QObject):
inputConfigurationChanged = Signal()
activityChanged = Signal()
-
def __init__(self, parent=None):
super().__init__(parent)
@@ -79,8 +84,6 @@ def toggleActiveState(self):
"""Toggles Gremlin between active and inactive."""
self.activate_gremlin(not self.runner.is_running())
-
-
def activate_gremlin(self, activate: bool):
"""Sets the activity state of Gremlin.
@@ -91,12 +94,12 @@ def activate_gremlin(self, activate: bool):
if activate:
# Generate the code for the profile and run it
# self._profile_auto_activated = False
- self.runner.start(self.profile,"Default")
-
+ self.runner.start(self.profile, "Default")
+
else:
# Stop running the code
self.runner.stop()
-
+
self.activityChanged.emit()
@Slot(InputIdentifier, result=int)
@@ -115,12 +118,13 @@ def getActionCount(self, identifier: InputIdentifier) -> int:
try:
import gremlin.base_profile
+
item = gremlin.base_profile.InputItem()
item.device_guid = identifier.device_guid
item.input_type = identifier.input_type
item.input_id = identifier.input_id
return len(item.action_configurations)
- except error.ProfileError as e:
+ except error.ProfileError:
return 0
@Slot(InputIdentifier, result=InputItemModel)
@@ -137,10 +141,7 @@ def getInputItem(self, identifier: InputIdentifier) -> InputItemModel:
return
try:
item = self.profile.get_input_item(
- identifier.device_guid,
- identifier.input_type,
- identifier.input_id,
- True
+ identifier.device_guid, identifier.input_type, identifier.input_id, True
)
return InputItemModel(item, self)
except error.ProfileError as e:
@@ -243,10 +244,7 @@ def newInputBinding(self, identifier: InputIdentifier) -> None:
library_item.action_tree = profile_library.ActionTree()
self.profile.library.add_item(library_item)
input_item = self.profile.get_input_item(
- identifier.device_guid,
- identifier.input_type,
- identifier.input_id,
- True
+ identifier.device_guid, identifier.input_type, identifier.input_id, True
)
# TODO: automatically determine the mode
input_item.mode = "Default"
@@ -302,16 +300,14 @@ def _load_profile(self, fpath):
"""
# Check if there exists a file with this path
if not os.path.isfile(fpath):
- self.display_error(
- f"Unable to load profile '{fpath}', no such file."
- )
+ self.display_error(f"Unable to load profile '{fpath}', no such file.")
return
# Disable the program if it is running when we're loading a
# new profile
# TODO: implement this for QML
- #self.ui.actionActivate.setChecked(False)
- #self.activate(False)
+ # self.ui.actionActivate.setChecked(False)
+ # self.activate(False)
# Attempt to load the new profile
try:
@@ -341,15 +337,11 @@ def _load_profile(self, fpath):
except (KeyError, TypeError) as e:
# An error occurred while parsing an existing profile,
# creating an empty profile instead
- syslog.exception(
- f"Invalid profile content:\n{e}"
- )
+ syslog.exception(f"Invalid profile content:\n{e}")
self.newProfile()
except error.ProfileError as e:
# Parsing the profile went wrong, stop loading and start with an
# empty profile
- #cfg = config.Configuration()
+ # cfg = config.Configuration()
self.newProfile()
- self.display_error(
- f"Failed to load the profile {fpath} due to:\n\n{e}"
- )
\ No newline at end of file
+ self.display_error(f"Failed to load the profile {fpath} due to:\n\n{e}")
diff --git a/gremlin/ui/config.py b/gremlin/ui/config.py
index c25df8d2..bc441b9d 100644
--- a/gremlin/ui/config.py
+++ b/gremlin/ui/config.py
@@ -21,7 +21,6 @@
from typing import Any, Dict, Optional
from PySide6 import QtCore, QtQml
-from PySide6.QtCore import Property, Signal, Slot
import gremlin.config
from gremlin.types import PropertyType
@@ -33,7 +32,6 @@
@QtQml.QmlElement
class ConfigSectionModel(QtCore.QAbstractListModel):
-
"""Exposes the sections present in the configuration as a list model."""
roles = {
@@ -41,7 +39,7 @@ class ConfigSectionModel(QtCore.QAbstractListModel):
QtCore.Qt.UserRole + 2: QtCore.QByteArray("groupModel".encode()),
}
- def __init__(self, parent: Optional[QtCore.QObject]=None) -> None:
+ def __init__(self, parent: Optional[QtCore.QObject] = None) -> None:
super().__init__(parent)
self._config = gremlin.config.Configuration()
@@ -69,7 +67,6 @@ def roleNames(self) -> Dict[int, QtCore.QByteArray]:
@QtQml.QmlElement
class ConfigGroupModel(QtCore.QAbstractListModel):
-
"""Exposes the groups present in a specific configuration section as a
list model.
"""
@@ -79,7 +76,7 @@ class ConfigGroupModel(QtCore.QAbstractListModel):
QtCore.Qt.UserRole + 2: QtCore.QByteArray("entryModel".encode()),
}
- def __init__(self, section: str, parent: Optional[QtCore.QObject]=None) -> None:
+ def __init__(self, section: str, parent: Optional[QtCore.QObject] = None) -> None:
super().__init__(parent)
self._config = gremlin.config.Configuration()
@@ -105,7 +102,6 @@ def roleNames(self) -> Dict[int, QtCore.QByteArray]:
@QtQml.QmlElement
class ConfigEntryModel(QtCore.QAbstractListModel):
-
"""Exposes the entries in a section's group as a list model."""
roles = {
@@ -116,10 +112,7 @@ class ConfigEntryModel(QtCore.QAbstractListModel):
}
def __init__(
- self,
- section: str,
- group: str,
- parent: Optional[QtCore.QObject]=None
+ self, section: str, group: str, parent: Optional[QtCore.QObject] = None
) -> None:
super().__init__(parent)
@@ -138,10 +131,7 @@ def data(self, index: QtCore.QModelIndex, role: int) -> Any:
if role in ConfigEntryModel.roles:
role_name = ConfigEntryModel.roles[role].data().decode()
value = self._config.get(
- self._section_name,
- self._group_name,
- entries[index.row()],
- role_name
+ self._section_name, self._group_name, entries[index.row()], role_name
)
if isinstance(value, PropertyType):
value = PropertyType.to_string(value)
@@ -156,13 +146,10 @@ def setData(self, index: QtCore.QModelIndex, value: Any, role: int) -> bool:
role_name = ConfigEntryModel.roles[role].data().decode()
if role_name == "value":
self._config.set(
- self._section_name,
- self._group_name,
- entries[index.row()],
- value
+ self._section_name, self._group_name, entries[index.row()], value
)
- self.dataChanged.emit(index, index, {role});
+ self.dataChanged.emit(index, index, {role})
return True
return False
diff --git a/gremlin/ui/deadzone.py b/gremlin/ui/deadzone.py
index 41a48162..fdaaa652 100644
--- a/gremlin/ui/deadzone.py
+++ b/gremlin/ui/deadzone.py
@@ -1,9 +1,6 @@
-
-
-
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,16 +16,12 @@
# along with this program. If not, see .
from __future__ import annotations
-import os
-from lxml import etree as ElementTree
-from PySide6 import QtWidgets, QtCore, QtGui #QtWebEngineWidgets
+from PySide6 import QtWidgets, QtCore # QtWebEngineWidgets
import gremlin.base_profile
import gremlin.config
-import gremlin.config
import gremlin.event_handler
import gremlin.execution_graph
-from gremlin.input_types import InputType
import gremlin.joystick_handling
import gremlin.shared_state
import gremlin.macro
@@ -43,18 +36,12 @@
from gremlin.types import *
import gremlin.clipboard
-from enum import Enum, auto
from gremlin.macro_handler import *
import gremlin.util
import gremlin.singleton_decorator
-from gremlin.util import InvokeUiMethod
import gremlin.util
-from itertools import pairwise
-from gremlin.ui.ui_common import DynamicDoubleSpinBox, DualSlider, get_text_width
import enum
-from lxml import etree
-
class DeadzonePreset(enum.IntEnum):
@@ -68,25 +55,26 @@ class DeadzonePreset(enum.IntEnum):
reset = 7
@staticmethod
- def to_display(value : DeadzonePreset) -> str:
+ def to_display(value: DeadzonePreset) -> str:
return _deadzone_preset_string_lookup[value]
-_deadzone_preset_string_lookup = {
- DeadzonePreset.center_zero : "Center 0%",
- DeadzonePreset.center_two : "Center 2%",
- DeadzonePreset.center_five : "Center 5%",
- DeadzonePreset.center_ten : "Center 10%",
- DeadzonePreset.end_two : "End 2%",
- DeadzonePreset.end_five : "End 5%",
- DeadzonePreset.end_ten : "End 10%",
- DeadzonePreset.reset : "Reset"
+
+_deadzone_preset_string_lookup = {
+ DeadzonePreset.center_zero: "Center 0%",
+ DeadzonePreset.center_two: "Center 2%",
+ DeadzonePreset.center_five: "Center 5%",
+ DeadzonePreset.center_ten: "Center 10%",
+ DeadzonePreset.end_two: "End 2%",
+ DeadzonePreset.end_five: "End 5%",
+ DeadzonePreset.end_ten: "End 10%",
+ DeadzonePreset.reset: "Reset",
}
+
class DeadzoneWidget(QtWidgets.QWidget):
- ''' deadzone widget '''
+ """deadzone widget"""
- changed = QtCore.Signal() # indicates the data has changed
-
+ changed = QtCore.Signal() # indicates the data has changed
def __init__(self, profile_data, parent=None):
"""Creates a new instance.
@@ -106,7 +94,6 @@ def __init__(self, profile_data, parent=None):
# use a single slider for non-centered axes
self.slider = QSliderWidget()
-
self.slider.desired_height = 20
self.slider.setRange(-1.0, 1.0)
self.slider.setMarkerVisible(False)
@@ -157,26 +144,26 @@ def __init__(self, profile_data, parent=None):
self.right_slider.valueChanged.connect(self._update_right)
self.left_lower.valueChanged.connect(
- lambda value: self._update_from_spinner(value,0)
+ lambda value: self._update_from_spinner(value, 0)
)
self.left_upper.valueChanged.connect(
- lambda value: self._update_from_spinner(value,1)
+ lambda value: self._update_from_spinner(value, 1)
)
self.right_lower.valueChanged.connect(
- lambda value: self._update_from_spinner(value,2)
+ lambda value: self._update_from_spinner(value, 2)
)
self.right_upper.valueChanged.connect(
- lambda value: self._update_from_spinner(value,3)
+ lambda value: self._update_from_spinner(value, 3)
)
-
-
-
self.container_preset_widget = QtWidgets.QWidget()
- self.container_preset_layout = QtWidgets.QHBoxLayout(self.container_preset_widget)
+ self.container_preset_layout = QtWidgets.QHBoxLayout(
+ self.container_preset_widget
+ )
self.container_preset_layout.addWidget(QtWidgets.QLabel("Deadzone"))
from gremlin.curve_handler import DeadzonePreset
+
self._center_presets = []
for preset in DeadzonePreset:
name = DeadzonePreset.to_display(preset)
@@ -205,23 +192,27 @@ def __init__(self, profile_data, parent=None):
self.main_layout.addWidget(widget, row, 0)
widget = gremlin.ui.ui_common.QDataWidget()
- widget.data = DeadzonePreset.center_five # this is so it gets hidden when in slider mode
+ widget.data = (
+ DeadzonePreset.center_five
+ ) # this is so it gets hidden when in slider mode
layout = QtWidgets.QHBoxLayout(widget)
layout.addStretch()
layout.addWidget(QtWidgets.QLabel("Ctr Min:"))
layout.addWidget(self.left_upper)
-
+
self.main_layout.addWidget(widget, row, 1)
self._center_presets.append(widget)
widget = gremlin.ui.ui_common.QDataWidget()
- widget.data = DeadzonePreset.center_five # this is so it gets hidden when in slider mode
+ widget.data = (
+ DeadzonePreset.center_five
+ ) # this is so it gets hidden when in slider mode
layout = QtWidgets.QHBoxLayout(widget)
-
+
layout.addWidget(QtWidgets.QLabel("Ctr Max:"))
layout.addWidget(self.right_lower)
layout.addStretch()
-
+
self.main_layout.addWidget(widget, row, 2)
self._center_presets.append(widget)
widget = gremlin.ui.ui_common.QDataWidget()
@@ -229,19 +220,25 @@ def __init__(self, profile_data, parent=None):
layout.addStretch()
layout.addWidget(QtWidgets.QLabel("Max:"))
layout.addWidget(self.right_upper)
-
+
self.main_layout.addWidget(widget, row, 3)
self._update()
def _is_center_preset(self, preset):
- ''' true if a center preset '''
- return preset in (DeadzonePreset.center_zero, DeadzonePreset.center_two, DeadzonePreset.center_five, DeadzonePreset.center_ten)
+ """true if a center preset"""
+ return preset in (
+ DeadzonePreset.center_zero,
+ DeadzonePreset.center_two,
+ DeadzonePreset.center_five,
+ DeadzonePreset.center_ten,
+ )
- @QtCore.Slot()
+ @QtCore.Slot()
def _deadzone_preset_cb(self):
- ''' handles deadzone presets '''
+ """handles deadzone presets"""
from gremlin.curve_handler import DeadzonePreset
+
widget = self.sender()
preset = widget.data
@@ -254,31 +251,31 @@ def _deadzone_preset_cb(self):
d_left = 0
if d_right is None:
d_right = 0
-
+
match preset:
case DeadzonePreset.center_zero:
d_left = 0.0
d_right = 0.0
- case DeadzonePreset.center_two :
+ case DeadzonePreset.center_two:
d_left = -0.02 * 2
d_right = 0.02 * 2
- case DeadzonePreset.center_five :
+ case DeadzonePreset.center_five:
d_left = -0.05 * 2
d_right = 0.05 * 2
- case DeadzonePreset.center_ten :
+ case DeadzonePreset.center_ten:
d_left = -0.1 * 2
d_right = 0.1 * 2
- case DeadzonePreset.end_two :
+ case DeadzonePreset.end_two:
d_start = -1 + 0.02 * 2
d_end = 1 - 0.02 * 2
- case DeadzonePreset.end_five :
+ case DeadzonePreset.end_five:
d_start = -1 + 0.05 * 2
d_end = 1 - 0.05 * 2
- case DeadzonePreset.end_ten :
+ case DeadzonePreset.end_ten:
d_start = -1 + 0.1 * 2
d_end = 1 - 0.1 * 2
- case DeadzonePreset.reset :
+ case DeadzonePreset.reset:
d_start = -1
d_left = 0
d_right = 0
@@ -286,17 +283,17 @@ def _deadzone_preset_cb(self):
self._update_deadzone([d_start, d_left, d_right, d_end])
-
@property
def isCentered(self) -> bool:
return self._centered
+
@isCentered.setter
def isCentered(self, value: bool):
if value != self._centered:
self._centered = value
self._update()
- def setValues(self, values, emit = False):
+ def setValues(self, values, emit=False):
"""Sets the deadzone values.
:param values the new deadzone values [min, min center, max center, max]
@@ -304,35 +301,33 @@ def setValues(self, values, emit = False):
if len(values) == 2:
# has enpoints only
- v1,v4 = values
+ v1, v4 = values
v2 = v3 = 0.0
else:
# has enpoints and centering
- v1,v2,v3,v4 = values
+ v1, v2, v3, v4 = values
with QtCore.QSignalBlocker(self.left_slider):
- self.left_slider.setValue((v1,v2))
+ self.left_slider.setValue((v1, v2))
# print (f"left slider: {v1} {v2} values: {self.left_slider.values}")
with QtCore.QSignalBlocker(self.left_lower):
self.left_lower.setValue(v1)
- with QtCore.QSignalBlocker(self.left_upper):
+ with QtCore.QSignalBlocker(self.left_upper):
self.left_upper.setValue(v2)
with QtCore.QSignalBlocker(self.right_slider):
- self.right_slider.setValue((v3,v4))
+ self.right_slider.setValue((v3, v4))
with QtCore.QSignalBlocker(self.right_lower):
self.right_lower.setValue(v3)
with QtCore.QSignalBlocker(self.right_upper):
self.right_upper.setValue(v4)
with QtCore.QSignalBlocker(self.slider):
- self.slider.setValue((v1,v4))
+ self.slider.setValue((v1, v4))
self._update()
-
if emit:
self.changed.emit()
-
def values(self):
"""Returns the current deadzone values.
@@ -342,23 +337,23 @@ def values(self):
self.left_lower.value(),
self.left_upper.value(),
self.right_lower.value(),
- self.right_upper.value()
- ]
-
-
+ self.right_upper.value(),
+ ]
+
def get_min(self) -> float:
return self.left_lower.value()
def get_max(self) -> float:
return self.right_upper.value()
-
+
def get_center_left(self) -> float:
return self.left_upper.value()
+
def get_center_right(self) -> float:
return self.right_lower.value()
-
+
def _update_center(self, handle, value):
- ''' updates the main slider when in non centered mode'''
+ """updates the main slider when in non centered mode"""
if not self.event_lock:
self.event_lock = True
if handle == 0:
@@ -407,8 +402,6 @@ def _update_right(self, handle, value):
self.changed.emit()
self.event_lock = False
-
-
def _update_from_spinner(self, value, index):
"""Updates the slider position.
@@ -426,20 +419,14 @@ def _update_from_spinner(self, value, index):
self.setValues(values)
# print (f"index {index} value: {value} Values: {values} left range: {self.left_slider.values} {self.left_slider.range()} right range: {self.right_slider.values} {self.right_slider.range()}")
-
-
-
-
- def _update_deadzone(self, data : list):
- ''' updates the deadzone text values '''
+ def _update_deadzone(self, data: list):
+ """updates the deadzone text values"""
if len(data) == 2:
v1, v4 = data
data = [v1, 0.0, 0.0, v4]
self.setValues(data)
self.profile_data.deadzone = data
- self.changed.emit() # notify we changed
-
-
+ self.changed.emit() # notify we changed
def _update(self):
is_centered = self._centered
@@ -460,4 +447,3 @@ def _update(self):
preset = button.data
if self._is_center_preset(preset):
button.setVisible(is_centered)
-
diff --git a/gremlin/ui/device.py b/gremlin/ui/device.py
index feb2c09a..c38855ad 100644
--- a/gremlin/ui/device.py
+++ b/gremlin/ui/device.py
@@ -54,7 +54,6 @@
@QtQml.QmlElement
class DeviceListModel(QtCore.QAbstractListModel):
-
"""Model containing absic information about all connected devices."""
roles = {
@@ -83,13 +82,10 @@ def __init__(self, parent=None):
super().__init__(parent)
self._devices = joystick_handling.joystick_devices()
- event_handler.EventListener().device_change_event.connect(
- self.update_model
- )
+ event_handler.EventListener().device_change_event.connect(self.update_model)
def update_model(self) -> None:
"""Updates the model if the connected devices change."""
- old_count = len(self._devices)
self._devices = joystick_handling.joystick_devices()
new_count = len(self._devices)
@@ -103,15 +99,13 @@ def update_model(self) -> None:
# list(DeviceData.roles.keys())
# )
- def rowCount(self, parent:QtCore.QModelIndex=...) -> int:
+ def rowCount(self, parent: QtCore.QModelIndex = ...) -> int:
return len(self._devices)
- def data(self, index:QtCore.QModelIndex, role:int=...) -> typing.Any:
+ def data(self, index: QtCore.QModelIndex, role: int = ...) -> typing.Any:
if role in DeviceListModel.roles:
role_name = DeviceListModel.roles[role].data().decode()
- return DeviceListModel.role_query[role_name](
- self._devices[index.row()]
- )
+ return DeviceListModel.role_query[role_name](self._devices[index.row()])
else:
return "Unknown"
@@ -120,7 +114,7 @@ def roleNames(self) -> typing.Dict:
@Slot(int, result=str)
def guidAtIndex(self, index: int) -> str:
- if not(0 <= index < len(self._devices)):
+ if not (0 <= index < len(self._devices)):
raise error.GremlinError("Provided index out of range")
return str(self._devices[index].device_guid)
@@ -128,7 +122,6 @@ def guidAtIndex(self, index: int) -> str:
@QtQml.QmlElement
class Device(QtCore.QAbstractListModel):
-
"""Model providing access to information about a single device."""
roles = {
@@ -153,21 +146,19 @@ def _set_guid(self, guid: str) -> None:
if self._device is not None and guid == str(self._device.device_guid):
return
- self._device = dinput.DILL.get_device_information_by_guid(
- parse_guid(guid)
- )
+ self._device = dinput.DILL.get_device_information_by_guid(parse_guid(guid))
self.deviceChanged.emit()
self.layoutChanged.emit()
- def rowCount(self, parent:QtCore.QModelIndex=...) -> int:
+ def rowCount(self, parent: QtCore.QModelIndex = ...) -> int:
if self._device is None:
return 0
- return self._device.axis_count + \
- self._device.button_count + \
- self._device.hat_count
+ return (
+ self._device.axis_count + self._device.button_count + self._device.hat_count
+ )
- def data(self, index: QtCore.QModelIndex, role:int=...) -> typing.Any:
+ def data(self, index: QtCore.QModelIndex, role: int = ...) -> typing.Any:
if role not in Device.roles:
return "Unknown"
@@ -196,6 +187,7 @@ def inputIdentifier(self, index: int):
the given index.
"""
import gremlin.ui.input_item
+
identifier = gremlin.ui.input_item.InputIdentifier(self)
identifier.device_guid = self._device.device_guid
input_info = self._convert_index(index)
@@ -210,37 +202,22 @@ def _name(self, identifier: typing.Tuple[InputType, int]) -> str:
def _convert_index(self, index: int) -> typing.Tuple[InputType, int]:
axis_count = self._device.axis_count
button_count = self._device.button_count
- hat_count = self._device.hat_count
if index < axis_count:
- return (
- InputType.JoystickAxis,
- self._device.axis_map[index].axis_index
- )
+ return (InputType.JoystickAxis, self._device.axis_map[index].axis_index)
elif index < axis_count + button_count:
- return (
- InputType.JoystickButton,
- index + 1 - axis_count
- )
+ return (InputType.JoystickButton, index + 1 - axis_count)
else:
- return (
- InputType.JoystickHat,
- index + 1 - axis_count - button_count
- )
+ return (InputType.JoystickHat, index + 1 - axis_count - button_count)
def roleNames(self) -> typing.Dict:
return Device.roles
- guid = Property(
- str,
- fget=_get_guid,
- fset=_set_guid
- )
+ guid = Property(str, fget=_get_guid, fset=_set_guid)
@QtQml.QmlElement
class VJoyDevices(QtCore.QObject):
-
"""vJoy model used together with the VJoySelector QML.
The model provides setters and getters for UI selection index values while
@@ -262,15 +239,14 @@ def __init__(self, parent=None):
super().__init__(parent)
self._devices = sorted(
- joystick_handling.vjoy_devices(),
- key=lambda x: x.vjoy_id
+ joystick_handling.vjoy_devices(), key=lambda x: x.vjoy_id
)
# Information used to determine what to show in the UI
self._valid_types = [
InputType.JoystickAxis,
InputType.JoystickButton,
- InputType.JoystickHat
+ InputType.JoystickHat,
]
self._input_items = []
self._input_data = []
@@ -297,9 +273,11 @@ def _is_state_valid(self) -> bool:
Returns:
True if the state is valid and consistent, False otherwise
"""
- return self._current_vjoy_index is not None and \
- self._current_input_index is not None and \
- self._current_input_type is not None
+ return (
+ self._current_vjoy_index is not None
+ and self._current_input_index is not None
+ and self._current_input_type is not None
+ )
@Slot(int, int, str)
def setSelection(self, vjoy_id: int, input_id: int, input_type: str) -> None:
@@ -318,19 +296,14 @@ def setSelection(self, vjoy_id: int, input_id: int, input_type: str) -> None:
self._set_vjoy_index(i)
if vjoy_index == -1:
- raise error.GremlinError(
- f"Could not find vJoy device with id {vjoy_id}"
- )
+ raise error.GremlinError(f"Could not find vJoy device with id {vjoy_id}")
# Find the index corresponding to the provided input_type and input_id
- input_label = common.input_to_ui_string(
- InputType.to_enum(input_type),
- input_id
- )
+ input_label = common.input_to_ui_string(InputType.to_enum(input_type), input_id)
try:
self._set_input_index(self._input_items.index(input_label))
except ValueError:
- raise error.GremlinError(f"No input named \"{input_label}\" present")
+ raise error.GremlinError(f'No input named "{input_label}" present')
@Property(type="QVariantList", notify=deviceModelChanged)
def deviceModel(self):
@@ -341,7 +314,7 @@ def inputModel(self):
input_count = {
InputType.JoystickAxis: lambda x: x.axis_count,
InputType.JoystickButton: lambda x: x.button_count,
- InputType.JoystickHat: lambda x: x.hat_count
+ InputType.JoystickHat: lambda x: x.hat_count,
}
self._input_items = []
@@ -350,14 +323,13 @@ def inputModel(self):
# Add items based on the input type
for input_type in self._valid_types:
for i in range(input_count[input_type](device)):
- input_id = i+1
+ input_id = i + 1
if input_type == InputType.JoystickAxis:
input_id = device.axis_map[i].axis_index
- self._input_items.append(common.input_to_ui_string(
- input_type,
- input_id
- ))
+ self._input_items.append(
+ common.input_to_ui_string(input_type, input_id)
+ )
self._input_data.append((input_type, input_id))
return self._input_items
@@ -381,15 +353,10 @@ def _set_valid_types(self, valid_types: typing.List[str]) -> None:
self.inputModel
input_label = common.input_to_ui_string(
- InputType.to_enum(old_input_type),
- old_vjoy_id
+ InputType.to_enum(old_input_type), old_vjoy_id
)
if input_label in self._input_items:
- self.setSelection(
- self._get_vjoy_id(),
- old_vjoy_id,
- old_input_type
- )
+ self.setSelection(self._get_vjoy_id(), old_vjoy_id, old_input_type)
else:
self._current_vjoy_index = 0
self._current_input_index = 0
@@ -456,44 +423,25 @@ def _get_input_type(self) -> str:
"QVariantList",
fget=_get_valid_types,
fset=_set_valid_types,
- notify=validTypesChanged
+ notify=validTypesChanged,
)
- vjoyId = Property(
- int,
- fget=_get_vjoy_id,
- notify=vjoyIdChanged
- )
+ vjoyId = Property(int, fget=_get_vjoy_id, notify=vjoyIdChanged)
vjoyIndex = Property(
- int,
- fget=_get_vjoy_index,
- fset=_set_vjoy_index,
- notify=vjoyIndexChanged
+ int, fget=_get_vjoy_index, fset=_set_vjoy_index, notify=vjoyIndexChanged
)
- inputId = Property(
- int,
- fget=_get_input_id,
- notify=inputIdChanged
- )
+ inputId = Property(int, fget=_get_input_id, notify=inputIdChanged)
inputIndex = Property(
- int,
- fget=_get_input_index,
- fset=_set_input_index,
- notify=inputIndexChanged
+ int, fget=_get_input_index, fset=_set_input_index, notify=inputIndexChanged
)
- inputType = Property(
- str,
- fget=_get_input_type,
- notify=inputTypeChanged
- )
+ inputType = Property(str, fget=_get_input_type, notify=inputTypeChanged)
class AbstractDeviceState(QtCore.QAbstractListModel):
-
deviceChanged = Signal()
roles = {
@@ -535,13 +483,13 @@ def _initilize_state(self) -> None:
"AbstractDeviceState._initialize_state not implemented"
)
- def rowCount(self, parent:QtCore.QModelIndex=...) -> int:
+ def rowCount(self, parent: QtCore.QModelIndex = ...) -> int:
if self._device is None:
return 0
return len(self._state)
- def data(self, index: QtCore.QModelIndex, role:int=...) -> typing.Any:
+ def data(self, index: QtCore.QModelIndex, role: int = ...) -> typing.Any:
if role not in AbstractDeviceState.roles:
return False
@@ -551,16 +499,11 @@ def data(self, index: QtCore.QModelIndex, role:int=...) -> typing.Any:
def roleNames(self) -> typing.Dict:
return DeviceButtonState.roles
- guid = Property(
- str,
- fset=_set_guid,
- notify=deviceChanged
- )
+ guid = Property(str, fset=_set_guid, notify=deviceChanged)
@QtQml.QmlElement
class DeviceAxisState(AbstractDeviceState):
-
def __init__(self, parent=None):
super().__init__(parent)
@@ -575,41 +518,35 @@ def _event_handler_impl(self, event: event_handler.Event) -> None:
def _initialize_state(self) -> None:
for i in range(self._device.axis_count):
self._identifier_map[self._device.axis_map[i].axis_index] = i
- self._state.append({
- "identifier": self._device.axis_map[i].axis_index,
- "value": 0.0
- })
+ self._state.append(
+ {"identifier": self._device.axis_map[i].axis_index, "value": 0.0}
+ )
@QtQml.QmlElement
class DeviceButtonState(AbstractDeviceState):
-
def __init__(self, parent=None):
super().__init__(parent)
def _event_handler_impl(self, event):
if event.event_type == InputType.JoystickButton:
- idx = event.identifier-1
+ idx = event.identifier - 1
self._state[idx]["value"] = event.is_pressed
self.dataChanged.emit(self.index(idx, 0), self.index(idx, 0))
def _initialize_state(self) -> None:
for i in range(self._device.button_count):
- self._state.append({
- "identifier": i+1,
- "value": False
- })
+ self._state.append({"identifier": i + 1, "value": False})
@QtQml.QmlElement
class DeviceHatState(AbstractDeviceState):
-
def __init__(self, parent=None):
super().__init__(parent)
def _event_handler_impl(self, event):
if event.event_type == InputType.JoystickHat:
- idx = event.identifier-1
+ idx = event.identifier - 1
pt = QtCore.QPoint(event.value[0], event.value[1])
if pt != self._state[idx]["value"]:
self._state[idx]["value"] = pt
@@ -617,15 +554,11 @@ def _event_handler_impl(self, event):
def _initialize_state(self) -> None:
for i in range(self._device.hat_count):
- self._state.append({
- "identifier": i+1,
- "value": QtCore.QPoint(0, 0)
- })
+ self._state.append({"identifier": i + 1, "value": QtCore.QPoint(0, 0)})
@QtQml.QmlElement
class DeviceAxisSeries(QtCore.QObject):
-
windowSizeChanged = Signal()
deviceChanged = Signal()
axisCountChanged = Signal()
@@ -650,10 +583,9 @@ def _set_guid(self, guid: str) -> None:
self._state = []
for i in range(self._device.axis_count):
self._identifier_map[self._device.axis_map[i].axis_index] = i
- self._state.append({
- "identifier": self._device.axis_map[i].axis_index,
- "timeSeries": []
- })
+ self._state.append(
+ {"identifier": self._device.axis_map[i].axis_index, "timeSeries": []}
+ )
self.deviceChanged.emit()
def _get_window_size(self) -> int:
@@ -670,9 +602,7 @@ def _event_callback(self, event: event_handler.Event):
if event.event_type == InputType.JoystickAxis:
index = self._identifier_map[event.identifier]
- self._state[index]["timeSeries"].append(
- (time.time(), event.value)
- )
+ self._state[index]["timeSeries"].append((time.time(), event.value))
@Property(int, notify=axisCountChanged)
def axisCount(self) -> int:
@@ -683,13 +613,15 @@ def updateSeries(self, series: QtCharts.QLineSeries, identifier: int):
data = self._state[identifier]["timeSeries"]
if len(data) == 0:
- series.replace([
- QtCore.QPointF(0.0, 0.0),
- QtCore.QPointF(self._window_size, 0.0),
- ])
+ series.replace(
+ [
+ QtCore.QPointF(0.0, 0.0),
+ QtCore.QPointF(self._window_size, 0.0),
+ ]
+ )
return
- now = time.time()
+ now = time.time()
while now - data[0][0] > self._window_size:
data.pop(0)
@@ -703,15 +635,8 @@ def updateSeries(self, series: QtCharts.QLineSeries, identifier: int):
def axisIdentifier(self, index: int) -> int:
return self._state[index]["identifier"]
- guid = Property(
- str,
- fset=_set_guid,
- notify=deviceChanged
- )
+ guid = Property(str, fset=_set_guid, notify=deviceChanged)
windowSize = Property(
- int,
- fset=_set_window_size,
- fget=_get_window_size,
- notify=windowSizeChanged
- )
\ No newline at end of file
+ int, fset=_set_window_size, fget=_get_window_size, notify=windowSizeChanged
+ )
diff --git a/gremlin/ui/dialogs.py b/gremlin/ui/dialogs.py
index 62d74597..0e7de4ce 100644
--- a/gremlin/ui/dialogs.py
+++ b/gremlin/ui/dialogs.py
@@ -27,7 +27,6 @@
import gremlin
from PySide6.QtGui import QIcon as load_icon
-from PySide6.QtWidgets import QMessageBox
from gremlin.clipboard import Clipboard
import gremlin.config
import gremlin.event_handler
@@ -42,13 +41,11 @@
import gremlin.ui.ui_about as ui_about
import gremlin.ui.ui_common as ui_common
-from gremlin.util import load_icon, userprofile_path, load_pixmap, pushCursor, popCursor
+from gremlin.util import load_icon, load_pixmap, pushCursor, popCursor
import logging
from gremlin.input_types import InputType
import gremlin.base_profile
-import uuid
from lxml import etree
-import dinput
import gremlin.util
syslog = logging.getLogger("system")
@@ -134,7 +131,7 @@ def populate_selector(self, mode = None):
start_mode = mode if mode else self.profile.get_start_mode()
default_mode = self.profile.get_default_mode()
- if not start_mode in self.mode_list:
+ if start_mode not in self.mode_list:
# the start mode no longer exists - use the default mode
syslog.warning(f"Specified start mode {start_mode} no longer exists - using default mode {default_mode}")
default_mode = self.profile.get_default_mode()
@@ -1030,7 +1027,7 @@ def _create_osc_page(self):
row = 0
col = 0
- layout.addWidget(QtWidgets.QLabel(f"Local OSC Server:"), row, col)
+ layout.addWidget(QtWidgets.QLabel("Local OSC Server:"), row, col)
col+=1
layout.addWidget(local_host_ip_widget, row, col)
col+=1
@@ -1944,7 +1941,7 @@ def __init__(self, text = None, parent=None):
self._data = None
if text and os.path.isfile(text):
- if not text in process_list:
+ if text not in process_list:
# selected item is not in the running process list
self._browse(text)
@@ -2313,7 +2310,6 @@ def _create_ui(self):
widget, layout = gremlin.ui.ui_common.getHContainer(close_button_widget, left_stretch=True)
- button_container_layout = layout
button_container_widget = widget
@@ -2326,7 +2322,6 @@ def _create_ui(self):
options_container_widget = widget
- options_container_layout = layout
# Add header information
@@ -2502,7 +2497,7 @@ def _profile_modes(self) -> dict:
mode_map = {}
for device in self._profile.devices:
for mode in self._profile.devices[device].modes:
- if not mode in mode_map:
+ if mode not in mode_map:
mode_map[mode] = self._profile.devices[device].modes[mode]
return mode_map
@@ -2792,7 +2787,7 @@ def _copy_to_script(self):
s_list.append(f"{var_name}_GUID = \"{entry.device_guid}\"")
for mode_name in mode_list:
mode_suffix = mode_name.replace(" ","_")
- if not mode_name in a_map.keys():
+ if mode_name not in a_map.keys():
a_map[mode_name] = set()
a_map[mode_name].add(f"{var_name}_{mode_suffix} = gremlin.input_devices.JoystickDecorator({var_name}_NAME, {var_name}_GUID, \"{mode_name}\")")
@@ -3132,7 +3127,7 @@ def _replace_cb(self):
parser = etree.XMLParser(remove_blank_text=True)
root = etree.parse(xml_file, parser)
- nodes = root.xpath(f'//device') # iterate through all because we need to compare case for guid
+ nodes = root.xpath('//device') # iterate through all because we need to compare case for guid
for node in nodes:
tmp_guid = node.get("device-guid")
if tmp_guid.casefold() == current_guid.casefold():
diff --git a/gremlin/ui/input_item.py b/gremlin/ui/input_item.py
index e4de7247..8eef30e4 100644
--- a/gremlin/ui/input_item.py
+++ b/gremlin/ui/input_item.py
@@ -228,7 +228,7 @@ def data(self, index):
:return data stored at the provided index
"""
- if not index in self._index_map.keys():
+ if index not in self._index_map.keys():
# bad index
#syslog.error(f"InputItemListModel: bad index request {index} for mode: {self._mode} device: {self._device_data.name}")
return None
@@ -242,7 +242,7 @@ def removeRow(self, index):
data = self.data(index)
if data:
input_type = data.input_type
- if not input_type in (InputType.Keyboard, InputType.KeyboardLatched, InputType.OpenSoundControl, InputType.Midi):
+ if input_type not in (InputType.Keyboard, InputType.KeyboardLatched, InputType.OpenSoundControl, InputType.Midi):
# cannot remove other types
return False
@@ -483,7 +483,6 @@ def redraw(self):
row_count = self.model.rows()
device_name = self.current_device.name
- selected_index = - 1 # nothing selected
# remember the index of the item that was previously selected
selected_input_id = None
@@ -535,7 +534,7 @@ def redraw(self):
widget.index = index # assigned index
if selected:
# remember which item to select
- selected_index = index
+ pass
widget.edit.connect(self._create_edit_callback(index))
widget.edit_curve.connect(self._create_edit_curve_callback(index))
@@ -654,7 +653,6 @@ def selected_item(self):
def _close_item_cb(self, index):
''' remove a particular input '''
- from PySide6.QtCore import QMetaMethod
widget = self.itemAt(index)
if isSignalConnected(widget,"closed(InputIdentifier)"):
@@ -2013,7 +2011,7 @@ def _clipboard_changed(self, clipboard):
if clipboard.is_container:
self.paste_button.setToolTip(f"Paste container ({clipboard.data.name})")
else:
- self.paste_button.setToolTip(f"Paste container (not available)")
+ self.paste_button.setToolTip("Paste container (not available)")
@QtCore.Slot()
def _paste_container(self):
@@ -2068,11 +2066,11 @@ def register(self, input_item, container, dock_tab : QtWidgets.QTabWidget):
device_guid = input_item.device_guid
mode = gremlin.shared_state.current_mode
input_id = input_item.input_id
- if not device_guid in self._cache:
+ if device_guid not in self._cache:
self._cache[device_guid] = {}
- if not mode in self._cache[device_guid]:
+ if mode not in self._cache[device_guid]:
self._cache[device_guid][mode] = {}
- if not input_id in self._cache[device_guid][mode]:
+ if input_id not in self._cache[device_guid][mode]:
self._cache[device_guid][mode][input_id] = {}
info = ConditionTrackerInfo(input_item, device_guid, input_id, container, dock_tab)
self._cache[device_guid][mode][input_id][container.id] = info
@@ -2439,7 +2437,7 @@ def _update_counts(self):
self.activation_count_widget.setText(f"Container conditions ({self.container.condition_count} found):")
else:
# not a container
- self.activation_count_widget.setText(f"Container conditions (N/A):")
+ self.activation_count_widget.setText("Container conditions (N/A):")
@@ -2488,7 +2486,7 @@ def _tab_changed(self, index):
verbose = config.verbose_mode_device
verbose_detailed = config.verbose_mode_details
try:
- if verbose: syslog.info(f"Device change begin")
+ if verbose: syslog.info("Device change begin")
tab_text = self.dock_tabs.tabText(index)
self.profile_data.current_view_type = ui_common.ContainerViewTypes.to_enum(tab_text.lower())
self._update_selected(index)
@@ -2499,7 +2497,7 @@ def _tab_changed(self, index):
return
finally:
if verbose_detailed:
- syslog.info(f"Device change end")
+ syslog.info("Device change end")
diff --git a/gremlin/ui/input_viewer.py b/gremlin/ui/input_viewer.py
index c875cf21..75e5b990 100644
--- a/gremlin/ui/input_viewer.py
+++ b/gremlin/ui/input_viewer.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -15,12 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import copy
-import enum
-import time
-from PySide6 import QtCore, QtGui, QtWidgets
-import lxml.etree
+from PySide6 import QtCore, QtWidgets
import dinput
import gremlin
@@ -32,25 +28,25 @@
import gremlin.ui.virtual_keyboard
import gremlin.util
from . import ui_common
-from gremlin.input_types import InputType
from gremlin.types import VisualizationType
import os
from lxml import etree
import gremlin.singleton_decorator
import logging
import gremlin.ui.ui_common
-from vigem import vigem_gamepad as vg
syslog = logging.getLogger("system")
+
@gremlin.singleton_decorator.SingletonDecorator
-class VisualizationConfig():
- ''' stores data '''
+class VisualizationConfig:
+ """stores data"""
+
def __init__(self):
self._config = {} # map of device guid, input_type, input_id - selected flag
self.reload()
- def getValue(self, device_guid, input_type : VisualizationType) -> bool:
+ def getValue(self, device_guid, input_type: VisualizationType) -> bool:
if not isinstance(device_guid, str):
device_guid = str(device_guid)
if device_guid in self._config:
@@ -58,23 +54,22 @@ def getValue(self, device_guid, input_type : VisualizationType) -> bool:
if id in self._config[device_guid]:
return self._config[device_guid][id]
return False
-
- def setValue(self, device_guid, input_type : VisualizationType, value):
+
+ def setValue(self, device_guid, input_type: VisualizationType, value):
if not isinstance(device_guid, str):
device_guid = str(device_guid)
- if not device_guid in self._config:
+ if device_guid not in self._config:
self._config[device_guid] = {}
id = int(input_type)
self._config[device_guid][id] = value
-
def save(self):
- ''' saves to the config file '''
+ """saves to the config file"""
fname = self.get_config()
syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose
- if verbose:
+ if verbose:
syslog.info("INPUT VIEWER: save configuration")
root = etree.Element("config")
@@ -83,26 +78,28 @@ def save(self):
value = self._config[device_guid][id]
node = etree.Element("data")
node.set("device-guid", device_guid)
- node.set("id",str(id)) # visualization type
+ node.set("id", str(id)) # visualization type
node.set("value", str(value))
root.append(node)
- device_name = gremlin.joystick_handling.device_name_from_guid(device_guid)
+ device_name = gremlin.joystick_handling.device_name_from_guid(
+ device_guid
+ )
input_type = VisualizationType(id).name
- node_comment = etree.Comment(f"{device_name} {device_guid} type: {input_type}")
+ node_comment = etree.Comment(
+ f"{device_name} {device_guid} type: {input_type}"
+ )
node.addprevious(node_comment)
-
try:
tree = etree.ElementTree(root)
- tree.write(fname, pretty_print=True,xml_declaration=True,encoding="utf-8")
+ tree.write(fname, pretty_print=True, xml_declaration=True, encoding="utf-8")
except:
pass
def clear(self):
- ''' clears config selection '''
+ """clears config selection"""
self._config.clear()
-
def reload(self):
fname = self.get_config()
load_successful = False
@@ -122,20 +119,17 @@ def reload(self):
if not load_successful:
self._config = {}
-
-
def get_config(sef):
fname = os.path.join(gremlin.util.userprofile_path(), "inputViewer.xml")
return fname
-
-
-class VisualizationSelector(QtWidgets.QWidget):
+
+class VisualizationSelector(QtWidgets.QWidget):
"""Presents a list of devices and visualization widgets."""
# Event emitted when the visualization configuration changes
- changed = QtCore.Signal(dinput.DeviceSummary,VisualizationType,bool)
- clear = QtCore.Signal() # delete all
+ changed = QtCore.Signal(dinput.DeviceSummary, VisualizationType, bool)
+ clear = QtCore.Signal() # delete all
def __init__(self, change_callback, parent=None):
"""Creates a new instance.
@@ -149,7 +143,6 @@ def __init__(self, change_callback, parent=None):
self._selector_widgets = []
self._selector_callbacks = {}
-
# get the order of the devices as set by the user for the physical devices
tab_map = gremlin.shared_state.ui._get_tab_map()
tab_ids = [device_id for device_id, _, _, _ in tab_map.values()]
@@ -163,42 +156,42 @@ def __init__(self, change_callback, parent=None):
# add to the end (vjoy devices)
d_list.append((max_index, dev))
-
d_list.sort(key=lambda x: (x[0], x[1].vjoy_id, x[1].name))
devices = [dev for _, dev in d_list]
-
config = VisualizationConfig()
self.main_layout = QtWidgets.QVBoxLayout(self)
- self.main_layout.setContentsMargins(0,0,0,0)
-
+ self.main_layout.setContentsMargins(0, 0, 0, 0)
- dev : dinput.DeviceSummary
+ dev: dinput.DeviceSummary
for dev in devices:
-
box = QtWidgets.QGroupBox(dev.name)
- at_cb = gremlin.ui.ui_common.QDataCheckbox("Axes - Temporal", data = (VisualizationType.AxisTemporal, dev))
+ at_cb = gremlin.ui.ui_common.QDataCheckbox(
+ "Axes - Temporal", data=(VisualizationType.AxisTemporal, dev)
+ )
at_cb.setIgnoreKeyboard(True)
callback = self._create_callback(dev, VisualizationType.AxisTemporal, at_cb)
at_cb.clicked.connect(callback)
self._selector_callbacks[at_cb] = callback
- ac_cb = gremlin.ui.ui_common.QDataCheckbox("Axes - Current", data = (VisualizationType.AxisCurrent, dev))
+ ac_cb = gremlin.ui.ui_common.QDataCheckbox(
+ "Axes - Current", data=(VisualizationType.AxisCurrent, dev)
+ )
ac_cb.setIgnoreKeyboard(True)
callback = self._create_callback(dev, VisualizationType.AxisCurrent, ac_cb)
ac_cb.clicked.connect(callback)
self._selector_callbacks[ac_cb] = callback
- bh_cb = gremlin.ui.ui_common.QDataCheckbox("Buttons + Hats", data = (VisualizationType.ButtonHat, dev))
+ bh_cb = gremlin.ui.ui_common.QDataCheckbox(
+ "Buttons + Hats", data=(VisualizationType.ButtonHat, dev)
+ )
bh_cb.setIgnoreKeyboard(True)
callback = self._create_callback(dev, VisualizationType.ButtonHat, bh_cb)
bh_cb.clicked.connect(callback)
self._selector_callbacks[bh_cb] = callback
-
-
layout = QtWidgets.QVBoxLayout()
layout.addWidget(at_cb)
layout.addWidget(ac_cb)
@@ -216,20 +209,22 @@ def __init__(self, change_callback, parent=None):
device_guid = dev.device_guid
checked = config.getValue(device_guid, VisualizationType.AxisTemporal)
at_cb.setChecked(checked)
- if checked: change_callback(dev, VisualizationType.AxisTemporal,True)
+ if checked:
+ change_callback(dev, VisualizationType.AxisTemporal, True)
checked = config.getValue(device_guid, VisualizationType.AxisCurrent)
ac_cb.setChecked(checked)
- if checked: change_callback(dev, VisualizationType.AxisCurrent, True)
+ if checked:
+ change_callback(dev, VisualizationType.AxisCurrent, True)
checked = config.getValue(device_guid, VisualizationType.ButtonHat)
bh_cb.setChecked(checked)
- if checked: change_callback(dev, VisualizationType.ButtonHat, True)
-
+ if checked:
+ change_callback(dev, VisualizationType.ButtonHat, True)
@QtCore.Slot()
def _clear_selection(self):
- ''' clears the selection of all widgets '''
+ """clears the selection of all widgets"""
self.clear.emit()
for widget in self._selector_widgets:
with QtCore.QSignalBlocker(widget):
@@ -237,7 +232,7 @@ def _clear_selection(self):
@QtCore.Slot()
def _select_real(self):
- ''' selects all hardware inputs '''
+ """selects all hardware inputs"""
for widget in self._selector_widgets:
visualization, dev = widget.data
if visualization != VisualizationType.AxisTemporal and not dev.is_virtual:
@@ -247,23 +242,15 @@ def _select_real(self):
widget.setChecked(False)
self._create_callback(dev, visualization, widget)()
-
-
-
@QtCore.Slot()
def _select_all(self):
- ''' selects all widgets '''
-
+ """selects all widgets"""
+
for widget in self._selector_widgets:
with QtCore.QSignalBlocker(widget):
widget.setChecked(True)
visualisation, dev = widget.data
self._create_callback(dev, visualisation, widget)()
-
-
-
-
-
def _create_callback(self, device, vis_type, cb):
"""Creates the callback to trigger visualization updates.
@@ -271,17 +258,16 @@ def _create_callback(self, device, vis_type, cb):
:param device the device being updated
:param vis_type visualization type being updated
"""
- return lambda : self._callback(device,vis_type,cb)
-
- def _callback(self, device, vis_type, cb):
+ return lambda: self._callback(device, vis_type, cb)
+ def _callback(self, device, vis_type, cb):
checked = cb.isChecked()
config = VisualizationConfig()
config.setValue(device.device_guid, vis_type, checked)
- self.changed.emit(device,vis_type,checked)
+ self.changed.emit(device, vis_type, checked)
-class InputViewerUi(ui_common.BaseDialogUi):
+class InputViewerUi(ui_common.BaseDialogUi):
"""Main UI dialog for the input viewer."""
def __init__(self, parent=None):
@@ -304,9 +290,8 @@ def __init__(self, parent=None):
self.main_layout = QtWidgets.QVBoxLayout(self)
self.keyboard_visualizer_widget = None
- self.keyboard_widget = None # keyboard widget
+ self.keyboard_widget = None # keyboard widget
-
widget, layout = gremlin.ui.ui_common.getVContainer()
self.view_container_widget = widget
self.view_container_layout = layout
@@ -314,7 +299,6 @@ def __init__(self, parent=None):
self.view_container_layout.addWidget(self.views)
-
# configure the scroll area for the selectors
self.scroll_selector_layout = QtWidgets.QVBoxLayout()
self.scroll_selector_area = QtWidgets.QScrollArea()
@@ -322,39 +306,36 @@ def __init__(self, parent=None):
# Configure the widget holding the layout with all the buttons
self.scroll_selector_widget.setLayout(self.scroll_selector_layout)
- self.scroll_selector_widget.setSizePolicy(QtWidgets.QSizePolicy.Expanding,QtWidgets.QSizePolicy.Expanding)
- self.scroll_selector_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
- self.scroll_selector_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ self.scroll_selector_widget.setSizePolicy(
+ QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
+ )
+ self.scroll_selector_area.setVerticalScrollBarPolicy(
+ QtCore.Qt.ScrollBarAlwaysOn
+ )
+ self.scroll_selector_area.setHorizontalScrollBarPolicy(
+ QtCore.Qt.ScrollBarAsNeeded
+ )
-
self.scroll_selector_area.setMinimumWidth(200)
self.scroll_selector_area.setWidgetResizable(True)
self.scroll_selector_area.setWidget(self.scroll_selector_widget)
-
self.keyboard_widget_selector = gremlin.ui.ui_common.QDataCheckbox("Keyboard")
self.keyboard_widget_selector.setIgnoreKeyboard(True)
self.keyboard_widget_selector.clicked.connect(self._toggle_keyboard_widget)
-
self.vis_selector = VisualizationSelector(self._add_remove_visualization_widget)
self.vis_selector.changed.connect(self._add_remove_visualization_widget)
self.vis_selector.clear.connect(self._clear)
-
- system_selector_widget = QtWidgets.QGroupBox("System Inputs")
+ system_selector_widget = QtWidgets.QGroupBox("System Inputs")
system_selector_layout = QtWidgets.QHBoxLayout(system_selector_widget)
-
-
-
system_selector_layout.addWidget(self.keyboard_widget_selector)
self.scroll_selector_layout.addWidget(system_selector_widget)
self.scroll_selector_layout.addWidget(self.vis_selector)
-
-
clear_widget = QtWidgets.QPushButton("Clear")
clear_widget.setToolTip("Clears the selection")
clear_widget.clicked.connect(self.vis_selector._clear_selection)
@@ -367,21 +348,25 @@ def __init__(self, parent=None):
select_real_widget.setToolTip("Selects all hardware inputs")
select_real_widget.clicked.connect(self.vis_selector._select_real)
- options_widget, _ = gremlin.ui.ui_common.getHContainer((clear_widget, select_real_widget, select_all_widget))
+ options_widget, _ = gremlin.ui.ui_common.getHContainer(
+ (clear_widget, select_real_widget, select_all_widget)
+ )
self.main_layout.addWidget(options_widget)
- content_widget, _ = gremlin.ui.ui_common.getHContainer((self.scroll_selector_area, self.view_container_widget))
+ content_widget, _ = gremlin.ui.ui_common.getHContainer(
+ (self.scroll_selector_area, self.view_container_widget)
+ )
# Add the scroll area to the main layout
self.main_layout.addWidget(content_widget)
self.closed.connect(self._closed)
-
config = VisualizationConfig()
- self._keyboard_visible = config.getValue(gremlin.shared_state.keyboard_tab_guid, VisualizationType.Keyboard)
+ self._keyboard_visible = config.getValue(
+ gremlin.shared_state.keyboard_tab_guid, VisualizationType.Keyboard
+ )
self._toggle_keyboard_widget(self._keyboard_visible)
-
self.installEventFilter(self)
def eventFilter(self, widget, event):
@@ -389,14 +374,13 @@ def eventFilter(self, widget, event):
if self._keyboard_visible:
# keyboard visible - filter keys
t = event.type()
- if t in (QtCore.QEvent.Type.KeyPress, QtCore.QEvent.Type.KeyRelease):
+ if t in (QtCore.QEvent.Type.KeyPress, QtCore.QEvent.Type.KeyRelease):
return True
return super().eventFilter(widget, event)
-
@QtCore.Slot()
def _closed(self):
- ''' save the config on close'''
+ """save the config on close"""
if self.keyboard_widget:
self.keyboard_widget.unhook()
config = VisualizationConfig()
@@ -404,26 +388,26 @@ def _closed(self):
@QtCore.Slot()
def _clear(self):
- ''' clears all widgets '''
+ """clears all widgets"""
widget_list = [widget for widget in self._widget_storage.values()]
for widget in widget_list:
if widget == self.keyboard_widget:
with QtCore.QSignalBlocker(self.keyboard_widget_selector):
self.keyboard_widget_selector.setChecked(False)
- if hasattr(widget,"unhook"):
+ if hasattr(widget, "unhook"):
widget.unhook()
widget.setParent(None)
-
+
self._widget_storage.clear()
self.hideKeyboard()
config = VisualizationConfig()
config.clear()
config.save()
-
-
- @QtCore.Slot(dinput.DeviceSummary,VisualizationType,bool)
- def _add_remove_visualization_widget(self, device, visualization : VisualizationType, is_active : bool):
+ @QtCore.Slot(dinput.DeviceSummary, VisualizationType, bool)
+ def _add_remove_visualization_widget(
+ self, device, visualization: VisualizationType, is_active: bool
+ ):
"""Adds or removes a visualization widget.
:param device the device which is being updated
@@ -446,13 +430,13 @@ def _add_remove_visualization_widget(self, device, visualization : Visualization
self._update_view()
-
def showKeyboard(self):
# keyboard device
if not self.keyboard_visualizer_widget:
-
- self.keyboard_visualizer_widget = QtWidgets.QGroupBox("Keyboard")
- self.keyboard_visualizer_layout = QtWidgets.QVBoxLayout(self.keyboard_visualizer_widget)
+ self.keyboard_visualizer_widget = QtWidgets.QGroupBox("Keyboard")
+ self.keyboard_visualizer_layout = QtWidgets.QVBoxLayout(
+ self.keyboard_visualizer_widget
+ )
self.keyboard_widget = gremlin.ui.virtual_keyboard.QKeyboardWidget()
self.keyboard_widget.setReadonly(True)
self.keyboard_visualizer_layout.addWidget(self.keyboard_widget)
@@ -474,12 +458,12 @@ def hideKeyboard(self):
del self._widget_storage[key]
self.keyboard_visualizer_widget = None
self._keyboard_visible = False
-
+
with QtCore.QSignalBlocker(self.keyboard_widget_selector):
self.keyboard_widget_selector.setChecked(False)
self._update_view()
-
+
@QtCore.Slot(bool)
def _toggle_keyboard_widget(self, checked):
if checked:
@@ -488,21 +472,21 @@ def _toggle_keyboard_widget(self, checked):
self.hideKeyboard()
config = VisualizationConfig()
- config.setValue(gremlin.shared_state.keyboard_tab_guid, VisualizationType.Keyboard, checked)
-
-
+ config.setValue(
+ gremlin.shared_state.keyboard_tab_guid, VisualizationType.Keyboard, checked
+ )
def _update_view(self):
- ''' rebuids the view '''
+ """rebuids the view"""
self.view_container_layout.removeWidget(self.views)
self.views = InputViewerArea()
self.view_container_layout.addWidget(self.views)
for widget in self._widget_storage.values():
self.views.add_widget(widget)
-
-class InputViewerArea(QtWidgets.QScrollArea):
+
+class InputViewerArea(QtWidgets.QScrollArea):
"""Holds individual input visualization widgets."""
def __init__(self, parent=None):
@@ -513,7 +497,7 @@ def __init__(self, parent=None):
super().__init__(parent)
self.widgets = []
-
+
self.setWidgetResizable(True)
self.scroll_widget = QtWidgets.QWidget()
self.scroll_layout = QtWidgets.QVBoxLayout()
@@ -540,7 +524,7 @@ def add_widget(self, widget):
hint = widget.minimumSizeHint()
height = max(height, hint.height())
width = max(width, hint.width())
- self.setMinimumWidth(width+40)
+ self.setMinimumWidth(width + 40)
# self.setMinimumSize(QtCore.QSize(width+40, height))
def remove_widget(self, widget):
@@ -555,14 +539,14 @@ def remove_widget(self, widget):
del widget
def clear(self):
- ''' clears all widgets '''
+ """clears all widgets"""
for widget in self.widgets:
- self.scroll_layout.removeWidget(widget)
+ self.scroll_layout.removeWidget(widget)
widget.unhook()
del self.widgets[self.widgets.index(widget)]
del widget
self.widgets.clear()
-_visualization_config = VisualizationConfig()
\ No newline at end of file
+_visualization_config = VisualizationConfig()
diff --git a/gremlin/ui/joystick_device.py b/gremlin/ui/joystick_device.py
index 6221b738..d059c1a8 100644
--- a/gremlin/ui/joystick_device.py
+++ b/gremlin/ui/joystick_device.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -27,11 +27,8 @@
import gremlin.base_buttons
import gremlin.base_conditions
import gremlin.base_profile
-import gremlin.base_profile
-import gremlin.config
import gremlin.config
import gremlin.event_handler
-import gremlin.event_handler
import gremlin.joystick_handling
import gremlin.profile
import gremlin.shared_state
@@ -41,24 +38,24 @@
import gremlin.util
import gremlin.ui.input_item as input_item
import gremlin.ui.ui_common
-from gremlin.clipboard import Clipboard, ObjectEncoder, EncoderType
+from gremlin.clipboard import Clipboard, ObjectEncoder, EncoderType
syslog = logging.getLogger("system")
-class InputItemConfiguration(QtWidgets.QFrame):
- """ mapping viewer for a selected input item (this is the right side of the device tab) """
+class InputItemConfiguration(QtWidgets.QFrame):
+ """mapping viewer for a selected input item (this is the right side of the device tab)"""
# Signal emitted when the description changes
- description_changed = QtCore.Signal(str) # indicates the description was changed
- description_clear = QtCore.Signal() # clear the description field
+ description_changed = QtCore.Signal(str) # indicates the description was changed
+ description_clear = QtCore.Signal() # clear the description field
- def __init__(self, item_data = None, input_type = None, parent=None):
+ def __init__(self, item_data=None, input_type=None, parent=None):
"""Creates a new object instance.
:params:
-
+
item_data =profile data associated with the item, can be none to display an empty box
input_type = override input type if the input type is not that of the item_data (InputItem) - controls what containers/actions are available
parent = the parent of this widget
@@ -67,7 +64,7 @@ def __init__(self, item_data = None, input_type = None, parent=None):
super().__init__(parent)
self.id = gremlin.util.get_guid()
- self.item_data : gremlin.base_profile.InputItem = item_data
+ self.item_data: gremlin.base_profile.InputItem = item_data
self.main_layout = QtWidgets.QVBoxLayout(self)
self.button_layout = QtWidgets.QHBoxLayout()
self.widget_layout = QtWidgets.QVBoxLayout()
@@ -84,11 +81,11 @@ def __init__(self, item_data = None, input_type = None, parent=None):
self._deleted = False
def isBlank(self):
- ''' true if not associated with any data (blank widget)'''
+ """true if not associated with any data (blank widget)"""
return self.item_data is None
def _cleanup_ui(self):
- ''' called when widget is deleted '''
+ """called when widget is deleted"""
self._deleted = True
@property
@@ -96,54 +93,53 @@ def deleted(self):
return self._deleted
def setItemData(self, item_data):
- ''' updates the item data '''
+ """updates the item data"""
self.setUpdatesEnabled(False)
try:
gremlin.util.clear_layout(self.main_layout)
- self.item_data : gremlin.base_profile.InputItem = item_data
+ self.item_data: gremlin.base_profile.InputItem = item_data
if item_data is None:
parent = self.parent()
while parent and not isinstance(parent, JoystickDeviceTabWidget):
parent = self.parent()
- parent :JoystickDeviceTabWidget
+ parent: JoystickDeviceTabWidget
if parent is not None:
item_data = parent.last_item_data_key
-
+
label = QtWidgets.QLabel("Please select an input to configure")
- label.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop | QtCore.Qt.AlignmentFlag.AlignLeft)
+ label.setAlignment(
+ QtCore.Qt.AlignmentFlag.AlignTop | QtCore.Qt.AlignmentFlag.AlignLeft
+ )
self.main_layout.addWidget(label)
return
-
+
if not item_data.is_action:
# only draw description if not a sub action item
self._create_description()
-
+
if self.item_data.device_type == DeviceType.VJoy:
self._create_vjoy_dropdowns()
else:
self._create_dropdowns()
- self.action_model = ActionContainerModel(self.item_data.containers, self.item_data, self._input_type)
+ self.action_model = ActionContainerModel(
+ self.item_data.containers, self.item_data, self._input_type
+ )
self.action_view = ActionContainerView(self)
self.main_layout.addWidget(self.action_view)
- self.action_view.setContentsMargins(0,0,0,0)
+ self.action_view.setContentsMargins(0, 0, 0, 0)
self.action_view.set_model(self.action_model)
-
# setup the container widget reference
plugin_manager = gremlin.plugin_manager.ContainerPlugins()
plugin_manager.set_widget(self.item_data, self)
-
self.action_view.redraw()
-
-
finally:
self.setUpdatesEnabled(True)
self.update()
-
def _add_action(self, action_name):
"""Adds a new action to the input item.
@@ -152,14 +148,14 @@ def _add_action(self, action_name):
import container_plugins.basic
import gremlin.plugin_manager
import gremlin.ui.ui_common
+
# If this is a vJoy item then do not permit adding an action if
# there is already one present, as only response curves can be added
# and only one of them makes sense to exist
if self.item_data.get_device_type() == DeviceType.VJoy:
if len(self.item_data.containers) > 0:
return
-
-
+
plugin_manager = gremlin.plugin_manager.ActionPlugins()
container = container_plugins.basic.BasicContainer(self.item_data)
action = plugin_manager.get_class(action_name)(container)
@@ -167,28 +163,30 @@ def _add_action(self, action_name):
if action.singleton:
# action can only exist once in the container list
if self.item_data.is_action:
- gremlin.ui.ui_common.MessageBox(prompt=f"Unable to add [{action_name}]. The action cannot be added to a sub-container.")
+ gremlin.ui.ui_common.MessageBox(
+ prompt=f"Unable to add [{action_name}]. The action cannot be added to a sub-container."
+ )
return
if self.item_data.hasAction(action_name):
- gremlin.ui.ui_common.MessageBox(prompt=f"Unable to add: [{action_name}]. The action can only appear once per input.")
- return
-
+ gremlin.ui.ui_common.MessageBox(
+ prompt=f"Unable to add: [{action_name}]. The action can only appear once per input."
+ )
+ return
container.add_action(action)
-
+
if len(container.action_sets) > 0:
self.action_model.add_container(container)
-
+
self.action_model.data_changed.emit()
el = gremlin.event_handler.EventListener()
el.mapping_changed.emit(self.item_data)
self.notify_changed()
-
def notify_changed(self):
- ''' notifies the item has changed'''
-
+ """notifies the item has changed"""
+
el = gremlin.event_handler.EventListener()
event = gremlin.event_handler.DeviceChangeEvent()
event.device_guid = self.item_data.device_guid
@@ -201,22 +199,19 @@ def notify_changed(self):
el.profile_device_changed.emit(event)
el.icon_changed.emit(event)
-
def _paste_action(self, data_or_action, container):
- """ paste action to the input item """
+ """paste action to the input item"""
import container_plugins.basic
import gremlin.plugin_manager
import gremlin.base_profile
-
if self.item_data.get_device_type() == DeviceType.VJoy:
if len(self.item_data.containers) > 0:
return
-
+
plugin_manager = gremlin.plugin_manager.ActionPlugins()
action_tag_map = plugin_manager.tag_map
-
if isinstance(data_or_action, ObjectEncoder):
oc = data_or_action
if oc.encoder_type == EncoderType.Action:
@@ -234,15 +229,15 @@ def _paste_action(self, data_or_action, container):
elif isinstance(data_or_action, gremlin.base_profile.AbstractAction):
action = data_or_action
container = container_plugins.basic.BasicContainer(self.item_data)
- action_item = plugin_manager.duplicate(action, container )
+ action_item = plugin_manager.duplicate(action, container)
else:
# nothing to do
return
-
+
# remap inputs
action_item.update_inputs(self.item_data)
container.add_action(action_item)
-
+
if len(container.action_sets) > 0:
self.action_model.add_container(container)
self.action_model.data_changed.emit()
@@ -267,30 +262,30 @@ def _add_container(self, container_name):
eh.mapping_changed.emit(self.item_data)
return container
-
+
def _copy_container(self):
- ''' copies all containers to the clipboard '''
+ """copies all containers to the clipboard"""
if len(self.item_data.containers) > 0:
clipboard = Clipboard()
-
+
root = lxml.etree.Element("multi_containers")
for container in self.item_data.containers:
- node = container.to_xml()
- root.append(node)
+ node = container.to_xml()
+ root.append(node)
xml = lxml.etree.tostring(root)
- oc = ObjectEncoder(self.item_data.containers, xml, "multi", EncoderType.MultiContainer)
+ oc = ObjectEncoder(
+ self.item_data.containers, xml, "multi", EncoderType.MultiContainer
+ )
clipboard.data = oc
- syslog.info(f"multi container copied to clipboard")
-
+ syslog.info("multi container copied to clipboard")
- @QtCore.Slot(object)
+ @QtCore.Slot(object)
def _load_container_from_template(self):
-
fname, _ = QtWidgets.QFileDialog.getOpenFileName(
None,
"Container template",
gremlin.util.userprofile_path(),
- "XML files (*.xml)"
+ "XML files (*.xml)",
)
if fname and os.path.isfile(fname):
container_list = []
@@ -307,11 +302,15 @@ def _load_container_from_template(self):
container_tag_map = container_plugins.tag_map
# verify the container is valid for the input type
- valid_containers_names = self.item_data.get_valid_container_list()
+ valid_containers_names = (
+ self.item_data.get_valid_container_list()
+ )
if container_type in container_tag_map:
container_name = container_tag_map[container_type].name
if container_name in valid_containers_names:
- new_container = container_tag_map[container_type](self.item_data)
+ new_container = container_tag_map[container_type](
+ self.item_data
+ )
new_container.from_xml(node, self.item_data)
new_container.generateGuids()
container_list.append(new_container)
@@ -320,10 +319,11 @@ def _load_container_from_template(self):
msg_list.append(msg)
syslog.warning(msg)
-
if msg_list:
prompt = "".join((msg + "\n" for msg in msg_list))
- gremlin.ui.ui_common.MessageBox(title="Load Template", prompt = prompt)
+ gremlin.ui.ui_common.MessageBox(
+ title="Load Template", prompt=prompt
+ )
except:
pass
@@ -331,17 +331,14 @@ def _load_container_from_template(self):
for new_container in container_list:
if hasattr(new_container, "action_model"):
new_container.action_model = self.action_model
-
+
plugin_manager.set_container_data(self.item_data, new_container)
self.action_model.add_container(new_container)
-
-
el = gremlin.event_handler.EventListener()
el.mapping_changed.emit(self.item_data)
self.notify_changed()
-
@QtCore.Slot(object)
def _paste_container(self, container):
"""Adds a new container to the input item.
@@ -367,7 +364,9 @@ def _paste_container(self, container):
if container_type in container_tag_map:
container_name = container_tag_map[container_type].name
if container_name in valid_containers_names:
- new_container = container_tag_map[container_type](self.item_data)
+ new_container = container_tag_map[container_type](
+ self.item_data
+ )
new_container.from_xml(node, self.item_data)
new_container.generateGuids()
container_list.append(new_container)
@@ -380,16 +379,15 @@ def _paste_container(self, container):
if container_type in container_tag_map:
container_name = container_tag_map[container_type].name
if container_name in valid_containers_names:
-
- new_container = container_tag_map[container_type](self.item_data)
+ new_container = container_tag_map[container_type](
+ self.item_data
+ )
new_container.from_xml(node, self.item_data)
new_container.generateGuids()
-
container_list.append(new_container)
-
else:
new_container = plugin_manager.duplicate(container, self.item_data)
container_list.append(new_container)
@@ -398,39 +396,37 @@ def _paste_container(self, container):
for new_container in container_list:
if hasattr(new_container, "action_model"):
new_container.action_model = self.action_model
-
+
plugin_manager.set_container_data(self.item_data, new_container)
self.action_model.add_container(new_container)
-
-
-
el.mapping_changed.emit(self.item_data)
self.notify_changed()
return container_list
-
+
def _delete_container(self):
- ''' call to delete all containers '''
+ """call to delete all containers"""
if not self.item_data.containers:
# nothing to do
- return
+ return
# do a confirmation box just in case
message_box = QtWidgets.QMessageBox()
message_box.setIcon(QtWidgets.QMessageBox.Icon.Warning)
- message_box.setText("This will remove the current container set and any actions.")
+ message_box.setText(
+ "This will remove the current container set and any actions."
+ )
message_box.setInformativeText("Are you sure?")
message_box.setStandardButtons(
- QtWidgets.QMessageBox.StandardButton.Cancel |
- QtWidgets.QMessageBox.StandardButton.Ok
+ QtWidgets.QMessageBox.StandardButton.Cancel
+ | QtWidgets.QMessageBox.StandardButton.Ok
)
gremlin.util.centerDialog(message_box)
result = message_box.exec()
if result == QtWidgets.QMessageBox.StandardButton.Cancel:
return
-
+
self.action_model.remove_all_containers()
-
def _remove_container(self, container):
"""Removes an existing container from the InputItem.
@@ -440,20 +436,15 @@ def _remove_container(self, container):
self.action_model.remove_container(container)
-
-
-
def _create_description(self):
"""Creates the description input for the input item."""
self.description_layout = QtWidgets.QHBoxLayout()
- self.description_layout.addWidget(
- QtWidgets.QLabel("Action Description")
- )
+ self.description_layout.addWidget(QtWidgets.QLabel("Action Description"))
self.description_field = QtWidgets.QLineEdit()
self.description_field.setText(self.item_data.description)
self.description_field.textChanged.connect(self._edit_description_cb)
self.description_layout.addWidget(self.description_field)
- self.description_field.setReadOnly(self.item_data.descriptionReadOnly)
+ self.description_field.setReadOnly(self.item_data.descriptionReadOnly)
del_icon = gremlin.util.load_icon("mdi.delete")
self.description_clear_button = QtWidgets.QPushButton()
self.description_clear_button.setIcon(del_icon)
@@ -464,28 +455,29 @@ def _create_description(self):
self.main_layout.addLayout(self.description_layout)
-
def _create_dropdowns(self):
"""Creates a drop down selection with actions that can be
added to the current input item.
"""
import gremlin.ui.input_item as input_item
import gremlin.ui.ui_common as ui_common
+
self.dropdown_widget = QtWidgets.QWidget()
self.dropdown_layout = QtWidgets.QHBoxLayout(self.dropdown_widget)
- self.action_selector = ui_common.ActionSelector(
- self._input_type,
- None
- )
+ self.action_selector = ui_common.ActionSelector(self._input_type, None)
self.action_selector.action_added.connect(self._add_action)
self.action_selector.action_paste.connect(self._paste_action)
- self.container_selector = input_item.ContainerSelector(self._input_type, self.item_data.is_axis)
+ self.container_selector = input_item.ContainerSelector(
+ self._input_type, self.item_data.is_axis
+ )
self.container_selector.container_added.connect(self._add_container)
self.container_selector.container_copy.connect(self._copy_container)
self.container_selector.container_paste.connect(self._paste_container)
- self.container_selector.container_from_template.connect(self._load_container_from_template)
+ self.container_selector.container_from_template.connect(
+ self._load_container_from_template
+ )
self.container_selector.container_delete.connect(self._delete_container)
self.always_execute = QtWidgets.QCheckBox("Always execute")
self.always_execute.setChecked(self.item_data.always_execute)
@@ -505,8 +497,7 @@ def _create_vjoy_dropdowns(self):
self.action_selector = gremlin.ui.ui_common.ActionSelector(
gremlin.types.DeviceType.VJoy,
None,
- parent = self.action_selector_widget,
-
+ parent=self.action_selector_widget,
)
self.action_selector.action_added.connect(self._add_action)
self.action_selector.action_paste.connect(self._paste_action)
@@ -524,7 +515,7 @@ def _edit_description_cb(self, text):
@QtCore.Slot()
def _delete_description_cb(self):
- """ deletes the description text.
+ """deletes the description text.
:param text the new contents of the text field
"""
@@ -546,15 +537,12 @@ def _valid_action_names(self):
action_names = []
if self.item_data.input_type == gremlin.types.DeviceType.VJoy:
entry = gremlin.plugin_manager.ActionPlugins().repository.get(
- "response-curve-ex",
- None
+ "response-curve-ex", None
)
if entry is not None:
action_names.append(entry.name)
else:
- raise gremlin.error.GremlinError(
- "Response curve plugin is missing"
- )
+ raise gremlin.error.GremlinError("Response curve plugin is missing")
else:
for entry in gremlin.plugin_manager.ActionPlugins().repository.values():
if self.item_data.input_type in entry.input_types:
@@ -563,10 +551,15 @@ def _valid_action_names(self):
class ActionContainerModel(gremlin.ui.ui_common.AbstractModel):
-
"""Stores action containers for display using the corresponding view."""
- def __init__(self, containers, item_data : InputItemConfiguration = None, input_type: InputType = None, parent=None):
+ def __init__(
+ self,
+ containers,
+ item_data: InputItemConfiguration = None,
+ input_type: InputType = None,
+ parent=None,
+ ):
"""Creates a new instance.
:param containers: the container instances of this model
@@ -577,17 +570,19 @@ def __init__(self, containers, item_data : InputItemConfiguration = None, input_
super().__init__(parent)
self._containers = containers
self._item_data = item_data
- self._input_type = input_type if input_type is not None else item_data._input_type
+ self._input_type = (
+ input_type if input_type is not None else item_data._input_type
+ )
@property
def item_data(self) -> InputItemConfiguration:
- ''' get the item data associated with this action container '''
+ """get the item data associated with this action container"""
return self._item_data
-
+
@property
def input_type(self) -> InputType:
return self._input_type
-
+
def rows(self):
"""Returns the number of rows in the model.
@@ -630,9 +625,6 @@ def remove_container(self, container):
self.data_changed.emit()
el.container_delete.emit(self.item_data, container)
el.mapping_changed.emit(self.item_data)
-
-
-
def remove_all_containers(self):
"""Removes an existing container from the model.
@@ -653,13 +645,10 @@ def remove_all_containers(self):
del self._containers[self._containers.index(container)]
self.data_changed.emit()
-
el.mapping_changed.emit(self.item_data)
-
class ActionContainerView(gremlin.ui.ui_common.AbstractView):
-
"""View class used to display ActionContainerModel contents."""
def __init__(self, parent=None):
@@ -671,7 +660,7 @@ def __init__(self, parent=None):
# Create required UI items
self.main_layout = QtWidgets.QVBoxLayout(self)
- self.main_layout.setContentsMargins(0,0,0,0)
+ self.main_layout.setContentsMargins(0, 0, 0, 0)
self.redraw_lock = False
self.scroll_area = QtWidgets.QScrollArea()
@@ -681,8 +670,7 @@ def __init__(self, parent=None):
# Configure the widget holding the layout with all the buttons
self.scroll_widget.setLayout(self.scroll_layout)
self.scroll_widget.setSizePolicy(
- QtWidgets.QSizePolicy.Expanding,
- QtWidgets.QSizePolicy.Expanding
+ QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
)
self.scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.scroll_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
@@ -704,12 +692,12 @@ def redraw(self):
try:
self.redraw_lock = True
import gremlin.ui.ui_common
-
+
# if there is a cleanup handler defined for any actions widgets - call them before removing them
for container_widget in self._widgets:
for action_widget in container_widget.action_widgets:
for widget in action_widget._widgets:
- if hasattr(widget,"_cleanup_ui"):
+ if hasattr(widget, "_cleanup_ui"):
widget._cleanup_ui()
self._widgets.clear()
@@ -722,16 +710,20 @@ def redraw(self):
widget.container_modified.connect(self.model.data_changed.emit)
self.scroll_layout.addWidget(widget)
self._widgets.append(widget)
-
+
else:
- input_type = self.model.input_type # InputType.JoystickAxis
- label = QtWidgets.QLabel(f"Please add an action or container for {self.model.item_data.display_name}") # ({InputType.to_display_name(input_type)})")
- label.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop | QtCore.Qt.AlignmentFlag.AlignLeft)
+ label = QtWidgets.QLabel(
+ f"Please add an action or container for {self.model.item_data.display_name}"
+ ) # ({InputType.to_display_name(input_type)})")
+ label.setAlignment(
+ QtCore.Qt.AlignmentFlag.AlignTop
+ | QtCore.Qt.AlignmentFlag.AlignLeft
+ )
self.scroll_layout.addWidget(label)
self.scroll_layout.addStretch(1)
finally:
self.redraw_lock = False
-
+
else:
syslog.error("re-entry code detected")
@@ -744,24 +736,16 @@ def _create_closed_cb(self, widget):
"""
return lambda: self.model.remove_container(widget.profile_data)
-
-
-
class JoystickDeviceTabWidget(gremlin.ui.ui_common.QSplitTabWidget):
-
"""Widget used to display the input joystick device."""
- inputChanged = QtCore.Signal(str, object, object) # indicates the input selection changed sends (device_guid string, input_type, input_id)
+ inputChanged = QtCore.Signal(
+ str, object, object
+ ) # indicates the input selection changed sends (device_guid string, input_type, input_id)
- def __init__(
- self,
- device,
- device_profile,
- current_mode,
- parent=None
- ):
+ def __init__(self, device, device_profile, current_mode, parent=None):
"""Creates a new object instance.
:param device device information about this widget's device
@@ -774,30 +758,29 @@ def __init__(
import gremlin.plugin_manager
# Store parameters
-
-
- self.curve_update_handler = {} # map of curve handlers to the input by index
+
+ self.curve_update_handler = {} # map of curve handlers to the input by index
self.device = device
self.device_profile = device_profile
self.device_profile.ensure_mode_exists(current_mode, self.device)
- #self.widget_tracker = gremlin.ui.ui_common.DeviceWidgetTracker() # caches the InputConfigurationItem for this item
+ # self.widget_tracker = gremlin.ui.ui_common.DeviceWidgetTracker() # caches the InputConfigurationItem for this item
self.last_item_data_key = None
- self.last_selected_index = index = 0
+ self.last_selected_index = 0
self.device_guid = device.device_guid
self.device_name = device.name
self._debug_widget = None
-
- self._last_selected_index = -1 # last selected index in the list
-
+
+ self._last_selected_index = -1 # last selected index in the list
# List of inputs
self.input_item_list_model = input_item.InputItemListModel(
- device_profile,
- current_mode
+ device_profile, current_mode
+ )
+ self.input_item_list_view = input_item.InputItemListView(
+ name=device.name, custom_widget_handler=self._custom_widget_handler
)
- self.input_item_list_view = input_item.InputItemListView(name=device.name, custom_widget_handler = self._custom_widget_handler)
# Handle vJoy as input and vJoy as output devices properly
vjoy_as_input = self.device_profile.parent.settings.vjoy_as_input
@@ -806,7 +789,6 @@ def __init__(
# as if they were physical input devices
if device.is_virtual and not vjoy_as_input.get(device.vjoy_id, False):
self.input_item_list_view.limit_input_types([InputType.JoystickAxis])
-
self.input_item_list_view.item_edit_curve.connect(self._edit_curve_item_cb)
self.input_item_list_view.item_delete_curve.connect(self._delete_curve_item_cb)
@@ -814,7 +796,6 @@ def __init__(
# load the model
self.input_item_list_view.set_model(self.input_item_list_model)
self.input_item_list_view.redraw()
-
# Handle user interaction
self.input_item_list_view.item_selected.connect(self._select_item_cb)
@@ -822,7 +803,7 @@ def __init__(
# Add modifiable device label
label_widget = QtWidgets.QWidget()
label_layout = QtWidgets.QHBoxLayout(label_widget)
- label_layout.setContentsMargins(4,8,0,0)
+ label_layout.setContentsMargins(4, 8, 0, 0)
label_layout.addWidget(QtWidgets.QLabel("Device Label"))
line_edit = QtWidgets.QLineEdit()
line_edit.setText(device_profile.label)
@@ -831,12 +812,13 @@ def __init__(
self.addLeftPanelWidget(label_widget)
self.addLeftPanelWidget(self.input_item_list_view)
-
# Add a help text for the purpose of the vJoy tab
- if device is not None and \
- device.is_virtual and \
- not vjoy_as_input.get(device.vjoy_id, False):
+ if (
+ device is not None
+ and device.is_virtual
+ and not vjoy_as_input.get(device.vjoy_id, False)
+ ):
label = QtWidgets.QLabel(
"This tab allows assigning a response curve to virtual axis. "
"The purpose of this is to enable split and merge axis to be "
@@ -849,7 +831,6 @@ def __init__(
label.setMargin(10)
self.addLeftPanelWidget(label)
-
config = gremlin.config.Configuration()
if config.debug_ui:
@@ -857,10 +838,6 @@ def __init__(
self._debug_widget.setMaximumHeight(32)
self.addRightPanelWidget(self._debug_widget)
-
-
-
-
el = gremlin.event_handler.EventListener()
# update on an edit mode change so we update the display
el.edit_mode_changed.connect(self._edit_mode_changed_cb)
@@ -875,36 +852,39 @@ def __init__(
if selected_index is not None and selected_index != -1:
self._select_item_cb(selected_index)
-
-
# update all curve icons
self.update_curve_icons()
-
def _cleanup_ui(self):
- ''' called when deleted '''
+ """called when deleted"""
self.input_item_list_view.item_edit_curve.disconnect(self._edit_curve_item_cb)
- self.input_item_list_view.item_delete_curve.disconnect(self._delete_curve_item_cb)
+ self.input_item_list_view.item_delete_curve.disconnect(
+ self._delete_curve_item_cb
+ )
self.input_item_list_view.item_selected.disconnect(self._select_item_cb)
el = gremlin.event_handler.EventListener()
-
+
el.edit_mode_changed.disconnect(self._edit_mode_changed_cb)
el.config_changed.disconnect(self._config_changed_cb)
-
def _edit_curve_item_cb(self, widget, index, data):
- ''' edit curve request '''
+ """edit curve request"""
import gremlin.curve_handler
import gremlin.event_handler
- curve_data : gremlin.curve_handler.AxisCurveData = data.curve_data
+
+ curve_data: gremlin.curve_handler.AxisCurveData = data.curve_data
if not curve_data:
curve_data = gremlin.curve_handler.AxisCurveData()
- curve_data.calibration = gremlin.ui.axis_calibration.CalibrationManager().getCalibration(data.device_guid, data.input_id)
+ curve_data.calibration = (
+ gremlin.ui.axis_calibration.CalibrationManager().getCalibration(
+ data.device_guid, data.input_id
+ )
+ )
curve_data.curve_update()
data.curve_data = curve_data
-
+
dialog = gremlin.curve_handler.AxisCurveDialog(curve_data)
gremlin.util.centerDialog(dialog, dialog.width(), dialog.height())
@@ -919,7 +899,7 @@ def _edit_curve_item_cb(self, widget, index, data):
gremlin.shared_state.push_suspend_highlighting()
dialog.exec()
self.curve_update_handler[index] = None
- print ("update curve data")
+ print("update curve data")
data.curve_data.curve_update()
# update the registered curve state
@@ -929,79 +909,93 @@ def _edit_curve_item_cb(self, widget, index, data):
# renable highlighting
gremlin.shared_state.pop_suspend_highlighting()
-
-
self._update_curve_icon(index, data)
-
def _delete_curve_item_cb(self, widget, index, data):
- ''' delete curve request '''
+ """delete curve request"""
message_box = QtWidgets.QMessageBox()
message_box.setIcon(QtWidgets.QMessageBox.Icon.Warning)
message_box.setText("Delete this input curve?")
- message_box.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok | QtWidgets.QMessageBox.StandardButton.Cancel)
+ message_box.setStandardButtons(
+ QtWidgets.QMessageBox.StandardButton.Ok
+ | QtWidgets.QMessageBox.StandardButton.Cancel
+ )
gremlin.util.centerDialog(message_box)
result = message_box.exec()
if result == QtWidgets.QMessageBox.StandardButton.Ok:
- print ("delete curve data")
+ print("delete curve data")
data.curve_data = None
self._update_curve_icon(index, data)
-
-
- def _update_input_value_changed_cb(self, index : int, value : float):
- if index in self.curve_update_handler and self.curve_update_handler[index] is not None:
+ def _update_input_value_changed_cb(self, index: int, value: float):
+ if (
+ index in self.curve_update_handler
+ and self.curve_update_handler[index] is not None
+ ):
self.curve_update_handler[index](value)
@QtCore.Slot(str)
- def _edit_mode_changed_cb(self, mode : str):
- ''' called on edit mode change '''
+ def _edit_mode_changed_cb(self, mode: str):
+ """called on edit mode change"""
self.set_mode(mode)
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_detailed
- if verbose: syslog.info(f"DeviceWidget: {self.device_name} change mode: [{mode}]")
+ if verbose:
+ syslog.info(f"DeviceWidget: {self.device_name} change mode: [{mode}]")
self.update_curve_icons()
-
def update_curve_icons(self):
for index, widget in self.input_item_list_view.widget_map.items():
if widget is not None:
- self._update_curve_icon(index, self.input_item_list_view.model.data(index))
+ self._update_curve_icon(
+ index, self.input_item_list_view.model.data(index)
+ )
- def _update_curve_icon(self, index : int, data):
+ def _update_curve_icon(self, index: int, data):
widget = self.input_item_list_view.widget_map[index]
- input_item = widget.data
enabled = data.curve_data is not None
widget.update_curve_icon(enabled)
-
-
def _config_changed_cb(self):
self.input_item_list_view.redraw()
- def _custom_widget_handler(self, list_view : input_item.InputItemListView, index : int, identifier : input_item.InputIdentifier, data, parent = None):
- ''' creates a widget for the input
-
+ def _custom_widget_handler(
+ self,
+ list_view: input_item.InputItemListView,
+ index: int,
+ identifier: input_item.InputIdentifier,
+ data,
+ parent=None,
+ ):
+ """creates a widget for the input
+
the widget must have a selected property
:param list_view The list view control the widget to create belongs to
:param index The index in the list starting at 0 being the top item
:param identifier the InpuIdentifier for the input list
:param data the data associated with this input item
-
- '''
-
-
+
+ """
+
if data.input_type == InputType.JoystickAxis:
- widget = input_item.InputItemWidget(identifier = identifier, parent=parent, data = data)
+ widget = input_item.InputItemWidget(
+ identifier=identifier, parent=parent, data=data
+ )
prefix = "dark_" if gremlin.shared_state.is_dark_theme else ""
widget.setIcon(f"{prefix}joystick.png", use_qta=False)
if widget.axis_widget is not None and identifier.is_axis:
- widget.axis_widget.valueChanged.connect(lambda x: self._update_input_value_changed_cb(index, x))
+ widget.axis_widget.valueChanged.connect(
+ lambda x: self._update_input_value_changed_cb(index, x)
+ )
elif data.input_type == InputType.JoystickButton:
- widget = input_item.InputItemWidget(identifier = identifier, parent=parent, data = data)
+ widget = input_item.InputItemWidget(
+ identifier=identifier, parent=parent, data=data
+ )
widget.setIcon("mdi.gesture-tap-button")
elif data.input_type == InputType.JoystickHat:
- widget = input_item.InputItemWidget(identifier = identifier, parent=parent, data = data)
+ widget = input_item.InputItemWidget(
+ identifier=identifier, parent=parent, data=data
+ )
widget.setIcon("ei.fullscreen")
widget.create_action_icons(data)
widget.disable_close()
@@ -1009,9 +1003,7 @@ def _custom_widget_handler(self, list_view : input_item.InputItemListView, index
widget.setDescription(data.description)
widget.index = index
-
return widget
-
# def _populate_axis_input_widget_ui(self, input_widget, container_widget, data):
# ''' called when the repeater widget is created for an axis input '''
@@ -1030,7 +1022,7 @@ def _custom_widget_handler(self, list_view : input_item.InputItemListView, index
# widget.valueChanged.connect(self._input_value_changed) # hook value changed event on the axis repeater when displayed
# return widget
# return None
-
+
# def _populate_button_input_widget_ui(self, input_widget, container_widget, data):
# ''' called when the widget is created for a button input '''
# if gremlin.config.Configuration().show_input_axis:
@@ -1044,31 +1036,29 @@ def _custom_widget_handler(self, list_view : input_item.InputItemListView, index
# layout.addWidget(widget)
# layout.addStretch()
# return widget
-
+
@property
def running(self):
return gremlin.shared_state.is_running
-
@QtCore.Slot(int)
- def _select_item_cb(self, index, force_update = False):
- """ Handles the loading of mappings for a given input item - handler for select_input event
+ def _select_item_cb(self, index, force_update=False):
+ """Handles the loading of mappings for a given input item - handler for select_input event
:param index the index of the selected item
"""
try:
-
self.setUpdatesEnabled(False)
config = gremlin.config.Configuration()
verbose = config.verbose_mode_details
- #verbose = True
+ # verbose = True
# syslog = logging.getLogger("system")
widget = None
current_mode = gremlin.shared_state.edit_mode
- #self.device_profile.ensure_mode_exists(current_mode, self.device)
- #print (f"joystick device input select: current edit mode is {current_mode} ======================================")
+ # self.device_profile.ensure_mode_exists(current_mode, self.device)
+ # print (f"joystick device input select: current edit mode is {current_mode} ======================================")
if index == -1:
index = self.last_selected_index
@@ -1078,24 +1068,29 @@ def _select_item_cb(self, index, force_update = False):
index = 0
else:
# no input to select
- widget = InputItemConfiguration()
+ widget = InputItemConfiguration()
self.setRightPanelWidget(widget)
return
else:
item_data = self.input_item_list_model.data(index)
-
-
-
if not item_data:
- syslog.warning(f"JoystickDevice: Device [{device_name}] has no inputs for mode {current_mode} - this is not normal.")
+ syslog.warning(
+ f"JoystickDevice: Device [{device_name}] has no inputs for mode {current_mode} - this is not normal."
+ )
if verbose:
- device_name = gremlin.joystick_handling.device_name_from_guid(self.device_guid)
+ device_name = gremlin.joystick_handling.device_name_from_guid(
+ self.device_guid
+ )
if item_data:
- syslog.info(f"Selecting input config item for {device_name} input index [{index}] mode: {current_mode}: {item_data.debug_display}")
+ syslog.info(
+ f"Selecting input config item for {device_name} input index [{index}] mode: {current_mode}: {item_data.debug_display}"
+ )
else:
- syslog.info(f"Selecting input config item for {device_name} input index [{index}] mode: {current_mode}: Empty content")
+ syslog.info(
+ f"Selecting input config item for {device_name} input index [{index}] mode: {current_mode}: Empty content"
+ )
new_key = None
if item_data is not None:
@@ -1105,24 +1100,24 @@ def _select_item_cb(self, index, force_update = False):
# same input - nothing to do
return
-
-
-
self.last_item_data_key = new_key
-
-
if item_data is not None:
-
-
self.clearRightPanel()
-
+
# not in cache, create it and add to cache for this device/input combination
- if verbose: syslog.info(f"create and store in cache content widget for index: {index} device: {self.device_guid}")
- widget = InputItemConfiguration(item_data, parent = self)
+ if verbose:
+ syslog.info(
+ f"create and store in cache content widget for index: {index} device: {self.device_guid}"
+ )
+ widget = InputItemConfiguration(item_data, parent=self)
widget.action_model.data_changed.connect(self._create_change_cb(index))
- widget.description_changed.connect(lambda x: self._description_changed_cb(index, x))
- widget.description_clear.connect(lambda: self._description_clear_cb(index,widget))
+ widget.description_changed.connect(
+ lambda x: self._description_changed_cb(index, x)
+ )
+ widget.description_clear.connect(
+ lambda: self._description_clear_cb(index, widget)
+ )
# indicate the input changed
device_guid = str(item_data.device_guid)
@@ -1130,29 +1125,28 @@ def _select_item_cb(self, index, force_update = False):
input_id = item_data.input_id
self.inputChanged.emit(device_guid, input_type, input_id)
self.addRightPanelWidget(widget)
- #self.widget_tracker.registerWidget(widget, self.device_guid, item_data.input_type, item_data.input_id, item_data.id)
+ # self.widget_tracker.registerWidget(widget, self.device_guid, item_data.input_type, item_data.input_id, item_data.id)
-
self.input_item_list_view.select_item(index, False)
if force_update:
# update the container to reflect the data change
widget.setItemData(item_data)
-
- #assert widget.item_data == item_data,"cache mismatch"
+ # assert widget.item_data == item_data,"cache mismatch"
if verbose:
syslog.info(f"Show widget: {widget.id} {item_data.debug_display}")
-
- if config.debug_ui:
- self._debug_widget.setText(f"Contents for : {item_data.debug_display}")
+ if config.debug_ui:
+ self._debug_widget.setText(
+ f"Contents for : {item_data.debug_display}"
+ )
else:
# show the empty widget
if self._debug_widget:
- self._debug_widget.setText(f"Contents for : N/A")
+ self._debug_widget.setText("Contents for : N/A")
widget = InputItemConfiguration()
self.addRightPanelWidget(widget)
@@ -1160,42 +1154,40 @@ def _select_item_cb(self, index, force_update = False):
el = gremlin.event_handler.EventListener()
el.input_selection_changed.emit(device_guid, input_type, input_id)
- finally:
- #widget.update()
+ finally:
+ # widget.update()
self.setUpdatesEnabled(True)
# if widget:
# widget.setVisible(True)
self.update()
-
-
def _description_changed_cb(self, index, text):
- ''' called when the description text of the widget changes to update the description on the input item
-
+ """called when the description text of the widget changes to update the description on the input item
+
:param: index = the index of the input widget to update with the new text
-
- '''
+
+ """
item = self.input_item_list_view.itemAt(index)
item.data.description = text
item.setDescription(text)
def _description_clear_cb(self, index, widget):
- ''' delete description entry '''
+ """delete description entry"""
with QtCore.QSignalBlocker(widget.description_field):
- widget.description_field.setText('')
+ widget.description_field.setText("")
item = self.input_item_list_view.itemAt(index)
item.data.description = None
- item.setDescription('')
-
-
+ item.setDescription("")
def set_mode(self, mode):
- ''' changes the mode of the tab '''
+ """changes the mode of the tab"""
if gremlin.config.Configuration().verbose_mode_detailed:
# syslog = logging.getLogger("system")
- syslog.info(f"Device tab: change mode requested: device tab: {gremlin.shared_state.get_device_name(self.device.device_guid)} current mode: [{mode}] new mode: [{mode}] ")
-
+ syslog.info(
+ f"Device tab: change mode requested: device tab: {gremlin.shared_state.get_device_name(self.device.device_guid)} current mode: [{mode}] new mode: [{mode}] "
+ )
+
self.device_profile.ensure_mode_exists(mode, self.device)
# index = self.last_item_index
@@ -1206,30 +1198,25 @@ def set_mode(self, mode):
self.input_item_list_model.mode = mode
- #self.input_item_list_view.select_item(-1)
+ # self.input_item_list_view.select_item(-1)
if gremlin.shared_state.isDeviceTabActive(self.device_guid):
self.input_item_list_model.refresh()
- self.input_item_list_view.redraw()
+ self.input_item_list_view.redraw()
self._select_item_cb(self._last_selected_index)
-
-
-
-
def redraw(self):
- ''' updates the list widget '''
+ """updates the list widget"""
self.input_item_list_view.redraw()
-
+
def refresh(self):
"""Refreshes the current selection, ensuring proper synchronization."""
- self._select_item_cb(self.input_item_list_view.current_index, force_update = True)
+ self._select_item_cb(self.input_item_list_view.current_index, force_update=True)
# self.redraw()
-
+
# if self.input_item_list_view.current_index is not None:
# self._select_item_cb(self.input_item_list_view.current_index, force_update = True)
-
def _create_change_cb(self, index):
"""Creates a callback handling content changes.
@@ -1237,7 +1224,7 @@ def _create_change_cb(self, index):
:return callback function redrawing changed content
"""
return lambda: self.input_item_list_view.redraw_index(index)
-
+
def _create_description_change_cb(self, index):
"""Creates a callback handling content changes.
@@ -1253,9 +1240,6 @@ def update_device_label(self, text):
"""
self.device_profile.label = text
-
-
-
def input_item_index_lookup(self, index):
"""Returns the profile data belonging to the provided index.
@@ -1274,7 +1258,6 @@ def input_item_index_lookup(self, index):
button_count = len(input_items.config[InputType.JoystickButton])
hat_count = len(input_items.config[InputType.JoystickHat])
-
if index < axis_count:
# Handle non continuous axis setups
axis_keys = sorted(input_items.config[InputType.JoystickAxis].keys())
@@ -1284,14 +1267,10 @@ def input_item_index_lookup(self, index):
f"type={InputType.to_string(InputType.JoystickAxis)} index={axis_keys[index]}"
)
- return input_items.get_data(
- InputType.JoystickAxis,
- axis_keys[index]
- )
+ return input_items.get_data(InputType.JoystickAxis, axis_keys[index])
elif index < axis_count + button_count:
if not input_items.has_data(
- InputType.JoystickButton,
- index - axis_count + 1
+ InputType.JoystickButton, index - axis_count + 1
):
syslog.error(
"Attempting to retrieve non existent button input, "
@@ -1299,13 +1278,11 @@ def input_item_index_lookup(self, index):
)
return input_items.get_data(
- InputType.JoystickButton,
- index - axis_count + 1
+ InputType.JoystickButton, index - axis_count + 1
)
elif index < axis_count + button_count + hat_count:
if not input_items.has_data(
- InputType.JoystickHat,
- index - axis_count - button_count + 1
+ InputType.JoystickHat, index - axis_count - button_count + 1
):
syslog.error(
"Attempting to retrieve non existent hat input, "
@@ -1313,35 +1290,31 @@ def input_item_index_lookup(self, index):
)
return input_items.get_data(
- InputType.JoystickHat,
- index - axis_count - button_count + 1
+ InputType.JoystickHat, index - axis_count - button_count + 1
)
-
@gremlin.singleton_decorator.SingletonDecorator
-class InputConfigurationWidgetCache():
- ''' caches the joystick input widget for each device/input combination '''
+class InputConfigurationWidgetCache:
+ """caches the joystick input widget for each device/input combination"""
+
def __init__(self):
self._widget_map = {}
-
def register(self, key, widget):
- if not key in self._widget_map:
+ if key not in self._widget_map:
self._widget_map[key] = widget
-
-
+
def clear(self):
- ''' clears the cache '''
+ """clears the cache"""
self._widget_map.clear()
-
def retrieve(self, key):
if key in self._widget_map:
return self._widget_map[key]
return None
-
- def retrieve_by_data(self,item_data):
+
+ def retrieve_by_data(self, item_data):
if item_data:
key = item_data.id
return self.retrieve(key)
@@ -1352,15 +1325,22 @@ def remove(self, key):
del self._widget_map[key]
def dump(self):
- ''' dumps the cache content to the log for debug purposes '''
+ """dumps the cache content to the log for debug purposes"""
# syslog = logging.getLogger("system")
items = list(self._widget_map.values())
- items.sort(key = lambda x: (x.item_data.profile_mode, x.item_data.device_guid, x.item_data.input_type, x.item_data.input_id))
+ items.sort(
+ key=lambda x: (
+ x.item_data.profile_mode,
+ x.item_data.device_guid,
+ x.item_data.input_type,
+ x.item_data.input_id,
+ )
+ )
current_device_guid = None
current_mode = None
current_input_type = None
-
- syslog.info("-"*50)
+
+ syslog.info("-" * 50)
syslog.info("UI widget cache dump")
for index, input_item_config in enumerate(items):
item: gremlin.base_profile.InputItem = input_item_config.item_data
@@ -1373,10 +1353,11 @@ def dump(self):
syslog.info(f"\tDevice {device_name} id {str(item.device_guid)}:")
if not current_input_type or current_input_type != item.input_type:
current_input_type = item.input_type
- syslog.info(f"\t\tInput Type: {InputType.to_display_name(item.input_type)}")
+ syslog.info(
+ f"\t\tInput Type: {InputType.to_display_name(item.input_type)}"
+ )
syslog.info(f"\t\t\tInput Id: {item.display_name} cache index [{index:,}]")
-
# primary cache instantiation to prevent GC
-_cache = InputConfigurationWidgetCache()
\ No newline at end of file
+_cache = InputConfigurationWidgetCache()
diff --git a/gremlin/ui/keyboard_device.py b/gremlin/ui/keyboard_device.py
index bee52833..5a5b51a9 100644
--- a/gremlin/ui/keyboard_device.py
+++ b/gremlin/ui/keyboard_device.py
@@ -20,7 +20,6 @@
from __future__ import annotations
import logging
from PySide6 import QtWidgets, QtCore
-import json
# import container_plugins.basic
# import gremlin
@@ -35,7 +34,6 @@
from .input_item import InputItemWidget, InputIdentifier, InputItemListView
import uuid
from gremlin.util import *
-from gremlin.input_types import InputType
import gremlin.base_classes
from lxml import etree as ElementTree
import gremlin.ui.ui_common
@@ -165,7 +163,6 @@ def has_mouse(self):
def parse_xml(self, node, data = None):
''' loads itself from xml '''
- from gremlin.keyboard import key_from_code
self._suspend_update = True
if node.tag == "input":
@@ -203,7 +200,7 @@ def parse_xml(self, node, data = None):
# else:
(scan_code, is_extended), _ = gremlin.keyboard.KeyMap.translate((scan_code, is_extended))
key = gremlin.keyboard.KeyMap.find(scan_code, is_extended)
- if not key in self._key.latched_keys:
+ if key not in self._key.latched_keys:
self._key._latched_keys.append(key)
self._key._update()
@@ -714,7 +711,6 @@ def _populate_input_widget_ui(self, input_widget, container_widget, data):
def _edit_item_cb(self, widget, index, data):
''' called when the edit button is clicked '''
- from gremlin.keyboard import Key
from gremlin.ui.virtual_keyboard import InputKeyboardDialog
data = self.model.data(index)
diff --git a/gremlin/ui/merge_axis.py b/gremlin/ui/merge_axis.py
index 8c5ac53c..579d3695 100644
--- a/gremlin/ui/merge_axis.py
+++ b/gremlin/ui/merge_axis.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -26,8 +26,8 @@
syslog = logging.getLogger("system")
-class MergeAxisUi(ui_common.BaseDialogUi):
+class MergeAxisUi(ui_common.BaseDialogUi):
"""Allows merging physical axes into a single virtual ones."""
def __init__(self, profile_data, parent=None):
@@ -62,14 +62,13 @@ def __init__(self, profile_data, parent=None):
container_widget = QtWidgets.QWidget()
container_layout = QtWidgets.QHBoxLayout(container_widget)
container_layout.addStretch()
-
+
self.add_button = QtWidgets.QPushButton("New Axis")
self.add_button.clicked.connect(self._add_entry)
container_layout.addWidget(self.add_button)
container_layout.addStretch()
-
self.main_layout.addLayout(self.merge_layout)
self.main_layout.addWidget(container_widget)
@@ -90,7 +89,7 @@ def _remove_entry(self, widget):
:param widget the widget to remove
"""
- assert(isinstance(widget, MergeAxisEntry))
+ assert isinstance(widget, MergeAxisEntry)
# Remove profile data
del self.profile_data.merge_axes[self.entries.index(widget)]
@@ -111,24 +110,26 @@ def to_profile(self):
joy2_sel = entry.joy2_selector.get_selection()
mode_idx = entry.mode_selector.currentIndex()
operation_str = entry.operation_selector.currentText()
- self.profile_data.merge_axes.append({
- "mode": entry.mode_selector.mode_list[mode_idx],
- "operation": gremlin.types.MergeAxisOperation.to_enum(
- operation_str
- ),
- "vjoy": {
- "vjoy_id": vjoy_sel["device_id"],
- "axis_id": vjoy_sel["input_id"]
- },
- "lower": {
- "device_guid": joy1_sel["device_id"],
- "axis_id": joy1_sel["input_id"]
- },
- "upper": {
- "device_guid": joy2_sel["device_id"],
- "axis_id": joy2_sel["input_id"]
+ self.profile_data.merge_axes.append(
+ {
+ "mode": entry.mode_selector.mode_list[mode_idx],
+ "operation": gremlin.types.MergeAxisOperation.to_enum(
+ operation_str
+ ),
+ "vjoy": {
+ "vjoy_id": vjoy_sel["device_id"],
+ "axis_id": vjoy_sel["input_id"],
+ },
+ "lower": {
+ "device_guid": joy1_sel["device_id"],
+ "axis_id": joy1_sel["input_id"],
+ },
+ "upper": {
+ "device_guid": joy2_sel["device_id"],
+ "axis_id": joy2_sel["input_id"],
+ },
}
- })
+ )
def from_profile(self):
"""Populates the merge axis entries of the ui from the profile data."""
@@ -137,8 +138,7 @@ def from_profile(self):
# Show an error if the desired vJoy device is no longer available
# as an output
if self.profile_data.settings.vjoy_as_input.get(
- entry["vjoy"]["vjoy_id"],
- False
+ entry["vjoy"]["vjoy_id"], False
):
entries_to_remove.append(entry)
gremlin.util.display_error(
@@ -150,16 +150,13 @@ def from_profile(self):
new_entry.select(entry)
for entry in entries_to_remove:
- del self.profile_data.merge_axes[
- self.profile_data.merge_axes.index(entry)
- ]
+ del self.profile_data.merge_axes[self.profile_data.merge_axes.index(entry)]
def _output_vjoy_devices(self):
output_devices = []
for dev in gremlin.joystick_handling.vjoy_devices():
is_virtual = not self.profile_data.settings.vjoy_as_input.get(
- dev.vjoy_id,
- False
+ dev.vjoy_id, False
)
has_axes = dev.axis_count > 0
if is_virtual and has_axes:
@@ -168,7 +165,6 @@ def _output_vjoy_devices(self):
class MergeAxisEntry(QtWidgets.QDockWidget):
-
"""UI dialog which allows configuring how to merge two axes."""
# Signal which is emitted whenever the widget is closed
@@ -202,15 +198,13 @@ def __init__(self, change_cb, profile_data, parent=None):
self.vjoy_selector = ui_common.VJoySelector(
lambda x: change_cb(),
[InputType.JoystickAxis],
- profile_data.settings.vjoy_as_input
+ profile_data.settings.vjoy_as_input,
)
self.joy1_selector = ui_common.JoystickSelector(
- lambda x: change_cb(),
- [InputType.JoystickAxis]
+ lambda x: change_cb(), [InputType.JoystickAxis]
)
self.joy2_selector = ui_common.JoystickSelector(
- lambda x: change_cb(),
- [InputType.JoystickAxis]
+ lambda x: change_cb(), [InputType.JoystickAxis]
)
# Operation selection
@@ -219,9 +213,7 @@ def __init__(self, change_cb, profile_data, parent=None):
self.operation_selector.addItem("Minimum")
self.operation_selector.addItem("Maximum")
self.operation_selector.addItem("Sum")
- self.operation_selector.currentIndexChanged.connect(
- lambda x: change_cb()
- )
+ self.operation_selector.currentIndexChanged.connect(lambda x: change_cb())
# Mode selection
self.mode_container_widget = QtWidgets.QWidget()
@@ -275,14 +267,12 @@ def select(self, data):
"""
try:
self.vjoy_selector.set_selection(
- InputType.JoystickAxis,
- data["vjoy"]["vjoy_id"],
- data["vjoy"]["axis_id"]
+ InputType.JoystickAxis, data["vjoy"]["vjoy_id"], data["vjoy"]["axis_id"]
)
except gremlin.error.GremlinError as e:
gremlin.util.display_error(
- f"A needed vJoy device is not accessible: {e}\n\n" +
- "Default values have been set for the input, but they are "
+ f"A needed vJoy device is not accessible: {e}\n\n"
+ + "Default values have been set for the input, but they are "
"not what has been specified."
)
syslog.error(str(e))
@@ -292,14 +282,10 @@ def select(self, data):
joy2_id = data["upper"]["device_guid"]
self.joy1_selector.set_selection(
- InputType.JoystickAxis,
- joy1_id,
- data["lower"]["axis_id"]
+ InputType.JoystickAxis, joy1_id, data["lower"]["axis_id"]
)
self.joy2_selector.set_selection(
- InputType.JoystickAxis,
- joy2_id,
- data["upper"]["axis_id"]
+ InputType.JoystickAxis, joy2_id, data["upper"]["axis_id"]
)
if data["mode"] in self.mode_selector.mode_list:
self.mode_selector.setCurrentIndex(
@@ -307,7 +293,5 @@ def select(self, data):
)
self.operation_selector.setCurrentText(
- gremlin.types.MergeAxisOperation.to_string(
- data["operation"]
- ).capitalize()
+ gremlin.types.MergeAxisOperation.to_string(data["operation"]).capitalize()
)
diff --git a/gremlin/ui/midi_device.py b/gremlin/ui/midi_device.py
index d8ca9914..fbe6e07d 100644
--- a/gremlin/ui/midi_device.py
+++ b/gremlin/ui/midi_device.py
@@ -32,7 +32,7 @@
import mido
from gremlin.singleton_decorator import SingletonDecorator
-from gremlin.util import parse_guid, byte_list_to_string
+from gremlin.util import byte_list_to_string
import gremlin.event_handler
import gremlin.config
from gremlin.base_classes import AbstractInputItem
@@ -610,7 +610,7 @@ def stop(self):
# request stop
verbose = gremlin.config.Configuration().verbose_mode_details
if verbose:
- syslog.info(f"MIDI Interface: STOP listen requested")
+ syslog.info("MIDI Interface: STOP listen requested")
for port_number in self._monitored_ports:
@@ -688,7 +688,7 @@ def __init__(
self._interface.midi_message.connect(self._midi_message)
self._callback = callback
- if port_name and not port_name in self._interface.ports:
+ if port_name and port_name not in self._interface.ports:
syslog.error(f"MIDI listener: invalid port name: {port_name}")
self.close()
return
@@ -721,7 +721,7 @@ def __init__(
def _kb_event_cb(self, event):
- from gremlin.keyboard import key_from_code, key_from_name
+ from gremlin.keyboard import key_from_name
key = gremlin.keyboard.KeyMap.from_event(event)
if event.is_pressed and key == key_from_name("esc"):
@@ -1018,13 +1018,13 @@ def _update_display(self):
with QtCore.QSignalBlocker(self._mode_button_widget):
self._mode_button_widget.setChecked(True)
elif self._mode == MidiInputItem.InputMode.Axis:
- self._container_mode_description_widget.setText(f"The input act as an axis input using the MIDI value.
Use this mode if mapping to an axis output (MIDI value messages only)")
+ self._container_mode_description_widget.setText("The input act as an axis input using the MIDI value.
Use this mode if mapping to an axis output (MIDI value messages only)")
# turn off the value when in axis mode as it will change
display_b_data = False
with QtCore.QSignalBlocker(self._mode_axis_widget):
self._mode_axis_widget.setChecked(True)
elif self._mode == MidiInputItem.InputMode.OnChange:
- self._container_mode_description_widget.setText(f"The input will trigger a button press on any value change
Use this mode to trigger a button or action whenever the MIDI command value changes.")
+ self._container_mode_description_widget.setText("The input will trigger a button press on any value change
Use this mode to trigger a button or action whenever the MIDI command value changes.")
with QtCore.QSignalBlocker(self._mode_on_change_widget):
self._mode_on_change_widget.setChecked(True)
@@ -1155,7 +1155,7 @@ def _mode_axis_cb(self):
elif self._mode == MidiInputItem.InputMode.OnChange:
with QtCore.QSignalBlocker(self._mode_on_change_widget):
self._mode_on_change_widget.setChecked(True)
- self._validation_message_widget.setText(f"The current MIDI command cannot be setup as an axis input.")
+ self._validation_message_widget.setText("The current MIDI command cannot be setup as an axis input.")
warning_color = gremlin.ui.ui_common.Color.warningColor()
icon_color= QtGui.QColor(warning_color)
self._validation_message_widget.setIcon("ph.shield-warning-fill",True,color=icon_color)
@@ -1772,7 +1772,7 @@ def _update_conflicts(self):
conflicted_widgets.append(input_widget)
break
- ok_widgets = [widget for widget in widgets if not widget in conflicted_widgets]
+ ok_widgets = [widget for widget in widgets if widget not in conflicted_widgets]
for widget in ok_widgets:
self._set_status(widget)
@@ -1891,12 +1891,12 @@ def _profile_start(self):
if not self._started:
- if verbose: syslog.info(f"MIDI: Start")
+ if verbose: syslog.info("MIDI: Start")
self._start()
else:
- syslog.info(f"MIDI: Running")
+ syslog.info("MIDI: Running")
else:
- syslog.info(f"MIDI: no MIDI mappings found - start skipped")
+ syslog.info("MIDI: no MIDI mappings found - start skipped")
def registerInput(self, input_item):
@@ -1910,9 +1910,9 @@ def registerInput(self, input_item):
self._start()
message_key = input_item.message_key
- if not current_mode in self._midi_map:
+ if current_mode not in self._midi_map:
self._midi_map[current_mode] = {}
- if not message_key in self._midi_map[current_mode]:
+ if message_key not in self._midi_map[current_mode]:
self._midi_map[current_mode][message_key] = []
self._midi_map[current_mode][message_key].append(input_item)
@@ -1944,7 +1944,7 @@ def _update_messages(self):
if profile:
self._midi_map = {} # list of message keys
current_mode = gremlin.shared_state.current_mode
- if not current_mode in self._midi_map:
+ if current_mode not in self._midi_map:
self._midi_map[current_mode] = {}
if verbose: syslog.info("MIDI: reload map")
for device in profile.devices.values():
@@ -1954,7 +1954,7 @@ def _update_messages(self):
for input_item in input_items:
if isinstance(input_item, MidiInputItem):
message_key = input_item.message_key
- if not message_key in self._midi_map[current_mode]:
+ if message_key not in self._midi_map[current_mode]:
self._midi_map[current_mode][message_key] = []
if input_item.port_valid:
self._midi_map[current_mode][message_key].append(input_item)
diff --git a/gremlin/ui/mode_device.py b/gremlin/ui/mode_device.py
index c90c7ee7..21387ebc 100644
--- a/gremlin/ui/mode_device.py
+++ b/gremlin/ui/mode_device.py
@@ -1,8 +1,6 @@
-
-
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -18,43 +16,27 @@
# along with this program. If not, see .
from __future__ import annotations
-import logging
-from PySide6 import QtWidgets, QtCore, QtGui
-import threading
+from PySide6 import QtWidgets, QtCore
import gremlin.config
import gremlin.event_handler
-from gremlin.types import DeviceType
from gremlin.input_types import InputType
import gremlin.shared_state
-from gremlin.keyboard import Key
import gremlin.ui.joystick_device
-import uuid
-from gremlin.singleton_decorator import SingletonDecorator
-import collections
-import logging
-import re
-import time
-import logging
-from typing import Any, Iterator, List, Union
-import gremlin.ui.input_item
-import os
import gremlin.ui.input_item
from gremlin.util import *
-from lxml import etree as ElementTree
import enum
import gremlin.util
import gremlin.base_profile
-
-
class ModeInputModeType(enum.IntEnum):
- ''' possible input modes '''
+ """possible input modes"""
+
ModeEnter = 0 # executes on mode enter
- ModeExit = 1 # executes on mode exit
- ModeGlobalEnter = 2 # executes on any mode change (activate)
- ModeGlobalExit = 3 # executes on any mode change (deactivate)
+ ModeExit = 1 # executes on mode exit
+ ModeGlobalEnter = 2 # executes on any mode change (activate)
+ ModeGlobalExit = 3 # executes on any mode change (deactivate)
@staticmethod
def to_display_name(value):
@@ -67,25 +49,17 @@ def to_display_name(value):
return "Mode Activate (any)"
case ModeInputModeType.ModeGlobalExit:
return "Mode Deactivate (any)"
-
- return f"Unknown mode: {value}"
-
+ return f"Unknown mode: {value}"
class ModeDeviceTabWidget(gremlin.ui.ui_common.QSplitTabWidget):
+ """Widget used to configure mode change actions"""
- """Widget used to configure mode change actions """
-
# IMPORTANT: MUST BE A DID FORMATTED ID ON CUSTOM INPUTS
device_guid = gremlin.shared_state.mode_tab_guid
- def __init__(
- self,
- device_profile,
- current_mode,
- parent=None
- ):
+ def __init__(self, device_profile, current_mode, parent=None):
"""Creates a new object instance.
:param device_profile profile data of the entire device
@@ -93,7 +67,6 @@ def __init__(
:param parent the parent of this widget
"""
super().__init__(parent)
- import gremlin.ui.ui_common as ui_common
import gremlin.ui.input_item as input_item
# Store parameters
@@ -105,16 +78,17 @@ def __init__(
self.input_item_list_model = input_item.InputItemListModel(
device_profile,
current_mode,
- [InputType.ModeControl] # only allow Mode inputs for this widget
+ [InputType.ModeControl], # only allow Mode inputs for this widget
)
# create the two entries
self.ensureInputItems()
+ # update the display names
- # update the display names
-
- self.input_item_list_view = input_item.InputItemListView(custom_widget_handler=self._custom_widget_handler)
+ self.input_item_list_view = input_item.InputItemListView(
+ custom_widget_handler=self._custom_widget_handler
+ )
self.input_item_list_view.setMinimumWidth(350)
# Input type specific setups
@@ -130,123 +104,113 @@ def __init__(
self._item_data = gremlin.ui.joystick_device.InputItemConfiguration()
self.setRightPanelWidget(self._item_data)
-
self.input_item_list_model.refresh()
self.input_item_list_view.redraw()
-
-
el = gremlin.event_handler.EventListener()
el.mode_name_changed.connect(self._mode_name_changed)
- el.edit_mode_changed.connect(self._edit_mode_changed_cb) # edit mode changed or mode added/removed
-
+ el.edit_mode_changed.connect(
+ self._edit_mode_changed_cb
+ ) # edit mode changed or mode added/removed
# last index selected, -1 means none
- self._last_selected_index = -1
+ self._last_selected_index = -1
-
-
# Select default entry
selected_index = self.input_item_list_view.current_index
if selected_index is None:
selected_index = -1
self._select_item_cb(selected_index)
-
-
-
-
@QtCore.Slot(str)
- def _edit_mode_changed_cb(self, mode : str):
- ''' occurs when a new mode is selected '''
+ def _edit_mode_changed_cb(self, mode: str):
+ """occurs when a new mode is selected"""
self.set_mode(mode)
@QtCore.Slot(str)
def _mode_name_changed(self, name):
- ''' occurs when there's a mode name change '''
+ """occurs when there's a mode name change"""
self.input_item_list_view.redraw()
-
def _config_changed_cb(self):
- ''' called when configuraition has changed '''
- self.refresh()
-
+ """called when configuraition has changed"""
+ self.refresh()
def _custom_name_handler(self, input_item):
- ''' gets the custom name for the input item '''
- input_item : gremlin.base_profile.InputItem
+ """gets the custom name for the input item"""
+ input_item: gremlin.base_profile.InputItem
match input_item.input_id:
case ModeInputModeType.ModeEnter:
return f"Mode [{gremlin.shared_state.edit_mode}] Activate"
case ModeInputModeType.ModeExit:
return f"Mode [{gremlin.shared_state.edit_mode}] Deactivate"
case ModeInputModeType.ModeGlobalEnter:
- return f"Mode Activate (any)"
+ return "Mode Activate (any)"
case ModeInputModeType.ModeGlobalExit:
- return f"Mode Deactivate (any)"
-
- return f"Mode [{gremlin.shared_state.edit_mode}] Unknown id: {input_item.input_id}"
-
-
-
- def ensureInputItems(self, refresh = False):
- ''' ensures we have input items for the current mode
+ return "Mode Deactivate (any)"
+
+ return (
+ f"Mode [{gremlin.shared_state.edit_mode}] Unknown id: {input_item.input_id}"
+ )
+
+ def ensureInputItems(self, refresh=False):
+ """ensures we have input items for the current mode
:param refresh: True if list view should be updated if changes are made
- :returns: True if changes were made
+ :returns: True if changes were made
- '''
+ """
current_mode = gremlin.shared_state.edit_mode
self.device_profile.ensure_mode_exists(current_mode)
config = self.device_profile.modes[current_mode].config
-
+
changed = False
- if not ModeInputModeType.ModeEnter in config[InputType.ModeControl]:
+ if ModeInputModeType.ModeEnter not in config[InputType.ModeControl]:
modeEnter = gremlin.base_profile.InputItem(self._custom_name_handler)
modeEnter.input_id = ModeInputModeType.ModeEnter
modeEnter.device_name = "Mode"
modeEnter.input_type = InputType.ModeControl
modeEnter.device_guid = gremlin.shared_state.mode_tab_guid
- modeEnter.description="Enter mode actions"
+ modeEnter.description = "Enter mode actions"
config[InputType.ModeControl][ModeInputModeType.ModeEnter] = modeEnter
changed = True
- config[InputType.ModeControl][ModeInputModeType.ModeEnter].descriptionReadOnly = True
+ config[InputType.ModeControl][
+ ModeInputModeType.ModeEnter
+ ].descriptionReadOnly = True
-
- if not ModeInputModeType.ModeExit in config[InputType.ModeControl]:
+ if ModeInputModeType.ModeExit not in config[InputType.ModeControl]:
modeExit = gremlin.base_profile.InputItem(self._custom_name_handler)
modeExit.device_name = "Mode"
modeExit.device_guid = gremlin.shared_state.mode_tab_guid
modeExit.input_type = InputType.ModeControl
modeExit.input_id = ModeInputModeType.ModeExit
- modeExit.description="Exit mode actions"
+ modeExit.description = "Exit mode actions"
modeExit.descriptionReadonly = True
config[InputType.ModeControl][ModeInputModeType.ModeExit] = modeExit
changed = True
- config[InputType.ModeControl][ModeInputModeType.ModeExit].descriptionReadOnly = True
-
+ config[InputType.ModeControl][
+ ModeInputModeType.ModeExit
+ ].descriptionReadOnly = True
if changed or refresh:
- self.input_item_list_model.refresh()
+ self.input_item_list_model.refresh()
- return changed
+ return changed
def itemAt(self, index):
- ''' returns the input widget at the given index '''
+ """returns the input widget at the given index"""
return self.input_item_list_view.itemAt(index)
def display_name(self, input_id):
- ''' returns the name for the given input ID '''
+ """returns the name for the given input ID"""
return input_id.display_name
-
def _index_for_key(self, input_id):
- ''' returns the index of the selected input id'''
+ """returns the index of the selected input id"""
current_mode = gremlin.shared_state.edit_mode
mode = self.device_profile.modes[current_mode]
sorted_keys = list(mode.config[InputType.ModeControl].keys())
return sorted_keys.index(input_id)
-
def _select_item_cb(self, index):
"""Handles the selection of an input item.
@@ -254,8 +218,7 @@ def _select_item_cb(self, index):
:param index the index of the selected item
"""
-
- self.ensureInputItems(True) # ensure the control inputs exist for this mode
+ self.ensureInputItems(True) # ensure the control inputs exist for this mode
if index == -1:
index = self._last_selected_index
@@ -265,14 +228,15 @@ def _select_item_cb(self, index):
if self.input_item_list_model.rows():
index = 0
else:
- return
-
+ return
+
with QtCore.QSignalBlocker(self.input_item_list_view):
self.input_item_list_view.select_item(index, False)
-
-
- input_data : gremlin.base_profile.InputItem = self.input_item_list_model.data(index)
-
+
+ input_data: gremlin.base_profile.InputItem = self.input_item_list_model.data(
+ index
+ )
+
widget = gremlin.ui.joystick_device.InputItemConfiguration(input_data)
self._item_data = widget
self.setRightPanelWidget(widget)
@@ -282,39 +246,44 @@ def _select_item_cb(self, index):
device_guid = self.device_guid
input_type = InputType.OpenSoundControl
input_id = input_data.input_id if input_data else None
-
config.set_last_input(device_guid, input_type, input_id)
if input_data:
-
# Create new configuration widget
input_data.is_axis = False
change_cb = self._create_change_cb(index)
self._item_data.action_model.data_changed.connect(change_cb)
self._item_data.description_changed.connect(change_cb)
- self.input_item_list_view.select_item(index,False)
-
+ self.input_item_list_view.select_item(index, False)
self._last_selected_index = index
el = gremlin.event_handler.EventListener()
el.input_selection_changed.emit(device_guid, input_type, input_id)
- def _custom_widget_handler(self, list_view, index : int, identifier, data, parent = None):
- ''' creates a widget for the input
-
+ def _custom_widget_handler(
+ self, list_view, index: int, identifier, data, parent=None
+ ):
+ """creates a widget for the input
+
the widget must have a selected property
:param list_view The list view control the widget to create belongs to
:param index The index in the list starting at 0 being the top item
:param identifier the InpuIdentifier for the input list
:param data the data associated with this input item
-
- '''
+
+ """
import gremlin.ui.input_item
- widget = gremlin.ui.input_item.InputItemWidget(identifier = identifier, populate_ui_callback = self._populate_input_widget_ui, update_callback = self._update_input_widget, config_external=True, parent = parent)
- data : gremlin.base_profile.InputItem
+ widget = gremlin.ui.input_item.InputItemWidget(
+ identifier=identifier,
+ populate_ui_callback=self._populate_input_widget_ui,
+ update_callback=self._update_input_widget,
+ config_external=True,
+ parent=parent,
+ )
+ data: gremlin.base_profile.InputItem
widget.data = data
widget.create_action_icons(data)
widget.setTitle(self._custom_name_handler(data))
@@ -323,52 +292,38 @@ def _custom_widget_handler(self, list_view, index : int, identifier, data, paren
widget.disable_edit()
widget.setIcon("fa5.edit")
-
-
# remember what widget is at what index
widget.index = index
return widget
-
-
- def _set_status(self, widget, icon = None, status = None, use_qta = True, color = None):
- ''' sets the status of an input widget '''
+ def _set_status(self, widget, icon=None, status=None, use_qta=True, color=None):
+ """sets the status of an input widget"""
status_widget = widget.findChild(gremlin.ui.ui_common.QIconLabel, "status")
if color:
- status_widget.setIcon(icon, use_qta = use_qta, color = color)
+ status_widget.setIcon(icon, use_qta=use_qta, color=color)
else:
- status_widget.setIcon(icon, use_qta = use_qta)
-
- status_widget.setText(status)
- status_widget.setVisible(status is not None)
-
-
-
+ status_widget.setIcon(icon, use_qta=use_qta)
+ status_widget.setText(status)
+ status_widget.setVisible(status is not None)
def _update_input_widget(self, input_widget, container_widget):
- ''' called when the widget has to update itself on a data change '''
+ """called when the widget has to update itself on a data change"""
pass
-
def _populate_input_widget_ui(self, input_widget, container_widget, data):
- ''' called when a button is created for custom content '''
+ """called when a button is created for custom content"""
layout = QtWidgets.QVBoxLayout(container_widget)
status_widget = gremlin.ui.ui_common.QIconLabel()
status_widget.setObjectName("status")
layout.addWidget(status_widget)
self._update_input_widget(input_widget, container_widget)
-
-
-
-
def _index_for_key(self, input_id):
- ''' returns the index of the selected input id'''
+ """returns the index of the selected input id"""
mode = self.device_profile.modes[self.current_mode]
sorted_keys = list(mode.config[InputType.ModeControl].keys())
return sorted_keys.index(input_id)
-
def _create_change_cb(self, index):
"""Creates a callback handling content changes.
@@ -379,20 +334,18 @@ def _create_change_cb(self, index):
return lambda: self.input_item_list_view.redraw_index(index)
def set_mode(self, mode):
- ''' changes the mode of the tab '''
+ """changes the mode of the tab"""
self.current_mode = mode
self.ensureInputItems()
self.input_item_list_model.mode = mode
-
- #self.input_item_list_view.select_item(-1)
+
+ # self.input_item_list_view.select_item(-1)
if gremlin.shared_state.isDeviceTabActive(self.device_guid):
self.input_item_list_model.refresh()
- self.input_item_list_view.redraw()
+ self.input_item_list_view.redraw()
self._select_item_cb(self._last_selected_index)
-
-
def refresh(self):
"""Refreshes the current selection, ensuring proper synchronization."""
- self.set_mode(gremlin.shared_state.edit_mode) # force a model and reload
- #self._select_item_cb(self.input_item_list_view.current_index)
+ self.set_mode(gremlin.shared_state.edit_mode) # force a model and reload
+ # self._select_item_cb(self.input_item_list_view.current_index)
diff --git a/gremlin/ui/osc_device.py b/gremlin/ui/osc_device.py
index 64cf5084..a209cef4 100644
--- a/gremlin/ui/osc_device.py
+++ b/gremlin/ui/osc_device.py
@@ -26,35 +26,26 @@
import gremlin.config
import gremlin.event_handler
import gremlin.input_devices
-import gremlin.input_devices
-import gremlin.shared_state
import gremlin.shared_state
-from gremlin.types import DeviceType
from gremlin.input_types import InputType
-import gremlin.shared_state
-from gremlin.keyboard import Key
import gremlin.ui.joystick_device
import gremlin.base_profile
import uuid
from gremlin.singleton_decorator import SingletonDecorator
import collections
-import logging
import re
import time
-from typing import overload, List, Union, Any, Generator, Tuple, Callable, Optional, DefaultDict, Iterator, Union, cast, Coroutine, NamedTuple
-import logging
-from typing import Any, Iterator, List, Union
+from typing import overload, List, Any, Tuple, Callable, Optional, Iterator, Union, cast, Coroutine, NamedTuple
import asyncio
from asyncio import BaseEventLoop
import socketserver
import socket
from socket import socket as _socket
-import sys
import os
from collections.abc import Iterable
import struct
-from datetime import datetime, timedelta, date
+from datetime import datetime, timedelta
import gremlin.ui.ui_common
from gremlin.util import *
@@ -63,8 +54,6 @@
import enum
from gremlin.base_classes import AbstractInputItem
import gremlin.util
-import vjoy
-import vjoy.vjoy
syslog = logging.getLogger("system")
@@ -1793,7 +1782,6 @@ def __init__(self):
@QtCore.Slot(bool)
def _request_osc_state(self, state : bool):
- from gremlin.input_types import InputType
if state:
self.start()
@@ -1838,7 +1826,7 @@ def _output_server_changed(self):
def getClient(self, server : str, port : int, name : str = None) -> OscClient:
''' gets the client for that server/port '''
key = (server, port)
- if not key in self._client_pool:
+ if key not in self._client_pool:
client = OscClient(server, port, name)
self._client_pool[key] = client
verbose = gremlin.config.Configuration().verbose_mode_osc
@@ -2770,14 +2758,14 @@ def _validate(self):
# return
if self._min_range > self._max_range:
- self._validation_message_widget.setText(f"Min range must be less than max range")
+ self._validation_message_widget.setText("Min range must be less than max range")
warning_color = gremlin.ui.ui_common.Color.warningColor()
icon_color= QtGui.QColor(warning_color)
self._validation_message_widget.setIcon("ph.shield-warning-fill",True, color=icon_color)
return
if self._min_range == self._max_range:
- self._validation_message_widget.setText(f"Min range cannot be the same as the max range")
+ self._validation_message_widget.setText("Min range cannot be the same as the max range")
warning_color = gremlin.ui.ui_common.Color.warningColor()
icon_color= QtGui.QColor(warning_color)
self._validation_message_widget.setIcon("ph.shield-warning-fill",True, color=icon_color)
@@ -2788,7 +2776,7 @@ def _validate(self):
self._command_data = [0]
arg = self._command_data[0]
if not (isinstance(arg, int) or isinstance(arg, float)):
- self._validation_message_widget.setText(f"First data item must be a number for axis input")
+ self._validation_message_widget.setText("First data item must be a number for axis input")
warning_color = gremlin.ui.ui_common.Color.warningColor()
icon_color= QtGui.QColor(warning_color)
self._validation_message_widget.setIcon("ph.shield-warning-fill",True, color=icon_color)
@@ -2881,7 +2869,7 @@ def source_index(self, value : int):
def _set_parameter(self, index, value):
''' sets a data parameter - if the index does not exist, it's created '''
- if not index in self._data_widgets:
+ if index not in self._data_widgets:
widget = gremlin.ui.ui_common.QDataLineEdit()
widget.setReadOnly(True)
widget.data = index
@@ -2939,30 +2927,30 @@ def _update_display(self):
autorelease_visible = False
parameters_visible = True
if self._mode == OscInputItem.InputMode.Button:
- self._container_mode_description_widget.setText(f"The input will trigger a button press when the value is 1
Use this to trigger a button press from a specific OSC message.")
+ self._container_mode_description_widget.setText("The input will trigger a button press when the value is 1
Use this to trigger a button press from a specific OSC message.")
with QtCore.QSignalBlocker(self._mode_button_widget):
self._mode_button_widget.setChecked(True)
autorelease_visible = True
parameters_visible = False
elif self._mode == OscInputItem.InputMode.Axis:
- self._container_mode_description_widget.setText(f"The input act as an axis input using the OSC value.
Use this mode if mapping to an axis output (OSC value messages only)")
+ self._container_mode_description_widget.setText("The input act as an axis input using the OSC value.
Use this mode if mapping to an axis output (OSC value messages only)")
#self._command_mode = OscInputItem.CommandMode.Message # force message mode in axis as the value will determine the state
with QtCore.QSignalBlocker(self._mode_axis_widget):
self._mode_axis_widget.setChecked(True)
elif self._mode == OscInputItem.InputMode.OnChange:
- self._container_mode_description_widget.setText(f"The input will trigger a button press on any value change
Use this mode to trigger a button or action whenever the OSC command value changes.")
+ self._container_mode_description_widget.setText("The input will trigger a button press on any value change
Use this mode to trigger a button or action whenever the OSC command value changes.")
with QtCore.QSignalBlocker(self._mode_on_change_widget):
self._mode_on_change_widget.setChecked(True)
if self._command_mode == OscInputItem.CommandMode.Message:
- self._container_command_mode_description_widget.setText(f"The OSC message is the primary input (data ignored)")
+ self._container_command_mode_description_widget.setText("The OSC message is the primary input (data ignored)")
with QtCore.QSignalBlocker(self._command_mode_message_widget):
self._command_mode_message_widget.setChecked(True)
self._data_container_widget.setEnabled(False) # disable the value area if in message only mode
elif self._command_mode == OscInputItem.CommandMode.Data:
- self._container_command_mode_description_widget.setText(f"The OSC message and arguments are used as the primary input")
+ self._container_command_mode_description_widget.setText("The OSC message and arguments are used as the primary input")
with QtCore.QSignalBlocker(self._command_mode_data_widget):
self._command_mode_data_widget.setChecked(True)
self._data_container_widget.setEnabled(True) # enable the value area if in message + data mode
@@ -3365,7 +3353,7 @@ def _update_conflicts(self):
conflicted_widgets.append(input_widget)
break
- ok_widgets = [widget for widget in widgets if not widget in conflicted_widgets]
+ ok_widgets = [widget for widget in widgets if widget not in conflicted_widgets]
for widget in ok_widgets:
self._set_status(widget)
@@ -3549,12 +3537,12 @@ def _profile_start(self):
syslog.info(f"\t{input_item.display_name} key: [{input_item.message_key}] input mode: [{item_mode}]")
if not self._started:
- if verbose: syslog.info(f"OSC: Start")
+ if verbose: syslog.info("OSC: Start")
self.start()
else:
- syslog.info(f"OSC: Running")
+ syslog.info("OSC: Running")
else:
- syslog.info(f"OSC: no OSC mappings found - start skipped")
+ syslog.info("OSC: no OSC mappings found - start skipped")
@QtCore.Slot(bool)
def _request_osc_state(self, state : bool):
@@ -3572,10 +3560,10 @@ def registerInput(self, input_item):
self._start()
message_key = input_item.message_key
- if not message_key in self._osc_map.keys():
+ if message_key not in self._osc_map.keys():
self._osc_map[message_key] = []
- if not input_item in self._osc_map[message_key]:
+ if input_item not in self._osc_map[message_key]:
self._osc_map[message_key].append(input_item)
if self._verbose:
syslog.info(f"OSC: register trigger on: {input_item.display_name} source index: {input_item.source_index} mode: {input_item.mode_string} key: {message_key}")
@@ -3620,7 +3608,6 @@ def _start(self):
def _update_messages(self):
''' refresh OSC message we're listening to '''
- from gremlin.ui.osc_device import OscInputItem
self._osc_map = {} # list of message keys
profile = gremlin.shared_state.current_profile
if profile:
diff --git a/gremlin/ui/profile.py b/gremlin/ui/profile.py
index fa229a2a..6e55b06c 100644
--- a/gremlin/ui/profile.py
+++ b/gremlin/ui/profile.py
@@ -42,7 +42,6 @@
@QtQml.QmlElement
class InputItemModel(QtCore.QObject):
-
"""QML model class representing a LibraryItem instance."""
bindingsChanged = Signal()
@@ -54,10 +53,7 @@ def __init__(self, input_item: profile.InputItem, parent=None):
@Property(QtCore.QObject, notify=bindingsChanged)
def inputItemBindings(self) -> InputItemBindingListModel:
- return InputItemBindingListModel(
- self._input_item.action_configurations,
- self
- )
+ return InputItemBindingListModel(self._input_item.action_configurations, self)
@Slot(str, str, str)
def dropAction(self, source: str, target: str, method: str) -> None:
@@ -85,14 +81,13 @@ def dropAction(self, source: str, target: str, method: str) -> None:
for idx, entry in enumerate(self._input_item.action_configurations):
entry_id = entry.library_reference.action_tree.root.value.id
if entry_id == target_id:
- self._input_item.action_configurations.insert(idx+1, source_entry)
+ self._input_item.action_configurations.insert(idx + 1, source_entry)
self.bindingsChanged.emit()
@QtQml.QmlElement
class InputItemBindingListModel(QtCore.QAbstractListModel):
-
"""List model of all InputItemBinding instances of a single input item."""
# This fake single role and the roleName function are needed to have the
@@ -106,13 +101,12 @@ def __init__(self, items: List[profile.InputItemBinding], parent=None):
self._action_configurations = items
- def rowCount(self, parent: QtCore.QModelIndex=...) -> int:
+ def rowCount(self, parent: QtCore.QModelIndex = ...) -> int:
return len(self._action_configurations)
- def data(self, index: QtCore.QModelIndex, role: int=...) -> typing.Any:
+ def data(self, index: QtCore.QModelIndex, role: int = ...) -> typing.Any:
return InputItemBindingModel(
- self._action_configurations[index.row()],
- parent=self
+ self._action_configurations[index.row()], parent=self
)
def roleNames(self) -> typing.Dict:
@@ -121,7 +115,6 @@ def roleNames(self) -> typing.Dict:
@QtQml.QmlElement
class VirtualButtonModel(QtCore.QObject):
-
"""Represents both axis and hat virtual buttons."""
lowerLimitChanged = Signal()
@@ -133,7 +126,7 @@ class VirtualButtonModel(QtCore.QObject):
def __init__(
self,
virtual_button: profile.AbstractVirtualButton,
- parent: Optional[QtCore.QObject]=None
+ parent: Optional[QtCore.QObject] = None,
):
"""Creates a new instance.
@@ -193,92 +186,93 @@ def _set_hat_state(self, hat_direction, is_active):
self.hatDirectionChanged.emit()
lowerLimit = Property(
- float,
- fget=_get_lower_limit,
- fset=_set_lower_limit,
- notify=lowerLimitChanged
+ float, fget=_get_lower_limit, fset=_set_lower_limit, notify=lowerLimitChanged
)
upperLimit = Property(
- float,
- fget=_get_upper_limit,
- fset=_set_upper_limit,
- notify=upperLimitChanged
+ float, fget=_get_upper_limit, fset=_set_upper_limit, notify=upperLimitChanged
)
direction = Property(
- str,
- fget=_get_direction,
- fset=_set_direction,
- notify=directionChanged
+ str, fget=_get_direction, fset=_set_direction, notify=directionChanged
)
enabled = Property(
- bool,
- fget=_get_enabled,
- fset=_set_enabled,
- notify=enabledChanged
+ bool, fget=_get_enabled, fset=_set_enabled, notify=enabledChanged
)
hatNorth = Property(
bool,
fget=lambda cls: VirtualButtonModel._get_hat_state(cls, HatDirection.North),
- fset=lambda cls, x: VirtualButtonModel._set_hat_state(cls, HatDirection.North, x),
- notify=hatDirectionChanged
+ fset=lambda cls, x: VirtualButtonModel._set_hat_state(
+ cls, HatDirection.North, x
+ ),
+ notify=hatDirectionChanged,
)
hatNorthEast = Property(
bool,
fget=lambda cls: VirtualButtonModel._get_hat_state(cls, HatDirection.NorthEast),
- fset=lambda cls, x: VirtualButtonModel._set_hat_state(cls, HatDirection.NorthEast, x),
- notify=hatDirectionChanged
+ fset=lambda cls, x: VirtualButtonModel._set_hat_state(
+ cls, HatDirection.NorthEast, x
+ ),
+ notify=hatDirectionChanged,
)
hatEast = Property(
bool,
fget=lambda cls: VirtualButtonModel._get_hat_state(cls, HatDirection.East),
- fset=lambda cls, x: VirtualButtonModel._set_hat_state(cls, HatDirection.East, x),
- notify=hatDirectionChanged
+ fset=lambda cls, x: VirtualButtonModel._set_hat_state(
+ cls, HatDirection.East, x
+ ),
+ notify=hatDirectionChanged,
)
hatSouthEast = Property(
bool,
fget=lambda cls: VirtualButtonModel._get_hat_state(cls, HatDirection.SouthEast),
- fset=lambda cls, x: VirtualButtonModel._set_hat_state(cls, HatDirection.SouthEast, x),
- notify=hatDirectionChanged
+ fset=lambda cls, x: VirtualButtonModel._set_hat_state(
+ cls, HatDirection.SouthEast, x
+ ),
+ notify=hatDirectionChanged,
)
hatSouth = Property(
bool,
fget=lambda cls: VirtualButtonModel._get_hat_state(cls, HatDirection.South),
- fset=lambda cls, x: VirtualButtonModel._set_hat_state(cls, HatDirection.South, x),
- notify=hatDirectionChanged
+ fset=lambda cls, x: VirtualButtonModel._set_hat_state(
+ cls, HatDirection.South, x
+ ),
+ notify=hatDirectionChanged,
)
hatSouthWest = Property(
bool,
fget=lambda cls: VirtualButtonModel._get_hat_state(cls, HatDirection.SouthWest),
- fset=lambda cls, x: VirtualButtonModel._set_hat_state(cls, HatDirection.SouthWest, x),
- notify=hatDirectionChanged
+ fset=lambda cls, x: VirtualButtonModel._set_hat_state(
+ cls, HatDirection.SouthWest, x
+ ),
+ notify=hatDirectionChanged,
)
hatWest = Property(
bool,
fget=lambda cls: VirtualButtonModel._get_hat_state(cls, HatDirection.West),
- fset=lambda cls, x: VirtualButtonModel._set_hat_state(cls, HatDirection.West, x),
- notify=hatDirectionChanged
+ fset=lambda cls, x: VirtualButtonModel._set_hat_state(
+ cls, HatDirection.West, x
+ ),
+ notify=hatDirectionChanged,
)
hatNorthWest = Property(
bool,
fget=lambda cls: VirtualButtonModel._get_hat_state(cls, HatDirection.NorthWest),
- fset=lambda cls, x: VirtualButtonModel._set_hat_state(cls, HatDirection.NorthWest, x),
- notify=hatDirectionChanged
+ fset=lambda cls, x: VirtualButtonModel._set_hat_state(
+ cls, HatDirection.NorthWest, x
+ ),
+ notify=hatDirectionChanged,
)
@QtQml.QmlElement
class HatDirectionModel(QtCore.QObject):
-
"""QML model representing the directions of a hat."""
directionsChanged = Signal()
def __init__(
- self,
- directions: List[HatDirection],
- parent: Optional[QtCore.QObject]=None
+ self, directions: List[HatDirection], parent: Optional[QtCore.QObject] = None
):
super().__init__(parent)
@@ -301,66 +295,77 @@ def _set_hat_state(self, direction: HatDirection, is_active: bool) -> None:
hatNorth = Property(
bool,
fget=lambda cls: HatDirectionModel._get_hat_state(cls, HatDirection.North),
- fset=lambda cls, x: HatDirectionModel._set_hat_state(cls, HatDirection.North, x),
- notify=directionsChanged
+ fset=lambda cls, x: HatDirectionModel._set_hat_state(
+ cls, HatDirection.North, x
+ ),
+ notify=directionsChanged,
)
hatNorthEast = Property(
bool,
fget=lambda cls: HatDirectionModel._get_hat_state(cls, HatDirection.NorthEast),
- fset=lambda cls, x: HatDirectionModel._set_hat_state(cls, HatDirection.NorthEast, x),
- notify=directionsChanged
+ fset=lambda cls, x: HatDirectionModel._set_hat_state(
+ cls, HatDirection.NorthEast, x
+ ),
+ notify=directionsChanged,
)
hatEast = Property(
bool,
fget=lambda cls: HatDirectionModel._get_hat_state(cls, HatDirection.East),
fset=lambda cls, x: HatDirectionModel._set_hat_state(cls, HatDirection.East, x),
- notify=directionsChanged
+ notify=directionsChanged,
)
hatSouthEast = Property(
bool,
fget=lambda cls: HatDirectionModel._get_hat_state(cls, HatDirection.SouthEast),
- fset=lambda cls, x: HatDirectionModel._set_hat_state(cls, HatDirection.SouthEast, x),
- notify=directionsChanged
+ fset=lambda cls, x: HatDirectionModel._set_hat_state(
+ cls, HatDirection.SouthEast, x
+ ),
+ notify=directionsChanged,
)
hatSouth = Property(
bool,
fget=lambda cls: HatDirectionModel._get_hat_state(cls, HatDirection.South),
- fset=lambda cls, x: HatDirectionModel._set_hat_state(cls, HatDirection.South, x),
- notify=directionsChanged
+ fset=lambda cls, x: HatDirectionModel._set_hat_state(
+ cls, HatDirection.South, x
+ ),
+ notify=directionsChanged,
)
hatSouthWest = Property(
bool,
fget=lambda cls: HatDirectionModel._get_hat_state(cls, HatDirection.SouthWest),
- fset=lambda cls, x: HatDirectionModel._set_hat_state(cls, HatDirection.SouthWest, x),
- notify=directionsChanged
+ fset=lambda cls, x: HatDirectionModel._set_hat_state(
+ cls, HatDirection.SouthWest, x
+ ),
+ notify=directionsChanged,
)
hatWest = Property(
bool,
fget=lambda cls: HatDirectionModel._get_hat_state(cls, HatDirection.West),
fset=lambda cls, x: HatDirectionModel._set_hat_state(cls, HatDirection.West, x),
- notify=directionsChanged
+ notify=directionsChanged,
)
hatNorthWest = Property(
bool,
fget=lambda cls: HatDirectionModel._get_hat_state(cls, HatDirection.NorthWest),
- fset=lambda cls, x: HatDirectionModel._set_hat_state(cls, HatDirection.NorthWest, x),
- notify=directionsChanged
+ fset=lambda cls, x: HatDirectionModel._set_hat_state(
+ cls, HatDirection.NorthWest, x
+ ),
+ notify=directionsChanged,
)
@QtQml.QmlElement
class ActionNodeModel(QtCore.QObject):
-
"""QML model representing a single action instance."""
actionChanged = Signal()
def __init__(
- self,
- node: tree.TreeNode,
- input_type: InputType,
- action_tree: ActionTree,
- parent=None
+ self,
+ node: tree.TreeNode,
+ input_type: InputType,
+ action_tree: ActionTree,
+ parent=None,
):
super().__init__(parent)
@@ -381,6 +386,7 @@ def action_tree(self) -> ActionTree:
return self._action_tree
Property(type=InputType, notify=actionChanged)
+
def inputType(self) -> InputType:
return self._input_type
@@ -468,8 +474,7 @@ def _append_drop_action(self, source: str, target: str) -> None:
# Perform reordering on the parent node level if needed
source_node.parent.value.remove_action(source_node.value)
target_node.parent.value.add_action_after(
- target_node.value,
- source_node.value
+ target_node.value, source_node.value
)
# Perform reordering on the logical tree level
@@ -489,8 +494,7 @@ def appendNewAction(self, action_name: str) -> None:
action_name: name of the action to add
"""
action = plugin_manager.ActionPlugins().get_class(action_name)(
- self._action_tree,
- self._input_type
+ self._action_tree, self._input_type
)
if self._node.parent is None:
TreeNode(action, self._node)
@@ -552,7 +556,10 @@ def _find_node_with_id(self, uuid: uuid.UUID) -> tree.TreeNode:
Returns:
The TreeNode corresponding to the given uuid
"""
- predicate = lambda x: True if x.value and x.value.id == uuid else False
+ def predicate(x):
+ return True if x.value and x.value.id == uuid else False
+
+ predicate = predicate(uuid)
nodes = self._action_tree.root.nodes_matching(predicate)
if len(nodes) != 1:
@@ -562,7 +569,6 @@ def _find_node_with_id(self, uuid: uuid.UUID) -> tree.TreeNode:
@QtQml.QmlElement
class InputItemBindingModel(QtCore.QObject):
-
"""Model representing an ActionTree instance."""
behaviorChanged = Signal()
@@ -572,11 +578,7 @@ class InputItemBindingModel(QtCore.QObject):
rootActionChanged = Signal()
inputTypeChanged = Signal()
- def __init__(
- self,
- input_item_binding: profile.InputItemBinding,
- parent=None
- ):
+ def __init__(self, input_item_binding: profile.InputItemBinding, parent=None):
super().__init__(parent)
self._input_item_binding = input_item_binding
@@ -591,7 +593,7 @@ def rootNode(self) -> ActionNodeModel:
self._action_tree.root,
self._input_item_binding.behavior,
self._action_tree,
- parent=self
+ parent=self,
)
@Property(type=list, notify=rootActionChanged)
@@ -599,19 +601,19 @@ def topLevelNodes(self) -> List[ActionNodeModel]:
nodes = []
for node in self._action_tree.root.children:
node.value.setParent(self)
- nodes.append(ActionNodeModel(
- node,
- self._input_item_binding.behavior,
- self._action_tree,
- parent=self
- ))
+ nodes.append(
+ ActionNodeModel(
+ node,
+ self._input_item_binding.behavior,
+ self._action_tree,
+ parent=self,
+ )
+ )
return nodes
@Property(type=str, notify=inputTypeChanged)
def inputType(self) -> str:
- return InputType.to_string(
- self._input_item_binding.input_item.input_type
- )
+ return InputType.to_string(self._input_item_binding.input_item.input_type)
@Property(type=VirtualButtonModel, notify=virtualButtonChanged)
def virtualButton(self) -> VirtualButtonModel:
@@ -641,25 +643,27 @@ def _set_behavior(self, text: str) -> None:
# Ensure a virtual button instance exists of the correct type
# if one is needed
input_type = self._input_item_binding.input_item.input_type
- if input_type == InputType.JoystickAxis and \
- behavior == InputType.JoystickButton:
+ if (
+ input_type == InputType.JoystickAxis
+ and behavior == InputType.JoystickButton
+ ):
if not isinstance(
- self._input_item_binding.virtual_button,
- profile.VirtualAxisButton
+ self._input_item_binding.virtual_button, profile.VirtualAxisButton
):
- self._input_item_binding.virtual_button = \
+ self._input_item_binding.virtual_button = (
profile.VirtualAxisButton()
+ )
self._virtual_button_model = VirtualButtonModel(
self._input_item_binding.virtual_button
)
- elif input_type == InputType.JoystickHat and \
- behavior == InputType.JoystickButton:
+ elif (
+ input_type == InputType.JoystickHat
+ and behavior == InputType.JoystickButton
+ ):
if not isinstance(
- self._input_item_binding.virtual_button,
- profile.VirtualHatButton
+ self._input_item_binding.virtual_button, profile.VirtualHatButton
):
- self._input_item_binding.virtual_button = \
- profile.VirtualHatButton()
+ self._input_item_binding.virtual_button = profile.VirtualHatButton()
self._virtual_button_model = VirtualButtonModel(
self._input_item_binding.virtual_button
)
@@ -692,15 +696,9 @@ def action_tree(self):
return self._action_tree
behavior = Property(
- str,
- fget=_get_behavior,
- fset=_set_behavior,
- notify=behaviorChanged
+ str, fget=_get_behavior, fset=_set_behavior, notify=behaviorChanged
)
description = Property(
- str,
- fget=_get_description,
- fset=_set_description,
- notify=descriptionChanged
+ str, fget=_get_description, fset=_set_description, notify=descriptionChanged
)
diff --git a/gremlin/ui/profile_creator.py b/gremlin/ui/profile_creator.py
index 12507c2a..e182496a 100644
--- a/gremlin/ui/profile_creator.py
+++ b/gremlin/ui/profile_creator.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -30,8 +30,8 @@
# TODO: Retire this entire bit here as the action library will replace it
# in it's entirety
-class ProfileCreator(gremlin.ui.ui_common.BaseDialogUi):
+class ProfileCreator(gremlin.ui.ui_common.BaseDialogUi):
"""Displays a dialog to create a new profile from an existing one.
This dialog shows all actions present in an existing profile and allows
@@ -44,7 +44,7 @@ def __init__(self, profile_data, parent=None):
:param profile_data the data to use as the template
:param parent the parent widget of this one
"""
- super().__init__(self.__class__.__name__, parent)
+ super().__init__(self.__class__.__name__, parent)
self.profile_data = profile_data
self.new_profile = self._create_empty_profile()
@@ -75,9 +75,10 @@ def update_binding(self, mode, input_item, event):
# Add a new binding
else:
# Get the input item to be mapped then modify it and set it again
- item = self.new_profile.devices[event.device_guid].modes[mode].get_data(
- event.event_type,
- event.identifier
+ item = (
+ self.new_profile.devices[event.device_guid]
+ .modes[mode]
+ .get_data(event.event_type, event.identifier)
)
item.containers = copy.deepcopy(input_item.containers)
@@ -85,9 +86,7 @@ def update_binding(self, mode, input_item, event):
item.description = input_item.description
self.new_profile.devices[event.device_guid].modes[mode].set_data(
- item.input_type,
- item.input_id,
- item
+ item.input_type, item.input_id, item
)
self._binding_registry[input_item] = item
@@ -122,18 +121,17 @@ def _create_ui(self):
self._binding_registry,
mode,
self.update_binding,
- self.toolbox
+ self.toolbox,
),
- mode
+ mode,
)
-
# Create the help text indicating how to use the template tool
self.help_text = QtWidgets.QLabel(
"To map an input to an action, left click on the corresponding "
"button and then moved or press the desired physical input."
"To delete a mapping simply right click on the corresponding "
- "button. When done click on the \"Save\" button to save a "
+ 'button. When done click on the "Save" button to save a '
"new profile."
)
self.help_text.setWordWrap(True)
@@ -154,10 +152,7 @@ def save_profile(self):
stores it.
"""
fname, _ = QtWidgets.QFileDialog.getSaveFileName(
- None,
- "Save Profile",
- util.userprofile_path(),
- "XML files (*.xml)"
+ None, "Save Profile", util.userprofile_path(), "XML files (*.xml)"
)
if fname != "":
self.new_profile.to_xml(fname)
@@ -184,17 +179,10 @@ def _create_empty_profile(self):
class ModeBindings(QtWidgets.QWidget):
-
"""Allows binding of inputs to actions of a particular mode."""
def __init__(
- self,
- profile_data,
- new_profile,
- bound_inputs,
- mode,
- update_cb,
- parent=None
+ self, profile_data, new_profile, bound_inputs, mode, update_cb, parent=None
):
"""Creates a new instance.
@@ -223,14 +211,13 @@ def _create_ui(self):
common.InputType.Keyboard,
common.InputType.JoystickAxis,
common.InputType.JoystickButton,
- common.InputType.JoystickHat
+ common.InputType.JoystickHat,
]
# Find all input items associated with this mode and show them as
# a bindable item in the UI
sorted_devices = sorted(
- self.profile_data.devices.values(),
- key=lambda x: x.name
+ self.profile_data.devices.values(), key=lambda x: x.name
)
for device in sorted_devices:
for input_type in all_input_types:
@@ -239,11 +226,13 @@ def _create_ui(self):
if len(input_item.containers) == 0:
continue
- self._inputs.append(BindableAction(
- input_item,
- self._get_bound_to_string(input_item),
- self._create_input_cb(input_item)
- ))
+ self._inputs.append(
+ BindableAction(
+ input_item,
+ self._get_bound_to_string(input_item),
+ self._create_input_cb(input_item),
+ )
+ )
self.main_layout.addWidget(self._inputs[-1])
self.main_layout.addStretch(0)
@@ -262,8 +251,7 @@ def _get_bound_to_string(self, input_item):
# Special handling of keyboards
if bound_input.parent.parent.device_guid == dinput.GUID_Keyboard:
key_name = macro.key_from_code(
- bound_input.input_id[0],
- bound_input.input_id[1]
+ bound_input.input_id[0], bound_input.input_id[1]
).name
return f"{self.device_names[bound_input.parent.parent.device_guid]} - {key_name}"
else:
@@ -291,25 +279,20 @@ def _get_device_names(self):
class BindableAction(QtWidgets.QWidget):
-
"""UI widget for a single action that can be bound to an input."""
# Stores which input types are valid together
valid_bind_types = {
- common.InputType.JoystickAxis: [
- common.InputType.JoystickAxis
- ],
+ common.InputType.JoystickAxis: [common.InputType.JoystickAxis],
common.InputType.JoystickButton: [
common.InputType.JoystickButton,
- common.InputType.Keyboard
- ],
- common.InputType.JoystickHat: [
- common.InputType.JoystickHat
+ common.InputType.Keyboard,
],
+ common.InputType.JoystickHat: [common.InputType.JoystickHat],
common.InputType.Keyboard: [
common.InputType.JoystickButton,
- common.InputType.Keyboard
- ]
+ common.InputType.Keyboard,
+ ],
}
def __init__(self, input_item, label, input_cb, parent=None):
@@ -353,8 +336,7 @@ def __init__(self, input_item, label, input_cb, parent=None):
def _bind_action(self):
"""Prompts the user for the input to bind to this item."""
self.button_press_dialog = gremlin.ui.ui_common.InputListenerWidget(
- BindableAction.valid_bind_types[self.input_type],
- return_kb_event=True
+ BindableAction.valid_bind_types[self.input_type], return_kb_event=True
)
self.button_press_dialog.item_selected.connect(self.input_cb)
@@ -365,6 +347,6 @@ def _bind_action(self):
int(geom.x() + geom.width() / 2 - 150),
int(geom.y() + geom.height() / 2 - 75),
300,
- 150
+ 150,
)
- self.button_press_dialog.show()
\ No newline at end of file
+ self.button_press_dialog.show()
diff --git a/gremlin/ui/profile_settings.py b/gremlin/ui/profile_settings.py
index 80f7e95a..30407ee3 100644
--- a/gremlin/ui/profile_settings.py
+++ b/gremlin/ui/profile_settings.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -24,8 +24,9 @@
import gremlin.ui.ui_common
from gremlin.ui.qdatawidget import QDataWidget
-class ProfileSettingsWidget(QDataWidget):
+
+class ProfileSettingsWidget(QDataWidget):
"""Widget allowing changing profile specific settings."""
# Signal emitted when a change occurs
@@ -51,8 +52,7 @@ def __init__(self, profile_settings, parent=None):
# Configure the widget holding the layout with all the buttons
self.scroll_widget.setLayout(self.scroll_layout)
self.scroll_widget.setSizePolicy(
- QtWidgets.QSizePolicy.Expanding,
- QtWidgets.QSizePolicy.Expanding
+ QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
)
self.scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.scroll_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
@@ -73,7 +73,6 @@ def refresh_ui(self, emit_change=False):
if emit_change:
self.changed.emit()
-
def _create_ui(self):
"""Creates the UI elements of this widget."""
# Default start mode selection
@@ -88,12 +87,16 @@ def _create_ui(self):
vjoy_as_input_widget.changed.connect(lambda: self.refresh_ui(True))
# vJoy axis initialization value setup
- for dev in sorted(gremlin.joystick_handling.vjoy_devices(), key=lambda x: x.vjoy_id):
+ for dev in sorted(
+ gremlin.joystick_handling.vjoy_devices(), key=lambda x: x.vjoy_id
+ ):
# Only show devices that are not treated as inputs
if self.profile_settings.vjoy_as_input.get(dev.vjoy_id) is True:
continue
- widget = QtWidgets.QGroupBox(f"{dev.name} #{dev.vjoy_id} - Profile Start Initial Values")
+ widget = QtWidgets.QGroupBox(
+ f"{dev.name} #{dev.vjoy_id} - Profile Start Initial Values"
+ )
box_layout = QtWidgets.QVBoxLayout()
widget.setLayout(box_layout)
box_layout.addWidget(VJoyAxisDefaultsWidget(dev, self.profile_settings))
@@ -116,7 +119,6 @@ def _create_ui(self):
class DefaultDelay(QtWidgets.QGroupBox):
-
"""Configures the default delay used with macro executions."""
def __init__(self, profile_data, parent=None):
@@ -161,7 +163,6 @@ def _update_delay(self, value):
class DefaultModeSelector(QtWidgets.QGroupBox):
-
"""Allows selecting the mode in which Gremlin starts."""
def __init__(self, profile_data, parent=None):
@@ -203,7 +204,6 @@ def _update_cb(self, index):
class VJoyAxisDefaultsWidget(QtWidgets.QWidget):
-
"""UI widget allowing modification of axis initialization values."""
def __init__(self, device, profile_data, parent=None):
@@ -215,9 +215,11 @@ def __init__(self, device, profile_data, parent=None):
"""
super().__init__(parent)
- assert device.is_virtual and device.vjoy_id > 0,"Device provided is not a VJOY device"
+ assert (
+ device.is_virtual and device.vjoy_id > 0
+ ), "Device provided is not a VJOY device"
self.device = device
-
+
self.profile_data = profile_data
self.main_layout = QtWidgets.QGridLayout(self)
self.main_layout.setColumnMinimumWidth(0, 100)
@@ -236,24 +238,30 @@ def _create_ui(self):
axis_name = self.device.get_axis_name(input_id)
self.main_layout.addWidget(QtWidgets.QLabel(axis_name), row, 0)
frame = gremlin.ui.ui_common.QBoxFrame()
- frame.setStyleSheet(f"border: 2px solid {gremlin.ui.ui_common.Color.selectColor()};")
+ frame.setStyleSheet(
+ f"border: 2px solid {gremlin.ui.ui_common.Color.selectColor()};"
+ )
frame.setLayout(QtWidgets.QHBoxLayout())
box = gremlin.ui.ui_common.QFloatLineEdit()
box.setRange(-1, 1)
- box.setValue(self.profile_data.get_initial_vjoy_axis_value(self.device.vjoy_id, input_id))
+ box.setValue(
+ self.profile_data.get_initial_vjoy_axis_value(
+ self.device.vjoy_id, input_id
+ )
+ )
box.valueChanged.connect(self._create_value_cb(input_id))
self._spin_boxes.append(box)
frame.layout().addWidget(box)
- presets = [-1,-0.75,-0.5,-0.25,0,0.25,0.5,0.75,1]
+ presets = [-1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1]
container_widget = QtWidgets.QWidget()
container_layout = QtWidgets.QHBoxLayout(container_widget)
for value in presets:
widget = gremlin.ui.ui_common.QDataPushButton(f"{value:0.3f}")
- widget.data = (row, value) # (axis_id, value)
+ widget.data = (row, value) # (axis_id, value)
widget.setToolTip(f"Sets to {value:0.3f}")
widget.clicked.connect(self._handle_preset)
container_layout.addWidget(widget)
@@ -263,7 +271,7 @@ def _create_ui(self):
self.main_layout.addWidget(container_widget, row, 2)
row += 1
self.main_layout.addWidget(QtWidgets.QWidget(), 0, 3)
- self.main_layout.setColumnStretch(3,2)
+ self.main_layout.setColumnStretch(3, 2)
vjoy_proxy.reset()
@QtCore.Slot()
@@ -271,8 +279,6 @@ def _handle_preset(self):
widget = self.sender()
index, x = widget.data
self._spin_boxes[index].setValue(x)
-
-
def _create_value_cb(self, axis_id):
"""Creates a callback function which updates axis values.
@@ -289,14 +295,11 @@ def _update_axis_value(self, axis_id, value):
:param value the value to update the axis to
"""
self.profile_data.set_initial_vjoy_axis_value(
- self.device.vjoy_id,
- axis_id,
- value
+ self.device.vjoy_id, axis_id, value
)
class VJoyAsInputWidget(QtWidgets.QGroupBox):
-
"""Configures which vJoy devices are treated as physical inputs."""
# Signal emitted when a change occurs
@@ -314,7 +317,7 @@ def __init__(self, profile_data, parent=None):
self.profile_data = profile_data
self.setTitle("vJoy as Input")
- self.main_layout = QtWidgets. QHBoxLayout(self)
+ self.main_layout = QtWidgets.QHBoxLayout(self)
self.vjoy_layout = QtWidgets.QVBoxLayout()
self._create_ui()
@@ -322,15 +325,12 @@ def __init__(self, profile_data, parent=None):
def _create_ui(self):
"""Creates the UI to set physical input state."""
for dev in sorted(
- gremlin.joystick_handling.vjoy_devices(),
- key=lambda x: x.vjoy_id
+ gremlin.joystick_handling.vjoy_devices(), key=lambda x: x.vjoy_id
):
check_box = QtWidgets.QCheckBox(f"vJoy {dev.vjoy_id:d}")
if self.profile_data.vjoy_as_input.get(dev.vjoy_id, False):
check_box.setChecked(True)
- check_box.stateChanged.connect(
- self._create_update_state_cb(dev.vjoy_id)
- )
+ check_box.stateChanged.connect(self._create_update_state_cb(dev.vjoy_id))
self.vjoy_layout.addWidget(check_box)
# Information label
@@ -343,7 +343,7 @@ def _create_ui(self):
background_color = gremlin.ui.ui_common.Color.highlightBackgroundColor()
label.setStyleSheet(f"QLabel {{ background-color : {background_color}; }}")
-
+
label.setWordWrap(True)
label.setFrameShape(QtWidgets.QFrame.Box)
label.setMargin(10)
diff --git a/gremlin/ui/qdatawidget.py b/gremlin/ui/qdatawidget.py
index 87eca0bd..e55e9630 100644
--- a/gremlin/ui/qdatawidget.py
+++ b/gremlin/ui/qdatawidget.py
@@ -1,19 +1,17 @@
-
from PySide6 import QtWidgets
+
class QDataWidget(QtWidgets.QWidget):
- def __init__(self, data = None, parent = None):
+ def __init__(self, data=None, parent=None):
super().__init__(parent)
self._data = data
-
@property
def data(self):
return self._data
-
+
@data.setter
def data(self, value):
if self._data is not None:
pass
self._data = value
-
\ No newline at end of file
diff --git a/gremlin/ui/qsliderwidget.py b/gremlin/ui/qsliderwidget.py
index 8c7fc6e0..4e0c9dd6 100644
--- a/gremlin/ui/qsliderwidget.py
+++ b/gremlin/ui/qsliderwidget.py
@@ -1,57 +1,80 @@
# implements a custom multi-gate slider widget
from __future__ import annotations
-import enum
-import time
-import threading
-import os
-from typing import Optional
import logging
from PySide6 import QtWidgets, QtCore, QtGui
-import PySide6.QtGui
-import PySide6.QtWidgets
import gremlin.config
import gremlin.error
-import qtawesome as qta
import gremlin.event_handler
-from gremlin.input_types import InputType
-from gremlin.clipboard import Clipboard
import gremlin.input_types
import gremlin.joystick_handling
import gremlin.keyboard
import gremlin.shared_state
-import gremlin.shared_state
import gremlin.types
import gremlin.util
-from PySide6.QtCore import (
- Qt, QSize, QPoint, QPointF, QRectF, QRect, QThread, QTimer,
- QEasingCurve, QPropertyAnimation, QSequentialAnimationGroup,
- Slot, Property)
-
-from PySide6.QtWidgets import QCheckBox, QToolTip
-from PySide6.QtGui import QColor, QBrush, QPaintEvent, QPen, QPainter, QFont, QMouseEvent, QCursor
+from PySide6.QtCore import (
+ Qt,
+ QPoint,
+ QRect,
+ QTimer,
+)
+
+from PySide6.QtWidgets import QToolTip
+from PySide6.QtGui import (
+ QColor,
+ QBrush,
+ QPaintEvent,
+ QPen,
+ QPainter,
+ QMouseEvent,
+ QCursor,
+)
from itertools import pairwise
syslog = logging.getLogger("system")
+
class QSliderWidget(QtWidgets.QWidget):
- ''' custom slider object '''
-
- handleClicked = QtCore.Signal(int) # called when a handle is left clicked (handle index)
- handleRightClicked = QtCore.Signal(int) # called when a handle is right clicked (handle index)
- handleDoubleClicked = QtCore.Signal(int) # called when a handle is double clicked (handle index)
- handleDoubleRightClicked = QtCore.Signal(int) # called when a handle is double clicked with the right mouse button (handle index)
- rangeClicked = QtCore.Signal(float, int, int) # called when a groove is clicked (between handles) - sends the value of the slider where clicked - (value, left handle index, right handle index)
- rangeRightClicked = QtCore.Signal(float, int, int) # called when a range is right clicked (between handles) - sends the value of the slider where clicked - (value, left handle index, right handle index)
- rangeDoubleClicked = QtCore.Signal(float, int, int) # called when a range is double clicked (between handles) - sends the value of the slider where clicked - (value, left handle index, right handle index)
- rangeDoubleRightClicked = QtCore.Signal(float, int, int) # called when a range is double clicked with the right mouse button (between handles) - sends the value of the slider where clicked - (value, left handle index, right handle index)
- valueChanged = QtCore.Signal(int, float) # called when a gate value changes via dragging (index of handle, updated value)
- handleDragStart = QtCore.Signal(int) # called when a handle is being dragged (handle index)
- handleDragStop = QtCore.Signal(int) # called when a handle stops being dragged (handle index)
-
- class PixmapData():
- ''' holds a pixmap definition '''
- def __init__(self, pixmap : QtGui.QPixmap = None, offset_x = None, offset_y = None):
+ """custom slider object"""
+
+ handleClicked = QtCore.Signal(
+ int
+ ) # called when a handle is left clicked (handle index)
+ handleRightClicked = QtCore.Signal(
+ int
+ ) # called when a handle is right clicked (handle index)
+ handleDoubleClicked = QtCore.Signal(
+ int
+ ) # called when a handle is double clicked (handle index)
+ handleDoubleRightClicked = QtCore.Signal(
+ int
+ ) # called when a handle is double clicked with the right mouse button (handle index)
+ rangeClicked = QtCore.Signal(
+ float, int, int
+ ) # called when a groove is clicked (between handles) - sends the value of the slider where clicked - (value, left handle index, right handle index)
+ rangeRightClicked = QtCore.Signal(
+ float, int, int
+ ) # called when a range is right clicked (between handles) - sends the value of the slider where clicked - (value, left handle index, right handle index)
+ rangeDoubleClicked = QtCore.Signal(
+ float, int, int
+ ) # called when a range is double clicked (between handles) - sends the value of the slider where clicked - (value, left handle index, right handle index)
+ rangeDoubleRightClicked = QtCore.Signal(
+ float, int, int
+ ) # called when a range is double clicked with the right mouse button (between handles) - sends the value of the slider where clicked - (value, left handle index, right handle index)
+ valueChanged = QtCore.Signal(
+ int, float
+ ) # called when a gate value changes via dragging (index of handle, updated value)
+ handleDragStart = QtCore.Signal(
+ int
+ ) # called when a handle is being dragged (handle index)
+ handleDragStop = QtCore.Signal(
+ int
+ ) # called when a handle stops being dragged (handle index)
+
+ class PixmapData:
+ """holds a pixmap definition"""
+
+ def __init__(self, pixmap: QtGui.QPixmap = None, offset_x=None, offset_y=None):
self.pixmap = pixmap
self.offset_x = offset_x
self.offset_y = offset_y
@@ -60,89 +83,108 @@ def __init__(self, pixmap : QtGui.QPixmap = None, offset_x = None, offset_y = No
self.height = pixmap.height()
else:
self.width = 0
- self.height = 0
+ self.height = 0
- def __init__(self, parent = None):
+ def __init__(self, parent=None):
import gremlin.ui.ui_common
import gremlin.shared_state
+
super().__init__(parent)
- self._values = [-1.0, 1.0] # position of the gates inside the range - the values must be between the slider's min/max values
- self._handle_icons = {} # icon data for handles, keyed by index
+ self._values = [
+ -1.0,
+ 1.0,
+ ] # position of the gates inside the range - the values must be between the slider's min/max values
+ self._handle_icons = {} # icon data for handles, keyed by index
self._internal_handle_pixmaps = {} # holds the pixmaps for the current handle icons keyed by handle ID
self._minimum = -1.0
self._maximum = 1.0
self._draw_handles = True
- self._tick_count = 0 # no ticks
-
+ self._tick_count = 0 # no ticks
+
+ self.handleColor = QColor(
+ gremlin.ui.ui_common.Color.sliderHandleColor()
+ ) # QColor("#a7b59e")
+ self.tickColor = QColor(
+ gremlin.ui.ui_common.Color.sliderTickColor()
+ ) # QColor("#232323")
+ self.handleBorderColor = QColor(
+ gremlin.ui.ui_common.Color.sliderHandleBorderColor()
+ ) # QColor("#e0e0e0")
+ self.rangeBorderColor = QColor(
+ gremlin.ui.ui_common.Color.sliderRangeBorderColor()
+ ) # QColor("#e0e0e0")
+ self.rangeColor = QColor(
+ gremlin.ui.ui_common.Color.sliderRangeColor()
+ ) # QColor("8fb9bc")
+ self.rangeAlternateColor = QColor(
+ gremlin.ui.ui_common.Color.sliderAlternateRangeColor()
+ ) # QColor("#8fb9bc")
+ self.UseAlternateColor = True # alternate range colors
+ self.BackgroundColor = QColor(
+ gremlin.ui.ui_common.Color.sliderBackgroundColor()
+ ) # QColor("#c3c3c3")
- self.handleColor = QColor(gremlin.ui.ui_common.Color.sliderHandleColor()) #QColor("#a7b59e")
- self.tickColor = QColor(gremlin.ui.ui_common.Color.sliderTickColor()) #QColor("#232323")
- self.handleBorderColor = QColor(gremlin.ui.ui_common.Color.sliderHandleBorderColor()) #QColor("#e0e0e0")
- self.rangeBorderColor = QColor(gremlin.ui.ui_common.Color.sliderRangeBorderColor()) #QColor("#e0e0e0")
- self.rangeColor = QColor(gremlin.ui.ui_common.Color.sliderRangeColor()) #QColor("8fb9bc")
- self.rangeAlternateColor = QColor(gremlin.ui.ui_common.Color.sliderAlternateRangeColor()) #QColor("#8fb9bc")
- self.UseAlternateColor = True # alternate range colors
- self.BackgroundColor = QColor(gremlin.ui.ui_common.Color.sliderBackgroundColor()) #QColor("#c3c3c3")
-
self._finishedProgressLength = 0
- self._handle_hotspots = [] # rects of handle clickable spots
- self._range_hotspots = [] # rects of clickable spots for ranges between handles
- self._handle_positions = [] # computed gate position offset indexed by gate index
- self._marker_pos = [] # computed marker positions
+ self._handle_hotspots = [] # rects of handle clickable spots
+ self._range_hotspots = [] # rects of clickable spots for ranges between handles
+ self._handle_positions = [] # computed gate position offset indexed by gate index
+ self._marker_pos = [] # computed marker positions
self._readOnly = False
- self._usable_width = 0 # range width, pixels
- self._usable_left = 0 # left range start position, pixel
- self._usable_right = 0 # right range start position, pixel
- self._marker_size = 16 # size of the marker icons in pixels
- self._marker_visible = True # show marker
-
- self._range_left = 0 # position of the first gate in pixels
- self._range_right = 0 # position of the last gate in pixels
- self._range_width = 0 # width of the bar between min gate and max gate in pixels
- self._handle_height = 0 # height of the gate range bar
- self._handle_top = 0 # top offset for the gate range bar
- self._handle_min = 0 # min possible x for a handle position
- self._handle_max = 0 # max possible x for a handle position
+ self._usable_width = 0 # range width, pixels
+ self._usable_left = 0 # left range start position, pixel
+ self._usable_right = 0 # right range start position, pixel
+ self._marker_size = 16 # size of the marker icons in pixels
+ self._marker_visible = True # show marker
+
+ self._range_left = 0 # position of the first gate in pixels
+ self._range_right = 0 # position of the last gate in pixels
+ self._range_width = (
+ 0 # width of the bar between min gate and max gate in pixels
+ )
+ self._handle_height = 0 # height of the gate range bar
+ self._handle_top = 0 # top offset for the gate range bar
+ self._handle_min = 0 # min possible x for a handle position
+ self._handle_max = 0 # max possible x for a handle position
self._range_msg = "N/A"
- self._single_range = False # true if there are different ranges between handles, false if a single range between min/max handles
+ self._single_range = False # true if there are different ranges between handles, false if a single range between min/max handles
# mouse and drag tracking
- self._mouse_down = False # is mouse button down
- self._double_clicked = False # true if the mouse was just double clicked
- self._drag_start = None # start drag position
- self._drag_handle_index = None # handle being dragged
- self._drag_active = False # true if a drag operation is in progress
- self._drag_x_offset = 0 # offset in pixels of the mouse position to the center of the gate
- self._drag_emit = False # true if the drag event was emitted on drag start
+ self._mouse_down = False # is mouse button down
+ self._double_clicked = False # true if the mouse was just double clicked
+ self._drag_start = None # start drag position
+ self._drag_handle_index = None # handle being dragged
+ self._drag_active = False # true if a drag operation is in progress
+ self._drag_x_offset = (
+ 0 # offset in pixels of the mouse position to the center of the gate
+ )
+ self._drag_emit = False # true if the drag event was emitted on drag start
# hover tracking
- self._hover_handle = False # true if mouse is over a handle hotspot
- self._hover_range = False # true if mouse is over a range hotspot
- self._hover_handle_index = -1 # hover index for handle -1 indicates not set
- self._hover_range_handle_pair = None # hover index pairs
- self._hover_lock = False # true when a drag operation is in process to keep the hover as-is
-
- self._tooltip_timer : QTimer = None # tooltip delay timer
- self._tooltip_handle_map = {} # tooltips to display for the given handle, key is the index of the handle, 0 based
- self._tooltip_range_map = {} # tooltips for a given range, the key is a tuple of the index two bounding gates (a,b)
+ self._hover_handle = False # true if mouse is over a handle hotspot
+ self._hover_range = False # true if mouse is over a range hotspot
+ self._hover_handle_index = -1 # hover index for handle -1 indicates not set
+ self._hover_range_handle_pair = None # hover index pairs
+ self._hover_lock = (
+ False # true when a drag operation is in process to keep the hover as-is
+ )
+
+ self._tooltip_timer: QTimer = None # tooltip delay timer
+ self._tooltip_handle_map = {} # tooltips to display for the given handle, key is the index of the handle, 0 based
+ self._tooltip_range_map = {} # tooltips for a given range, the key is a tuple of the index two bounding gates (a,b)
self._desired_height = 32
self._tick_marks = None
self._tick_count = 0
-
- #self.sizePolicy().setHorizontalPolicy(QtWidgets.QSizePolicy.Expanding)
+ # self.sizePolicy().setHorizontalPolicy(QtWidgets.QSizePolicy.Expanding)
- self.setSizePolicy(
- QtWidgets.QSizePolicy.Expanding,
- QtWidgets.QSizePolicy.Fixed
- )
+ self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
- self._pixmaps = [] # list of pixmap data marker definition objects
+ self._pixmaps = [] # list of pixmap data marker definition objects
self.adjustSize()
@@ -151,53 +193,53 @@ def __init__(self, parent = None):
self.setMarkerValue(0)
self._update_offsets()
self._update_all_handle_pixmaps()
- self.setMouseTracking(True) # track mouse movements
+ self.setMouseTracking(True) # track mouse movements
- @property
+ @property
def desired_height(self) -> int:
return self._desired_height
-
+
@desired_height.setter
- def desired_height(self, value : int):
+ def desired_height(self, value: int):
self._desired_height = value
self.resize(self.minimumSizeHint())
-
def minimumSizeHint(self):
- '''
+ """
Minimum size of the widget in pixels
Returns:
Minimum widget size (Qsize)
- '''
- return QtCore.QSize(100,self._desired_height)
-
+ """
+ return QtCore.QSize(100, self._desired_height)
+
@property
def values(self) -> list:
return self._values
-
+
@values.setter
- def values(self, value : int | float | list | tuple):
+ def values(self, value: int | float | list | tuple):
self.setValue(value)
@property
def singleRange(self):
return self._single_range
+
@singleRange.setter
def singleRange(self, value):
self._single_range = value
self._update_offsets()
self.update()
- def setTickCount(self, value : int):
- ''' sets the number of ticks '''
+ def setTickCount(self, value: int):
+ """sets the number of ticks"""
value = gremlin.util.clamp(value, 0, 50)
if self._tick_count != value:
self._tick_count = value
self.update()
def setTickMarks(self, value):
- ''' sets the tick marks for the axis as set values '''
+ """sets the tick marks for the axis as set values"""
if value:
self._tick_marks = value
self._tick_count = len(value)
@@ -206,20 +248,20 @@ def setTickMarks(self, value):
self.update()
def setDrawHandles(self, value: bool):
- ''' enable/disables the drawing of handles '''
+ """enable/disables the drawing of handles"""
if self._draw_handles != value:
self._draw_handles = value
self.update()
- def setHandleIcon(self, index, icon, use_qta = False, color = "#a0a0a0"):
- ''' sets the handle icon - to clear an icon, set it to None
-
+ def setHandleIcon(self, index, icon, use_qta=False, color="#a0a0a0"):
+ """sets the handle icon - to clear an icon, set it to None
+
:param index: the handle index (int)
:param icon: the QTA name or the png/svg file
:param use_qta: set to true if the icon is a QTA icon
:param color: icon color (qta icons only), None if default color
- '''
+ """
if icon is None:
# clear the entry
if index in self._handle_icons:
@@ -231,51 +273,45 @@ def setHandleIcon(self, index, icon, use_qta = False, color = "#a0a0a0"):
self._update_handle_pixmaps(hid)
self.update()
-
- def setHandleTooltip(self, index : int, message : str):
- ''' sets the tooltip for a given handle, to disable, set the message to None
+ def setHandleTooltip(self, index: int, message: str):
+ """sets the tooltip for a given handle, to disable, set the message to None
:param index: index of the handle
:param message: message to display
- '''
+ """
if message is None and index in self._tooltip_handle_map:
del self._tooltip_handle_map[index]
else:
self._tooltip_handle_map[index] = message
- def setRangeTooltip(self, a: int, b : int, message : str):
- ''' sets the tooltip for a given handle, to disable, set the message to None
-
+ def setRangeTooltip(self, a: int, b: int, message: str):
+ """sets the tooltip for a given handle, to disable, set the message to None
+
:param a: index of the first handle (left)
:param b: index of the second handle (right)
:param message: message to display
-
- '''
- key = (a,b)
+
+ """
+ key = (a, b)
if message is None and key in self._tooltip_handle_map:
del self._tooltip_range_map[key]
else:
self._tooltip_range_map[key] = message
-
-
-
-
-
-
- def setValueIndex(self, index : int, value : int | float):
- ''' sets a specific value by index '''
+ def setValueIndex(self, index: int, value: int | float):
+ """sets a specific value by index"""
try:
values = self._values
values[index] = value
self.setValue(values)
- except:
- syslog.error(f"Unable to set value index {index} - out of index range error")
+ except Exception as e:
+ syslog.error(
+ f"Unable to set value index {index} - out of index range error\n{e}"
+ )
-
- def setValue(self, value : int | float | list | tuple):
- ''' input values expected to be -1 to +1 floating point '''
+ def setValue(self, value: int | float | list | tuple):
+ """input values expected to be -1 to +1 floating point"""
values = None
- if isinstance(value, float):
+ if isinstance(value, float):
values = [value]
elif isinstance(value, int):
values = [float(value)]
@@ -285,27 +321,23 @@ def setValue(self, value : int | float | list | tuple):
values = list(value)
values = [v for v in values if v is not None]
if values:
- values.sort() # sort by value so the values are always in smallest to greatest
- self._values = values # [max(min(1.0, n), -1.0) for n in values]
+ values.sort() # sort by value so the values are always in smallest to greatest
+ self._values = values # [max(min(1.0, n), -1.0) for n in values]
self._update_offsets()
self.update()
-
-
def value(self) -> list:
- ''' gets the list of values in the slider - a single value is returned as a list of one'''
+ """gets the list of values in the slider - a single value is returned as a list of one"""
return self._values
-
-
-
- def setMarkerVisible(self, value : bool):
+
+ def setMarkerVisible(self, value: bool):
self._marker_visible = value
self.update()
@property
def marker_size(self):
return self._marker_size
-
+
@marker_size.setter
def marker_size(self, value):
if value > 0:
@@ -314,39 +346,37 @@ def marker_size(self, value):
self._update_marker_offsets()
self.update()
- def setReadOnly(self, value : bool):
+ def setReadOnly(self, value: bool):
self._readOnly = value
-
+
def readOnly(self) -> bool:
return self._readOnly
-
def _update_offsets(self):
- ''' recomputes pixel offsets based on gate values '''
+ """recomputes pixel offsets based on gate values"""
size = self.size()
widget_width = size.width()
widget_height = size.height()
- margin = 4 # pixel margin
+ margin = 4 # pixel margin
usable_width = widget_width - margin
range_width = usable_width - margin
usable_margin = int((widget_width - range_width) * 0.5)
-
gate_positions = []
if len(self._values) == 0:
return
-
+
handle_count = len(self._values)
-
+
# handle sizes
- max_icon_size = widget_height - 2*margin
+ max_icon_size = widget_height - 2 * margin
handle_size = 24
handle_size = min(handle_size, max_icon_size)
self._handle_radius = int(handle_size * 0.5)
self._handle_size = self._handle_radius * 2
self._handle_top = int((widget_height - self._handle_size) * 0.5)
-
+
self._handle_count = handle_count
self._widget_width = widget_width
self._widget_height = widget_height
@@ -354,30 +384,35 @@ def _update_offsets(self):
# compute the max range geometry boundaries (horizontal only)
self._usable_width = range_width
- self._usable_margin = int(handle_size * 0.5) + usable_margin # account for handle diameter
- self._usable_left = self._usable_margin
+ self._usable_margin = (
+ int(handle_size * 0.5) + usable_margin
+ ) # account for handle diameter
+ self._usable_left = self._usable_margin
self._usable_right = widget_width - self._usable_margin
-
-
-
# print (f"Range width: {self._usable_width} range left: {self._usable_left} range right: {self._usable_right} range width: {self._usable_width} widget width: {widget_width} height {widget_height} handle diameter: {handle_size} radius: {self._handle_radius}")
- #handle_offsets = []
+ # handle_offsets = []
for value in self._values:
- x = gremlin.util.scale_to_range(value, source_min=self._minimum, source_max=self._maximum, target_min = self._usable_left, target_max = self._usable_right)
+ x = gremlin.util.scale_to_range(
+ value,
+ source_min=self._minimum,
+ source_max=self._maximum,
+ target_min=self._usable_left,
+ target_max=self._usable_right,
+ )
gate_positions.append(x - self._handle_radius)
- #handle_offsets.append((value, x))
+ # handle_offsets.append((value, x))
- #print(f"Handle offset: {handle_offsets}")
+ # print(f"Handle offset: {handle_offsets}")
# leftmost position of the first gate
if len(gate_positions) == 1:
# single gate - use the whole range
- self._range_left = self._usable_left
+ self._range_left = self._usable_left
self._range_right = self._usable_right
else:
- # two or more gates
+ # two or more gates
self._range_left = gate_positions[0] + self._handle_radius
self._range_right = gate_positions[-1] + self._handle_radius
@@ -386,7 +421,7 @@ def _update_offsets(self):
self._range_width = self._range_right - self._range_left
self._range_height = int(self._handle_size * 0.6)
self._range_corner = int(self._range_height * 0.33)
- self._range_top = int((widget_height - self._range_height)*0.5)
+ self._range_top = int((widget_height - self._range_height) * 0.5)
self._handle_positions = gate_positions
# print (f"Range left: {self._range_left} right: {self._range_right} width: {self._range_width} height: {self._range_height} top margin: {self._range_top}")
self._update_marker_offsets()
@@ -397,19 +432,25 @@ def _update_marker_offsets(self):
# compute marker positions
source_min = self._minimum
source_max = self._maximum
- target_min = self._usable_left # self._to_qinteger_space(self._range_left)
- target_max = self._usable_right # self._to_qinteger_space(self._range_right)
- self._int_marker_pos = [((v - source_min) * (target_max - target_min)) / (source_max - source_min) + target_min for v in self._marker_pos]
+ target_min = self._usable_left # self._to_qinteger_space(self._range_left)
+ target_max = (
+ self._usable_right
+ ) # self._to_qinteger_space(self._range_right)
+ self._int_marker_pos = [
+ ((v - source_min) * (target_max - target_min))
+ / (source_max - source_min)
+ + target_min
+ for v in self._marker_pos
+ ]
# print (f"marker: {[v for v in self._int_marker_pos]}")
-
def _update_targets(self):
- ''' update target positions '''
- self._target_min = self._minimum # self._to_qinteger_space(self._minimum)
- self._target_max = self._maximum # self._to_qinteger_space(self._maximum)
+ """update target positions"""
+ self._target_min = self._minimum # self._to_qinteger_space(self._minimum)
+ self._target_max = self._maximum # self._to_qinteger_space(self._maximum)
def setMarkerValue(self, value):
- ''' sets the marker(s) value - single float is one marker, passing a tuple creates multiple markers'''
+ """sets the marker(s) value - single float is one marker, passing a tuple creates multiple markers"""
try:
if isinstance(value, float) or isinstance(value, int):
list_value = [value]
@@ -418,17 +459,17 @@ def setMarkerValue(self, value):
self._marker_pos = list_value
# update geometry + repaint
- self._update_offsets()
+ self._update_offsets()
self.update()
- except:
+ except Exception:
pass
def minimum(self) -> float: # type: ignore
- ''' gets the slider's minimum value '''
+ """gets the slider's minimum value"""
return self._minimum
def setMinimum(self, value: float) -> None:
- ''' sets the slider's minimum value '''
+ """sets the slider's minimum value"""
self._minimum = value
if self._maximum < self._minimum:
self._maximum, self._minimum = self._minimum, self._maximum
@@ -436,24 +477,24 @@ def setMinimum(self, value: float) -> None:
self._update_offsets()
def maximum(self) -> float: # type: ignore
- ''' gets the slider's maximum value '''
+ """gets the slider's maximum value"""
return self._maximum
def setMaximum(self, value: float) -> None:
- ''' sets the slider's maximum value '''
+ """sets the slider's maximum value"""
self._maximum = value
if self._maximum < self._minimum:
self._maximum, self._minimum = self._minimum, self._maximum
self._update_targets()
self._update_offsets()
- def setRange(self, range_min : float, range_max : float):
- ''' sets the slider's min/max values
-
+ def setRange(self, range_min: float, range_max: float):
+ """sets the slider's min/max values
+
:param range_min: min range (float)
:param range_max: max range (float)
-
- '''
+
+ """
if range_min > range_max:
# swap
range_max, range_min = range_min, range_max
@@ -462,32 +503,29 @@ def setRange(self, range_min : float, range_max : float):
self._update_targets()
self._update_offsets()
-
def range(self):
- ''' returns the range of the slider '''
+ """returns the range of the slider"""
return (self._minimum, self._maximum)
- def paintEvent(self, event : QPaintEvent):
- ''' paint event
-
+ def paintEvent(self, event: QPaintEvent):
+ """paint event
+
:param event: QPaintEvent object
-
- '''
+
+ """
# draw the widget
# https://github.com/KhamisiKibet/QT-PyQt-PySide-Custom-Widgets/blob/main/Custom_Widgets/QFlowProgressBar.py
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
- #painter.eraseRect(0,0,self.width(), self.height())
-
+ # painter.eraseRect(0,0,self.width(), self.height())
self._draw_widget(painter)
if self._tick_count > 0:
- # draw the tick marks
+ # draw the tick marks
self._draw_ticks(painter)
if self._marker_visible:
self._draw_markers(painter)
-
painter.end()
@@ -497,7 +535,7 @@ def getBackgroundColor(self) -> QColor:
:return: QColor object representing the background color.
"""
- return self.BackgroundColor
+ return self.BackgroundColor
def getFinishedBackgroundColor(self) -> QColor:
"""
@@ -514,8 +552,6 @@ def getFinishedNumberColor(self) -> QColor:
:return: QColor object representing the finished number color.
"""
return self.handleColor
-
-
def _draw_widget(self, painter: QPainter):
"""
@@ -525,45 +561,55 @@ def _draw_widget(self, painter: QPainter):
"""
handle_count = self._handle_count
-
handle_pen = QPen(QBrush(self.handleBorderColor), self._handle_size * 0.1)
range_pen = QPen(QBrush(self.rangeBorderColor), self._range_height * 0.1)
- handle_pen_h = QPen(QBrush(gremlin.util.highlight_qcolor(self.handleBorderColor)), self._handle_size * 0.1)
- range_pen_h = QPen(QBrush(gremlin.util.highlight_qcolor(self.rangeBorderColor)), self._range_height * 0.1)
-
+ handle_pen_h = QPen(
+ QBrush(gremlin.util.highlight_qcolor(self.handleBorderColor)),
+ self._handle_size * 0.1,
+ )
+ range_pen_h = QPen(
+ QBrush(gremlin.util.highlight_qcolor(self.rangeBorderColor)),
+ self._range_height * 0.1,
+ )
handle_fill = QBrush(self.handleColor)
handle_fill_h = QBrush(gremlin.util.highlight_qcolor(self.handleColor))
if self.UseAlternateColor:
range_colors = [self.rangeColor, self.rangeAlternateColor]
- range_colors_h = [gremlin.util.highlight_qcolor(self.rangeColor),
- gremlin.util.highlight_qcolor(self.rangeAlternateColor)]
+ range_colors_h = [
+ gremlin.util.highlight_qcolor(self.rangeColor),
+ gremlin.util.highlight_qcolor(self.rangeAlternateColor),
+ ]
else:
color_h = gremlin.util.highlight_qcolor(self.rangeColor)
range_colors = [self.rangeColor, self.rangeColor]
range_colors_h = [color_h, color_h]
-
-
+
backgroundBrush = QBrush(self.getBackgroundColor())
emptyPen = QPen(Qt.NoPen)
# slider background
painter.setPen(emptyPen)
painter.setBrush(backgroundBrush)
- painter.drawRoundedRect(0, 0, self._widget_width, self._widget_height,self._widget_corner, self._widget_corner)
+ painter.drawRoundedRect(
+ 0,
+ 0,
+ self._widget_width,
+ self._widget_height,
+ self._widget_corner,
+ self._widget_corner,
+ )
# slider range - from the leftmost handle to the rightmost handle
-
-
# painter.setBrush(finishedBrush)
# painter.drawRoundedRect(self._range_left, self._range_top, self._range_width, self._range_height, self._range_corner, self._range_corner)
# reset computed hotspots
self._handle_hotspots = []
self._range_hotspots = []
- self._range_hotspots_map = {} # map of rect to range index pairs (a,b)
+ self._range_hotspots_map = {} # map of rect to range index pairs (a,b)
# draw ranges of different colors
color_index = 0
@@ -574,7 +620,6 @@ def _draw_widget(self, painter: QPainter):
range_corner = 0
range_top = self._range_top
-
if self._single_range:
# single range used across all gates
color_fill = range_colors[color_index]
@@ -584,12 +629,12 @@ def _draw_widget(self, painter: QPainter):
color_pen = range_pen_h
painter.setBrush(color_fill)
- painter.setPen(color_pen)
+ painter.setPen(color_pen)
a = 0
- b = handle_count-1
-
+ b = handle_count - 1
+
x1 = self._handle_positions[a]
- x2 = self._handle_positions[b]
+ x2 = self._handle_positions[b]
if self._draw_handles:
range_left = x1 + self._handle_radius
@@ -599,24 +644,30 @@ def _draw_widget(self, painter: QPainter):
x2 += self._handle_radius
range_width = x2 - x1
-
- painter.drawRoundedRect(range_left, range_top, range_width, range_height, range_corner, range_corner)
+
+ painter.drawRoundedRect(
+ range_left,
+ range_top,
+ range_width,
+ range_height,
+ range_corner,
+ range_corner,
+ )
range_rect = QRect(range_left, range_top, range_width, range_height)
# msg += f"range [{x1} {x2} {range_rect.left()} {range_rect.right()} {range_rect.top()} {range_rect.bottom()} ] "
self._range_hotspots.append(range_rect)
- self._range_hotspots_map[range_rect] = (a,b)
+ self._range_hotspots_map[range_rect] = (a, b)
else:
# individual ranges between handles
color_index = 0
for a, b in pairwise(range(handle_count)):
-
x1 = self._handle_positions[a]
x2 = self._handle_positions[b]
color_fill = range_colors[color_index]
- #print (f"range color index: {color_index}")
+ # print (f"range color index: {color_index}")
color_pen = self.rangeBorderColor
if self._hover_range:
ah, bh = self._hover_range_handle_pair
@@ -627,41 +678,48 @@ def _draw_widget(self, painter: QPainter):
color_pen = range_pen_h
painter.setBrush(color_fill)
- painter.setPen(color_pen)
-
+ painter.setPen(color_pen)
+
color_index = (color_index + 1) % 2
-
+
range_left = x1 + self._handle_radius
range_width = x2 - x1
-
- painter.drawRoundedRect(range_left, range_top, range_width, range_height, range_corner, range_corner)
+
+ painter.drawRoundedRect(
+ range_left,
+ range_top,
+ range_width,
+ range_height,
+ range_corner,
+ range_corner,
+ )
range_rect = QRect(range_left, range_top, range_width, range_height)
# msg += f"range [{x1} {x2} {range_rect.left()} {range_rect.right()} {range_rect.top()} {range_rect.bottom()} ] "
self._range_hotspots.append(range_rect)
- self._range_hotspots_map[range_rect] = (a,b)
-
+ self._range_hotspots_map[range_rect] = (a, b)
self._range_msg = msg
if self._draw_handles:
for index in range(handle_count):
-
is_hover = self._hover_handle and self._hover_handle_index == index
if is_hover:
color_fill = handle_fill_h
color_pen = handle_pen_h
-
+
else:
color_fill = handle_fill
color_pen = handle_pen
painter.setBrush(color_fill)
- painter.setPen(color_pen)
-
+ painter.setPen(color_pen)
+
x = self._handle_positions[index]
# clickable areas
- handle_rect = QRect(x, self._handle_top, self._handle_size, self._handle_size)
+ handle_rect = QRect(
+ x, self._handle_top, self._handle_size, self._handle_size
+ )
# print (f"handle [{index} {handle_rect}]")
self._handle_hotspots.append(handle_rect)
painter.drawEllipse(handle_rect)
@@ -670,14 +728,16 @@ def _draw_widget(self, painter: QPainter):
if index in self._internal_handle_pixmaps:
# pick the regular or highlighted icon
pd = self._internal_handle_pixmaps[index][1 if is_hover else 0]
- painter.drawPixmap(x + self._handle_radius + pd.offset_x, self._handle_top + pd.offset_y, pd.pixmap)
-
-
+ painter.drawPixmap(
+ x + self._handle_radius + pd.offset_x,
+ self._handle_top + pd.offset_y,
+ pd.pixmap,
+ )
def _draw_markers(self, painter: QPainter):
- ''' draws the markers on the widget '''
+ """draws the markers on the widget"""
positions = self._int_marker_pos
- center = self.height() *0.66
+ center = self.height() * 0.66
pixmaps = self._get_pixmaps()
p_count = len(pixmaps)
@@ -687,106 +747,103 @@ def _draw_markers(self, painter: QPainter):
painter.drawPixmap(value + pd.offset_x, center + pd.offset_y, pd.pixmap)
def _draw_ticks(self, painter: QPainter):
- ''' draws the tick markers '''
+ """draws the tick markers"""
tick_pen = QPen(QBrush(self.tickColor), 1)
painter.setPen(tick_pen)
-
+
count = self._tick_count
-
+
x1 = self._handle_min + self._handle_radius
x2 = self._handle_max + self._handle_radius
- center = self.height() *0.66
+ center = self.height() * 0.66
width = x2 - x1
if count > 0:
y1 = center
- y2 = 10 # int((self.height() - center) * 0.8)
+ y2 = 10 # int((self.height() - center) * 0.8)
if self._tick_marks:
# variable spaced
for value in self._tick_marks:
if value >= -1 and value <= 1:
- x = gremlin.util.scale_to_range(value, target_min=x1, target_max=x2)
+ x = gremlin.util.scale_to_range(
+ value, target_min=x1, target_max=x2
+ )
p1 = QPoint(x, y1)
p2 = QPoint(x, y2)
- painter.drawLine(p1,p2)
+ painter.drawLine(p1, p2)
else:
# equally spaced
- interval = width / (count-1)
- x = x1
- for _ in range(self._tick_count+1):
+ interval = width / (count - 1)
+ x = x1
+ for _ in range(self._tick_count + 1):
p1 = QPoint(x, y1)
p2 = QPoint(x, y2)
- painter.drawLine(p1,p2)
+ painter.drawLine(p1, p2)
x += interval
-
+
# horizontal line
p1 = QPoint(x1, y1)
p2 = QPoint(x2, y1)
- painter.drawLine(p1,p2)
+ painter.drawLine(p1, p2)
def resizeEvent(self, event):
- ''' called on resize '''
+ """called on resize"""
super().resizeEvent(event)
self._update_offsets()
-
+
self.adjustSize()
self.update()
-
def _mouse_position_to_value(self, x):
- ''' converts a mouse position to a slider value '''
+ """converts a mouse position to a slider value"""
if x >= self._usable_left and x <= self._usable_right:
# x is in the "value" zone
- value = gremlin.util.scale_to_range(x, self._usable_left, self._usable_right, self._minimum, self._maximum)
- #print (f"click value: {x} -> {value}")
+ value = gremlin.util.scale_to_range(
+ x, self._usable_left, self._usable_right, self._minimum, self._maximum
+ )
+ # print (f"click value: {x} -> {value}")
return value
# not in range
- #print (f"not in range click value: {x} -> N/A")
+ # print (f"not in range click value: {x} -> N/A")
return None
-
+
def _get_min_max_handles(self):
- ''' gets the index of the lowest and highest handle by value '''
+ """gets the index of the lowest and highest handle by value"""
values = [(value, index) for index, value in enumerate(self._values)]
- values.sort(key = lambda x: x[0])
+ values.sort(key=lambda x: x[0])
return (values[0][1], values[-1][1])
-
-
-
def _hover_enter_range(self, a, b):
- ''' enters a range '''
+ """enters a range"""
self._hover_exit_handle()
self._hover_exit_range()
- #print (f"hover enter range: {a} {b}")
+ # print (f"hover enter range: {a} {b}")
self._hover_range = True
- self._hover_range_handle_pair = (a,b)
+ self._hover_range_handle_pair = (a, b)
return True
def _hover_exit_range(self):
- ''' exists a range '''
+ """exists a range"""
if self._hover_range:
# no longer hovering over a range
# print (f"hover exit range: {self._hover_range_handle_pair}")
- self._hover_range = False # not over a range
+ self._hover_range = False # not over a range
self._hover_range_handle_pair = None
return True
return False
-
def _hover_enter_handle(self, index):
- ''' enters a handle '''
+ """enters a handle"""
self._hover_exit_range()
self._hover_exit_handle()
# print (f"hover enter handle: {index}")
self._hover_handle = True
self._hover_handle_index = index
return True
-
-
def _hover_exit_handle(self):
- ''' exits a handle '''
+ """exits a handle"""
if self._hover_handle:
# print (f"hover exit handle: {self._hover_handle_index}")
self._hover_handle = False
@@ -794,28 +851,29 @@ def _hover_exit_handle(self):
return True
return False
-
- def _show_tooltip(self, message : str):
+ def _show_tooltip(self, message: str):
if self._tooltip_timer is not None:
self._tooltip_timer.stop()
- self._tooltip_timer = QTimer(self)
+ self._tooltip_timer = QTimer(self)
self._tooltip_timer.setInterval(1000)
self._tooltip_timer.setSingleShot(True)
- self._tooltip_timer.timeout.connect(lambda: QToolTip.showText(QCursor.pos(), message, self))
+ self._tooltip_timer.timeout.connect(
+ lambda: QToolTip.showText(QCursor.pos(), message, self)
+ )
self._tooltip_timer.start()
- def _hover_update(self, event : QMouseEvent):
- ''' updates the hover state '''
+ def _hover_update(self, event: QMouseEvent):
+ """updates the hover state"""
hover_changed = False
if self._readOnly:
# no hovering in readonly mode
- return
-
+ return
+
if not self._hover_lock:
is_hover = False
- rect : QRect
+ rect: QRect
- point : QPoint = event.pos()
+ point: QPoint = event.pos()
# look for hover entry/exit on handles first
for index, rect in enumerate(self._handle_hotspots):
if rect.contains(point):
@@ -825,23 +883,28 @@ def _hover_update(self, event : QMouseEvent):
hover_changed = hover_changed or self._hover_enter_handle(index)
# tooltip
if index in self._tooltip_handle_map:
- self._show_tooltip(self._tooltip_handle_map[index])
-
+ self._show_tooltip(self._tooltip_handle_map[index])
+
is_hover = True
break
if not is_hover:
- #print (f"mouse {point} {self._range_msg}")
-
+ # print (f"mouse {point} {self._range_msg}")
+
# scan for hovering over a range
for rect in self._range_hotspots:
if rect.contains(point):
- a,b = self._range_hotspots_map[rect]
- if self._hover_range_handle_pair is None or self._hover_range_handle_pair != (a,b):
+ a, b = self._range_hotspots_map[rect]
+ if (
+ self._hover_range_handle_pair is None
+ or self._hover_range_handle_pair != (a, b)
+ ):
hover_changed = hover_changed or self._hover_exit_handle()
hover_changed = hover_changed or self._hover_exit_range()
- hover_changed = hover_changed or self._hover_enter_range(a,b)
- key = (a,b)
+ hover_changed = hover_changed or self._hover_enter_range(
+ a, b
+ )
+ key = (a, b)
if key in self._tooltip_range_map:
self._show_tooltip(self._tooltip_range_map[key])
is_hover = True
@@ -853,22 +916,21 @@ def _hover_update(self, event : QMouseEvent):
QToolTip.hideText()
return hover_changed
-
def mouseDoubleClickEvent(self, event):
- ''' double click event '''
+ """double click event"""
if self._readOnly:
# don't fire events in readonly mode
- #print ("readonly - skip mousepress")
- return
-
+ # print ("readonly - skip mousepress")
+ return
+
# print ("double click")
self._double_clicked = True
verbose = gremlin.config.Configuration().verbose
if verbose:
syslog = logging.getLogger("system")
-
+
point = event.pos()
# print("Mouse click coordinates:", point) # Debug print
for index, rect in enumerate(self._handle_hotspots):
@@ -883,13 +945,13 @@ def mouseDoubleClickEvent(self, event):
syslog.info(f"handle {index} right rouble clicked")
self.handleDoubleRightClicked.emit(index)
return
-
+
# check ranges
- #print (f"mouse point: {point}")
+ # print (f"mouse point: {point}")
for index, rect in enumerate(self._range_hotspots):
- #print (f"{rect}")
+ # print (f"{rect}")
if rect.contains(point):
- a,b = self._range_hotspots_map[rect]
+ a, b = self._range_hotspots_map[rect]
value = self._mouse_position_to_value(point.x())
if value is not None:
button = event.button()
@@ -901,122 +963,123 @@ def mouseDoubleClickEvent(self, event):
if verbose:
syslog.info(f"range right double clicked: {value}")
self.rangeDoubleRightClicked.emit(value, a, b)
-
- return
-
+ return
-
def mousePressEvent(self, event: QMouseEvent) -> None:
- ''' mouse press event handler
+ """mouse press event handler
:param event: QMouseEvent object.
- '''
- #print ("mouse press")
+ """
+ # print ("mouse press")
if self._readOnly:
# don't fire events in readonly mode
- #print ("readonly - skip mousepress")
- return
-
+ # print ("readonly - skip mousepress")
+ return
+
point = event.pos()
for index, rect in enumerate(self._handle_hotspots):
if rect.contains(point):
# gate clicked
-
+
self._mouse_down = True
self._drag_active = True
- self._drag_handle_index = index # current handle index of the handle bring dragged
+ self._drag_handle_index = (
+ index # current handle index of the handle bring dragged
+ )
self._drag_start = point
- # store the x offset where the drag started occuring
+ # store the x offset where the drag started occuring
self._drag_x_offset = self._handle_positions[index] - point.x()
self._drag_last_point = point
self._drag_x = point.x()
- self._hover_lock = True # lock the current hover mode
-
- # print (f"handle drag index {index} offset: {self._drag_x_offset}")
-
-
+ self._hover_lock = True # lock the current hover mode
+ # print (f"handle drag index {index} offset: {self._drag_x_offset}")
- def mouseMoveEvent(self, event : QMouseEvent):
- point : QPoint = event.pos()
+ def mouseMoveEvent(self, event: QMouseEvent):
+ point: QPoint = event.pos()
# process mouse movement for hover
hover_changed = self._hover_update(event)
-
-
if event.buttons() == QtCore.Qt.LeftButton:
# left mouse drag operation
point = event.pos()
x = point.x()
- if not self._drag_active and self._mouse_down and abs(self._drag_x - x) > 2: # move at least 3 pixels
+ if (
+ not self._drag_active and self._mouse_down and abs(self._drag_x - x) > 2
+ ): # move at least 3 pixels
# drag started
self._drag_active = True
self._drag_emit = False
- #print ("mouse drag starting")
-
+ # print ("mouse drag starting")
if self._drag_active:
-
if self._drag_x != x:
# mouse moved
- #print ("mouse drag detected")
+ # print ("mouse drag detected")
if not self._drag_emit:
- # only emit if there is an actual move drag happening
- self.handleDragStart.emit(self._drag_handle_index) # fire the drag start event
+ # only emit if there is an actual move drag happening
+ self.handleDragStart.emit(
+ self._drag_handle_index
+ ) # fire the drag start event
self._drag_emit = True
current_x = self._handle_positions[self._drag_handle_index]
-
+
x_offset = x - self._drag_x
current_x += x_offset
-
# drag bounds check
if current_x < self._handle_min:
current_x = self._handle_min
elif current_x > self._handle_max:
current_x = self._handle_max
-
- value = self._mouse_position_to_value(current_x + self._handle_radius)
+
+ value = self._mouse_position_to_value(
+ current_x + self._handle_radius
+ )
# get the index of the value relative to the other gates
- old_value = self._values[self._drag_handle_index]
- #print (f"Current x: {old_x} -> {current_x} offset: {x_offset} old value: {old_value} new value: {value}")
+ old_value = self._values[self._drag_handle_index]
+ # print (f"Current x: {old_x} -> {current_x} offset: {x_offset} old value: {old_value} new value: {value}")
if old_value != value:
self._values[self._drag_handle_index] = value
- values = [(value, index) for index, value in enumerate(self._values)]
+ values = [
+ (value, index) for index, value in enumerate(self._values)
+ ]
pair = values[self._drag_handle_index]
- values.sort(key = lambda x: x[0])
+ values.sort(key=lambda x: x[0])
self._values.sort()
new_index = values.index(pair)
- #print (f"new index: {new_index}")
+ # print (f"new index: {new_index}")
self._drag_handle_index = new_index
self._handle_positions[self._drag_handle_index] = current_x
index_min, index_max = self._get_min_max_handles()
-
- self._range_left = self._handle_positions[index_min] + self._handle_radius
- self._range_right = self._handle_positions[index_max] + self._handle_radius
+
+ self._range_left = (
+ self._handle_positions[index_min] + self._handle_radius
+ )
+ self._range_right = (
+ self._handle_positions[index_max] + self._handle_radius
+ )
self._range_width = self._range_right - self._range_left
self._drag_x = x
- #print (f"drag offset: {x_offset} new position: {current_x} new value: {value} min index: {index_min} max index: {index_max}")
+ # print (f"drag offset: {x_offset} new position: {current_x} new value: {value} min index: {index_min} max index: {index_max}")
# fire the gate value change
self.valueChanged.emit(self._drag_handle_index, value)
-
+
hover_changed = True
if hover_changed:
# update colors/state
- #print("hover changed")
+ # print("hover changed")
self.update()
-
-
def mouseReleaseEvent(self, event: QMouseEvent):
"""
Mouse release event handler.
@@ -1024,29 +1087,29 @@ def mouseReleaseEvent(self, event: QMouseEvent):
:param event: QMouseEvent object.
"""
- #print ("mouse release")
+ # print ("mouse release")
if self._readOnly:
# don't fire events in readonly mode
# print ("readonly - skip mouse release")
- return
-
+ return
+
if self._double_clicked:
self._double_clicked = False
# ignore mouse releases on double click
- return
-
+ return
+
verbose = gremlin.config.Configuration().verbose
if verbose:
syslog = logging.getLogger("system")
-
+
if self._drag_active:
# stop drag
# print ("stop drag")
- self._hover_lock = False
+ self._hover_lock = False
self._drag_active = False
- self._values.sort() # update any values
+ self._values.sort() # update any values
self._hover_update(event)
- self.update() # get the updated hotspots
+ self.update() # get the updated hotspots
button = event.button()
index = self._drag_handle_index
if self._drag_emit:
@@ -1061,8 +1124,7 @@ def mouseReleaseEvent(self, event: QMouseEvent):
# if verbose:
# syslog.info(f"handle {index} right clicked")
self.handleRightClicked.emit(index)
- return
-
+ return
point = event.pos()
# print("Mouse click coordinates:", point) # Debug print
@@ -1078,13 +1140,13 @@ def mouseReleaseEvent(self, event: QMouseEvent):
# syslog.info(f"handle {index} right clicked")
self.handleRightClicked.emit(index)
return
-
+
# check ranges
- #print (f"mouse point: {point}")
+ # print (f"mouse point: {point}")
for index, rect in enumerate(self._range_hotspots):
- #print (f"{rect}")
+ # print (f"{rect}")
if rect.contains(point):
- a,b = self._range_hotspots_map[rect]
+ a, b = self._range_hotspots_map[rect]
value = self._mouse_position_to_value(point.x())
if value is not None:
button = event.button()
@@ -1097,12 +1159,11 @@ def mouseReleaseEvent(self, event: QMouseEvent):
syslog.info(f"range right clicked: {value}")
self.rangeRightClicked.emit(value, a, b)
return
-
+ class PixmapData:
+ """holds a pixmap definition"""
- class PixmapData():
- ''' holds a pixmap definition '''
- def __init__(self, pixmap : QtGui.QPixmap = None, offset_x = None, offset_y = None):
+ def __init__(self, pixmap: QtGui.QPixmap = None, offset_x=None, offset_y=None):
self.pixmap = pixmap
self.offset_x = offset_x
self.offset_y = offset_y
@@ -1113,49 +1174,56 @@ def __init__(self, pixmap : QtGui.QPixmap = None, offset_x = None, offset_y = No
self.width = 0
self.height = 0
- class HandleIconData():
- def __init__(self, index: int, icon : str, use_qta: bool = True, color = "#808080"):
+ class HandleIconData:
+ def __init__(
+ self, index: int, icon: str, use_qta: bool = True, color="#808080"
+ ):
self.index = index
self.icon = icon
self.use_qta = use_qta
self.color = color
-
-
def _get_pixmaps(self):
- if self._pixmaps: return self._pixmaps
+ if self._pixmaps:
+ return self._pixmaps
return self._internal_pixmaps
-
+
def _update_pixmaps(self):
icon = gremlin.util.load_icon("ei.chevron-up")
pixmap = icon.pixmap(self.marker_size)
center = self.height() / 2
if pixmap.height() > center:
pixmap = pixmap.scaledToHeight(center)
- pd = QSliderWidget.PixmapData(pixmap = pixmap, offset_x = -pixmap.width()/2, offset_y=0)
+ pd = QSliderWidget.PixmapData(
+ pixmap=pixmap, offset_x=-pixmap.width() / 2, offset_y=0
+ )
self._internal_pixmaps = [pd]
- def _update_handle_pixmaps(self, hid : QSliderWidget.HandleIconData):
- ''' updates the pixmaps for the handle icons
+ def _update_handle_pixmaps(self, hid: QSliderWidget.HandleIconData):
+ """updates the pixmaps for the handle icons
+
+ two versions of the icon are loaded, a regular and highlighted version
- two versions of the icon are loaded, a regular and highlighted version
-
- '''
+ """
icon_size = int(self._handle_size * 0.75)
margin = (self._handle_size - icon_size) // 2
- #hex_color = "#323232" # default QTA is rgb 50,50,50
+ # hex_color = "#323232" # default QTA is rgb 50,50,50
hex_color = hid.color
color = QColor(hex_color)
- color_h = gremlin.util.highlight_qcolor(color, factor = 1.4)
- icon = gremlin.util.load_icon(hid.icon, qta_color = color)
- icon_h = gremlin.util.load_icon(hid.icon, qta_color = color_h)
+ color_h = gremlin.util.highlight_qcolor(color, factor=1.4)
+ icon = gremlin.util.load_icon(hid.icon, qta_color=color)
+ icon_h = gremlin.util.load_icon(hid.icon, qta_color=color_h)
pixmap = icon.pixmap(icon_size)
pixmap_h = icon_h.pixmap(icon_size)
- pd = QSliderWidget.PixmapData(pixmap = pixmap, offset_x = -pixmap.width()/2, offset_y= margin)
- pd_h = QSliderWidget.PixmapData(pixmap = pixmap_h, offset_x = -pixmap.width()/2, offset_y= margin)
+ pd = QSliderWidget.PixmapData(
+ pixmap=pixmap, offset_x=-pixmap.width() / 2, offset_y=margin
+ )
+ pd_h = QSliderWidget.PixmapData(
+ pixmap=pixmap_h, offset_x=-pixmap.width() / 2, offset_y=margin
+ )
self._internal_handle_pixmaps[hid.index] = (pd, pd_h)
def _update_all_handle_pixmaps(self):
- ''' updates all handle icons on resize/update'''
+ """updates all handle icons on resize/update"""
for hid in self._handle_icons.values():
- self._update_handle_pixmaps(hid)
\ No newline at end of file
+ self._update_handle_pixmaps(hid)
diff --git a/gremlin/ui/theme.py b/gremlin/ui/theme.py
index b6225572..bcbecfe8 100644
--- a/gremlin/ui/theme.py
+++ b/gremlin/ui/theme.py
@@ -1,15 +1,20 @@
-
-
def theme():
- ''' queries registry to determine the windows personalization mode:
-
+ """queries registry to determine the windows personalization mode:
+
:returns "Dark" if dark mode
:returns "Light" if not dark mode
-
- '''
- from winreg import HKEY_CURRENT_USER as hkey, QueryValueEx as getSubkeyValue, OpenKey as getKey
+
+ """
+ from winreg import (
+ HKEY_CURRENT_USER as hkey,
+ QueryValueEx as getSubkeyValue,
+ OpenKey as getKey,
+ )
+
try:
- key = getKey(hkey, "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize")
+ key = getKey(
+ hkey, "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"
+ )
subkey = getSubkeyValue(key, "AppsUseLightTheme")[0]
except FileNotFoundError:
subkey = 1
diff --git a/gremlin/ui/ui_about.py b/gremlin/ui/ui_about.py
index cb208de1..57543925 100644
--- a/gremlin/ui/ui_about.py
+++ b/gremlin/ui/ui_about.py
@@ -7,13 +7,14 @@
#
# WARNING! All changes made in this file will be lost!
-from PySide6 import QtCore, QtGui, QtWidgets
+from PySide6 import QtCore, QtWidgets
class Ui_About(object):
def setupUi(self, About):
import joystick_gremlin
import gremlin.util
+
About.setObjectName("About")
About.setWindowModality(QtCore.Qt.WindowModal)
About.resize(400, 300)
@@ -32,14 +33,16 @@ def setupUi(self, About):
self.about_box_widget = QtWidgets.QWidget()
self.about_box_widget.setLayout(self.about_box_layout)
- self.version_widget = QtWidgets.QLabel(f"Version: {joystick_gremlin.Version().version}")
+ self.version_widget = QtWidgets.QLabel(
+ f"Version: {joystick_gremlin.Version().version}"
+ )
self.about_box_layout.addWidget(self.version_widget)
- self.about_box_layout.addWidget(QtWidgets.QLabel(f"Python: {gremlin.util.getPythonVersion()}"))
+ self.about_box_layout.addWidget(
+ QtWidgets.QLabel(f"Python: {gremlin.util.getPythonVersion()}")
+ )
self.about_box_layout.addWidget(self.about)
self.horizontalLayout_4.addWidget(self.about_box_widget)
-
-
self.tabWidget.addTab(self.tab, "")
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
@@ -68,12 +71,23 @@ def setupUi(self, About):
def retranslateUi(self, About):
_translate = QtCore.QCoreApplication.translate
About.setWindowTitle(_translate("About", "About JoystickGremlin Ex"))
- self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("About", "About"))
- self.jg_license.setHtml(_translate("About", "\n"
-"\n"
-"
"))
- self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("About", "License"))
- self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("About", "3rd Party Licenses"))
-
+ self.tabWidget.setTabText(
+ self.tabWidget.indexOf(self.tab), _translate("About", "About")
+ )
+ self.jg_license.setHtml(
+ _translate(
+ "About",
+ '\n'
+ '\n"
+ '
',
+ )
+ )
+ self.tabWidget.setTabText(
+ self.tabWidget.indexOf(self.tab_2), _translate("About", "License")
+ )
+ self.tabWidget.setTabText(
+ self.tabWidget.indexOf(self.tab_3),
+ _translate("About", "3rd Party Licenses"),
+ )
diff --git a/gremlin/ui/ui_activation_condition.py b/gremlin/ui/ui_activation_condition.py
index c5f748fa..c763fb50 100644
--- a/gremlin/ui/ui_activation_condition.py
+++ b/gremlin/ui/ui_activation_condition.py
@@ -16,14 +16,14 @@
# along with this program. If not, see .
-from PySide6 import QtCore, QtGui, QtWidgets
+from PySide6 import QtCore, QtWidgets
import logging
import gremlin.base_profile
import gremlin.config
import gremlin.event_handler
from gremlin.input_types import InputType
-from gremlin import hints, input_devices, macro, util
+from gremlin import hints, util
import gremlin.joystick_handling
import gremlin.shared_state
import gremlin.types
@@ -31,7 +31,6 @@
import gremlin.ui.ui_common
from gremlin.util import load_icon
import gremlin.util
-import gremlin.base_classes as bc
from . import ui_common
from gremlin.base_conditions import *
import gremlin.keyboard
@@ -125,7 +124,7 @@ def _update_counts(self):
self.activation_count_widget.setText(f"Container action conditions ({self.container.condition_count} found):")
else:
# not a container
- self.activation_count_widget.setText(f"Conditions:")
+ self.activation_count_widget.setText("Conditions:")
def _show_hint(self, state):
"""Shows a help message.
@@ -507,7 +506,7 @@ def _axis_ui(self):
self.comparison_dropdown = ui_common.QComboBox()
self.comparison_dropdown.addItem("Inside")
self.comparison_dropdown.addItem("Outside")
- if not self.condition_data.comparison in ("inside","outside"):
+ if self.condition_data.comparison not in ("inside","outside"):
self.condition_data.comparison = "inside"
self.comparison_dropdown.setCurrentText(self.condition_data.comparison.capitalize())
@@ -555,7 +554,7 @@ def _button_ui(self):
self.comparison_dropdown = ui_common.QComboBox()
self.comparison_dropdown.addItem("Pressed")
self.comparison_dropdown.addItem("Released")
- if not self.condition_data.comparison in ("pressed","released"):
+ if self.condition_data.comparison not in ("pressed","released"):
self.condition_data.comparison = "pressed"
self.comparison_dropdown.setCurrentText(self.condition_data.comparison.capitalize())
self.comparison_dropdown.currentTextChanged.connect(self._comparison_changed_cb)
@@ -585,7 +584,7 @@ def _hat_ui(self):
]
self.comparison_dropdown = ui_common.QHatSelectorComboBox()
- if not self.condition_data.comparison or not self.condition_data.comparison.capitalize() in directions:
+ if not self.condition_data.comparison or self.condition_data.comparison.capitalize() not in directions:
self.condition_data.comparison = "center"
self.comparison_dropdown.setValue(self.condition_data.comparison)
@@ -826,7 +825,7 @@ def _axis_ui(self):
self.comparison_widget = ui_common.QComboBox()
self.comparison_widget.addItem("Inside")
self.comparison_widget.addItem("Outside")
- if not self.condition_data.comparison in ("inside","outside"):
+ if self.condition_data.comparison not in ("inside","outside"):
self.condition_data.comparison = "inside"
self.comparison_widget.setCurrentText(self.condition_data.comparison.capitalize())
self.comparison_widget.currentTextChanged.connect(self._comparison_changed_cb)
@@ -855,7 +854,7 @@ def _button_ui(self):
self.comparison_widget = ui_common.QComboBox()
self.comparison_widget.addItem("Pressed")
self.comparison_widget.addItem("Released")
- if not self.condition_data.comparison in ("pressed","released"):
+ if self.condition_data.comparison not in ("pressed","released"):
self.condition_data.comparison = "pressed"
self.comparison_widget.setCurrentText(self.condition_data.comparison.capitalize())
self.comparison_widget.currentTextChanged.connect(self._comparison_changed_cb)
@@ -876,7 +875,7 @@ def _hat_ui(self):
"South", "South West", "West", "North West"
]
self.comparison_widget = ui_common.QHatSelectorComboBox()
- if not self.condition_data.comparison or not self.condition_data.comparison.capitalize() in directions:
+ if not self.condition_data.comparison or self.condition_data.comparison.capitalize() not in directions:
self.condition_data.comparison = "center"
self.comparison_widget.setValue(self.condition_data.comparison)
self.comparison_widget.valueChanged.connect(self._comparison_changed_cb)
@@ -897,14 +896,14 @@ def _modify_vjoy(self, data):
self.condition_data.input_id = data["input_id"]
if data["input_type"] == InputType.JoystickAxis:
- if not self.condition_data.comparison in ("inside","outside"):
+ if self.condition_data.comparison not in ("inside","outside"):
self.condition_data.comparison = "inside"
elif data["input_type"] == InputType.JoystickButton:
- if not self.condition_data.comparison in ("pressed","released"):
+ if self.condition_data.comparison not in ("pressed","released"):
self.condition_data.comparison = "pressed"
elif data["input_type"] == InputType.JoystickHat:
directions = ("center", "north", "north-east", "east", "south-east","south", "south-west", "west", "north-west")
- if not self.condition_data.comparison in directions:
+ if self.condition_data.comparison not in directions:
self.condition_data.comparison = "center"
self._create_ui()
diff --git a/gremlin/ui/ui_common.py b/gremlin/ui/ui_common.py
index 478954d5..b40e1a20 100644
--- a/gremlin/ui/ui_common.py
+++ b/gremlin/ui/ui_common.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,11 +21,8 @@
import threading
import anytree
import os
-from typing import Optional
import logging
from PySide6 import QtWidgets, QtCore, QtGui
-import PySide6.QtGui
-import PySide6.QtWidgets
import gremlin.base_classes
import gremlin.base_profile
import gremlin.clipboard
@@ -34,22 +31,30 @@
import qtawesome as qta
import gremlin.event_handler
from gremlin.input_types import InputType
-from gremlin.clipboard import Clipboard
import gremlin.input_types
import gremlin.joystick_handling
import gremlin.keyboard
import gremlin.shared_state
import gremlin.types
+import gremlin.singleton_decorator
from qtpy.QtCore import (
- Qt, QSize, QPoint, QPointF, QRectF,
- QEasingCurve, QPropertyAnimation, QSequentialAnimationGroup,
- Slot, Property)
+ Qt,
+ QSize,
+ QPoint,
+ QPointF,
+ QRectF,
+ QEasingCurve,
+ QPropertyAnimation,
+ QSequentialAnimationGroup,
+ Slot,
+ Property,
+)
from qtpy.QtWidgets import QCheckBox
-from qtpy.QtGui import QColor, QBrush, QPaintEvent, QPen, QPainter, QStandardItemModel, QStandardItem
+from qtpy.QtGui import QColor, QBrush, QPaintEvent, QPen, QPainter
from gremlin.util import load_pixmap, load_icon
-import gremlin.util
-import gremlin.ui.ui_common
+#import gremlin.util
+#import gremlin.ui.ui_common
from gremlin.singleton_decorator import SingletonDecorator
from gremlin.types import HatDirection
from dinput import DeviceSummary
@@ -57,124 +62,155 @@
syslog = logging.getLogger("system")
-
-class Color():
+class Color:
@staticmethod
def activeColor():
return "#51f56f" if gremlin.shared_state.is_dark_theme else "#365a75"
+
@staticmethod
def inactiveColor():
return "#686a6e" if gremlin.shared_state.is_dark_theme else "#8c8c8c"
+
@staticmethod
def normalColor():
return "#AAAAAA" if gremlin.shared_state.is_dark_theme else "#111111"
+
@staticmethod
def normalDarkColor():
- return "#AAAAAA"
+ return "#AAAAAA"
+
@staticmethod
def selectedDockTabBackgroundColor():
return "#303030" if gremlin.shared_state.is_dark_theme else "#DDDDDD"
+
@staticmethod
def normalLightColor():
- return "#111111"
+ return "#111111"
+
@staticmethod
def normalGradientColor():
return "#777777" if gremlin.shared_state.is_dark_theme else "#CCCCCC"
+
@staticmethod
def backgroundColor():
return "#212121" if gremlin.shared_state.is_dark_theme else "#EEEEEE"
+
@staticmethod
def highlightBackgroundColor():
return "#66612f" if gremlin.shared_state.is_dark_theme else "#FFF4B0"
+
@staticmethod
def borderColor():
return "#444444" if gremlin.shared_state.is_dark_theme else "#111111"
+
@staticmethod
def titleBackgroundColor():
return "#222222" if gremlin.shared_state.is_dark_theme else "#AAAAAA"
+
@staticmethod
def warningColor():
return "#b39f32"
+
@staticmethod
def selectColor():
return "#658265" if gremlin.shared_state.is_dark_theme else "#8FBC8F"
+
@staticmethod
def selectGradientColor():
return "#658265" if gremlin.shared_state.is_dark_theme else "#8FBC8F"
+
@staticmethod
def selectBorderColor():
- return "#408540" if gremlin.shared_state.is_dark_theme else "#76c276"
+ return "#408540" if gremlin.shared_state.is_dark_theme else "#76c276"
+
@staticmethod
def rangeColor():
return "#8FBC8F"
+
@staticmethod
def alternateRangeColor():
return "#8fb9bc"
+
@staticmethod
def rangeBorderColor():
return "#8FBC8F"
+
@staticmethod
def actionIconBackgroundColor():
return "#424242" if gremlin.shared_state.is_dark_theme else "#EEEEEE"
+
@staticmethod
def keyBackgroundColor():
return "#424242" if gremlin.shared_state.is_dark_theme else "#EEEEEE"
+
@staticmethod
def keyEntryBackgroundColor():
return "#293d2d" if gremlin.shared_state.is_dark_theme else "#EEEEEE"
+
@staticmethod
def keyForegroundColor():
return "#AAAAAA" if gremlin.shared_state.is_dark_theme else "#000000"
+
@staticmethod
def keyBorderColor():
return "#AAAAAA" if gremlin.shared_state.is_dark_theme else "#000000"
+
@staticmethod
def keyHoverBorderColor():
return "#457d45" if gremlin.shared_state.is_dark_theme else "#56b056"
+
@staticmethod
def containerBackgroundColor():
return "#101010" if gremlin.shared_state.is_dark_theme else "#EEEEEE"
+
@staticmethod
def actionBackgroundColor():
return "#202020" if gremlin.shared_state.is_dark_theme else "#CCCCCC"
+
@staticmethod
def sliderTickColor():
return "#303030" if gremlin.shared_state.is_dark_theme else "#232323"
+
@staticmethod
def sliderHandleColor():
return "#a7b59e" if gremlin.shared_state.is_dark_theme else "#a7b59e"
+
@staticmethod
def sliderHandleBorderColor():
return "#e0e0e0" if gremlin.shared_state.is_dark_theme else "#e0e0e0"
+
@staticmethod
def sliderRangeBorderColor():
return "#8fb9bc" if gremlin.shared_state.is_dark_theme else "#8fb9bc"
+
@staticmethod
def sliderRangeColor():
return "#8fb9bc" if gremlin.shared_state.is_dark_theme else "#8fb9bc"
+
@staticmethod
def sliderAlternateRangeColor():
return "#8fb9bc" if gremlin.shared_state.is_dark_theme else "#8fb9bc"
+
@staticmethod
def sliderBackgroundColor():
return "#060606" if gremlin.shared_state.is_dark_theme else "#c3c3c3"
+
@staticmethod
def recordColor():
return "#c7450e"
+
@staticmethod
def activeContentColor():
return "#458ae6"
-
-
@staticmethod
- def warningColor(): # color for the warning flag
+ def warningColor(): # color for the warning flag
return "#ab8d18" if gremlin.shared_state.is_dark_theme else "#fc1900"
@staticmethod
def cssApplication():
border_color = Color.borderColor()
- background_color = Color.backgroundColor()
+ Color.backgroundColor()
if gremlin.config.Configuration().is_debug:
relative_path = "gfx/"
else:
@@ -186,8 +222,8 @@ def cssApplication():
radio_unchecked = f"{prefix}radiobox_blank.png"
radio_checked = f"{prefix}radiobox_marked.png"
-
- css = f'''
+
+ css = f"""
QCheckBox::indicator {{
width: 18px;
height: 18px;
@@ -221,12 +257,10 @@ def cssApplication():
border: 1px solid {border_color};
}}
- '''
+ """
# print (css)
-
-
- # css = '''
+ # css = '''
# QCheckBox::indicator {
# width: 18px;
# height: 18px;
@@ -254,14 +288,13 @@ def cssApplication():
# border: #444444;
# }
-
# '''
return css
-
+
@staticmethod
def cssButtonState():
- ''' gets a pushbutton state for the input viewer '''
+ """gets a pushbutton state for the input viewer"""
# buttons = ("mdi.radiobox-blank","mdi.radiobox-marked","mdi.checkbox-blank-outline","mdi.checkbox-intermediate")
# colors = (
@@ -269,7 +302,6 @@ def cssButtonState():
# ("#111111","")
# )
-
# root_folder = gremlin.shared_state.root_path
# icon_size = QtCore.QSize(64,64)
# for name in buttons:
@@ -280,7 +312,7 @@ def cssButtonState():
# if os.path.isfile(the_path):
# os.unlink(the_path)
# the_path = the_path.replace("\\","/")
-
+
# # f = QtCore.QFile(the_path)
# # f.open(QtCore.QIODeviceBase.OpenModeFlag.WriteOnly)
# pixmap = icon.pixmap(icon_size)
@@ -290,12 +322,12 @@ def cssButtonState():
normal_color = Color.normalColor()
normal_gradient_color = Color.normalGradientColor()
background_color = Color.keyBackgroundColor()
-
- border_color = Color.borderColor()
+
+ Color.borderColor()
selected_border_color = Color.selectBorderColor()
selected_color = Color.selectColor()
selected_gradient_color = Color.selectGradientColor()
- css = f'''
+ css = f"""
QPushButton {{
border: 2px solid #8f8f91;
border-radius: 15px;
@@ -319,12 +351,12 @@ def cssButtonState():
{{
color: {background_color};
}}
- '''
+ """
return css
-
+
@staticmethod
def PenColors():
- ''' list of pen colors '''
+ """list of pen colors"""
colors = {
0: "#c0c0c0" if gremlin.shared_state.is_dark_theme else "#111111",
@@ -338,8 +370,7 @@ def PenColors():
8: "#2cb2f5",
}
return colors
-
-
+
@staticmethod
def Pens():
# Pre-defined colors for eight time series
@@ -349,18 +380,18 @@ def Pens():
pens[index] = QtGui.QPen(QtGui.QColor(colors[index]), 2 if index else 1)
return pens
-class WidgetTracker():
+class WidgetTracker:
def __init__(self):
self._widget_cache = {}
-
+
def registerWidget(self, widget):
- ''' registers widget for cleanup - this is needed because QT doesn't tell us when widgets are discarded so we need to manually track this here
- so widgets cleanup correctly and remove any hooks / references '''
+ """registers widget for cleanup - this is needed because QT doesn't tell us when widgets are discarded so we need to manually track this here
+ so widgets cleanup correctly and remove any hooks / references"""
self._widget_cache[widget] = widget
def unregisterWidget(self, widget):
- ''' removes a widget from the cleanup list'''
+ """removes a widget from the cleanup list"""
if widget in self._widget_cache:
if hasattr(widget, "_cleanup_ui"):
widget._cleanup_ui()
@@ -368,20 +399,19 @@ def unregisterWidget(self, widget):
widget.setParent(None)
def clearRegisteredWidgets(self):
- ''' cleanup all widgets '''
+ """cleanup all widgets"""
for widget in self._widget_cache.values():
if hasattr(widget, "_cleanup_ui"):
widget._cleanup_ui()
widget.setParent(None)
self._widget_cache = {}
verbose = gremlin.config.Configuration().verbose_mode_ui
- if verbose: syslog.info("TRACKER: clear()")
-
-
+ if verbose:
+ syslog.info("TRACKER: clear()")
@SingletonDecorator
-class DeviceWidgetTracker():
+class DeviceWidgetTracker:
def __init__(self):
self._widget_cache = {}
self.any_mode = "[any]"
@@ -391,13 +421,13 @@ def registerWidget(self, widget, device_guid, mode, input_type, input_id, key):
mode = self.any_mode
if not isinstance(device_guid, str):
device_guid = str(device_guid)
- if not device_guid in self._widget_cache:
+ if device_guid not in self._widget_cache:
self._widget_cache[device_guid] = {}
- if not mode in self._widget_cache[device_guid]:
+ if mode not in self._widget_cache[device_guid]:
self._widget_cache[device_guid][mode] = {}
- if not input_type in self._widget_cache[device_guid][mode]:
+ if input_type not in self._widget_cache[device_guid][mode]:
self._widget_cache[device_guid][mode][input_type] = {}
- if not input_id in self._widget_cache[device_guid][mode][input_type]:
+ if input_id not in self._widget_cache[device_guid][mode][input_type]:
self._widget_cache[device_guid][mode][input_type][input_id] = {}
self._widget_cache[device_guid][mode][input_type][input_id][key] = widget
@@ -411,14 +441,21 @@ def unregisterWidget(self, device_guid, mode, input_type, input_id, key):
for mode in self._widget_cache[device_guid]:
if input_type in self._widget_cache[device_guid][mode]:
if input_id in self._widget_cache[device_guid][mode][input_type]:
- if key in self._widget_cache[device_guid][mode][input_type][input_id]:
- self._widget_cache[device_guid][mode][input_type][input_id] = None
-
+ if (
+ key
+ in self._widget_cache[device_guid][mode][input_type][
+ input_id
+ ]
+ ):
+ self._widget_cache[device_guid][mode][input_type][
+ input_id
+ ] = None
def clear(self):
self._widget_cache = {}
verbose = gremlin.config.Configuration().verbose_mode_ui
- if verbose: syslog.info("DEVICE WIDGET TRACKER: clear()")
+ if verbose:
+ syslog.info("DEVICE WIDGET TRACKER: clear()")
def getWidget(self, device_guid, mode, input_type, input_id, key):
if not mode:
@@ -429,9 +466,15 @@ def getWidget(self, device_guid, mode, input_type, input_id, key):
for mode in self._widget_cache[device_guid]:
if input_type in self._widget_cache[device_guid][mode]:
if input_id in self._widget_cache[device_guid][mode][input_type]:
- if key in self._widget_cache[device_guid][mode][input_type][input_id]:
- return self._widget_cache[device_guid][mode][input_type][input_id][key]
-
+ if (
+ key
+ in self._widget_cache[device_guid][mode][input_type][
+ input_id
+ ]
+ ):
+ return self._widget_cache[device_guid][mode][input_type][
+ input_id
+ ][key]
def getCache(self, device_guid, mode, input_type):
if not mode:
@@ -446,14 +489,13 @@ def getCache(self, device_guid, mode, input_type):
self._widget_cache[device_guid][mode] = {}
self._widget_cache[device_guid][mode][input_type] = {}
return self._widget_cache[device_guid][mode][input_type]
-
@SingletonDecorator
-class StateTracker():
+class StateTracker:
def __init__(self):
self._axis_cache = {}
-
+
self._button_cache = {}
self._state_cache = {}
el = gremlin.event_handler.EventListener()
@@ -463,20 +505,18 @@ def __init__(self):
el.update_input_state.connect(self._update_input_state)
self._queue = []
-
def _key(self, input_id):
if hasattr(input_id, "message_key"):
# item has a special key to use for indexing input ID
return input_id.message_key
return str(input_id)
-
def registerButtonState(self, widget, device_guid, input_type, input_id):
if not isinstance(device_guid, str):
device_guid = str(device_guid)
- if not device_guid in self._button_cache:
+ if device_guid not in self._button_cache:
self._button_cache[device_guid] = {}
- if not input_type in self._button_cache[device_guid]:
+ if input_type not in self._button_cache[device_guid]:
self._button_cache[device_guid][input_type] = {}
key = self._key(input_id)
if key:
@@ -496,13 +536,13 @@ def unregisterButtonState(self, device_guid, input_type, input_id):
del self._button_cache[device_guid][input_type][key]
def registerAxisState(self, widget, device_guid, input_type, input_id):
- if hasattr(widget,"deleted"):
+ if hasattr(widget, "deleted"):
widget.deleted.connect(self._widget_deleted)
if not isinstance(device_guid, str):
device_guid = str(device_guid)
- if not device_guid in self._axis_cache:
+ if device_guid not in self._axis_cache:
self._axis_cache[device_guid] = {}
- if not input_type in self._axis_cache[device_guid]:
+ if input_type not in self._axis_cache[device_guid]:
self._axis_cache[device_guid][input_type] = {}
key = self._key(input_id)
# print (f"Add axis {key}")
@@ -512,17 +552,15 @@ def registerAxisState(self, widget, device_guid, input_type, input_id):
def _widget_deleted(self, widget):
widget.deleted.disconnect(self._widget_deleted)
self._delete_widget(widget)
-
-
+
def _delete_widget(self, widget):
- ''' deletes a widget '''
+ """deletes a widget"""
for device_guid in self._axis_cache:
for input_type in self._axis_cache[device_guid]:
for key in self._axis_cache[device_guid][input_type]:
if self._axis_cache[device_guid][input_type][key] == widget:
del self._axis_cache[device_guid][input_type][key]
break
-
def clear(self):
self._axis_cache.clear()
@@ -537,14 +575,11 @@ def unregisterAxisState(self, device_guid, input_type, input_id):
if key in self._axis_cache[device_guid][input_type]:
del self._axis_cache[device_guid][input_type][key]
-
def _button_state_change(self, event: gremlin.event_handler.Event):
-
-
if gremlin.shared_state.is_running:
# do not update while profile is running
- return
-
+ return
+
self._process_event(event)
def _process_event(self, event):
@@ -565,7 +600,7 @@ def _process_event(self, event):
self._update_widget(device_guid, input_type, input_id, state)
def _get_device_state(self, device_guid, input_type, input_id):
- ''' gets the current state or value of the item '''
+ """gets the current state or value of the item"""
state = None
match input_type:
case InputType.JoystickAxis:
@@ -575,24 +610,24 @@ def _get_device_state(self, device_guid, input_type, input_id):
case InputType.JoystickHat:
value = gremlin.joystick_handling.get_hat(device_guid, input_id)
import vjoy.vjoy
- if value in vjoy.vjoy.Hat.to_continuous_position:
- state = vjoy.vjoy.Hat.to_continuous_position[value]
+ if value in vjoy.vjoy.Hat.to_continuous_position:
+ state = vjoy.vjoy.Hat.to_continuous_position[value]
case InputType.OpenSoundControl:
pass
case InputType.Midi:
pass
case InputType.KeyboardLatched:
- pass
+ pass
return state
def _update_widget(self, device_guid, input_type, input_id, state):
- ''' updates the state of the widget'''
+ """updates the state of the widget"""
if not isinstance(device_guid, str):
device_guid = str(device_guid)
# syslog = logging.getLogger("system")
- device_name = gremlin.shared_state.get_device_name(device_guid)
+ # device_name = gremlin.shared_state.get_device_name(device_guid)
if device_guid in self._button_cache:
if input_type in self._button_cache[device_guid]:
key = self._key(input_id)
@@ -614,28 +649,26 @@ def _update_widget(self, device_guid, input_type, input_id, state):
if hasattr(widget, "_update_value"):
widget._update_value(state)
-
except:
# discarded by QT - ignore
pass
# else:
- # syslog.info(f"ButtonState: {device_name} type {InputType.to_display_name(event.event_type)} input {event.identifier} connect")
-
-
+ # syslog.info(f"ButtonState: {device_name} type {InputType.to_display_name(event.event_type)} input {event.identifier} connect")
+
def _store_state(self, device_guid, input_type, input_id, state):
- ''' stores the last button state for the given input '''
+ """stores the last button state for the given input"""
if not isinstance(device_guid, str):
device_guid = str(device_guid)
- if not device_guid in self._state_cache:
+ if device_guid not in self._state_cache:
self._state_cache[device_guid] = {}
- if not input_type in self._state_cache[device_guid]:
+ if input_type not in self._state_cache[device_guid]:
self._state_cache[device_guid][input_type] = {}
# device_name = gremlin.joystick_handling.device_name_from_guid(device_guid)
# print (f"Store: {device_name} {InputType.to_display_name(input_type)} {input_id} state: {state}")
self._state_cache[device_guid][input_type][input_id] = state
def _get_state(self, device_guid, input_type, input_id):
- ''' gets the last button state for the given input '''
+ """gets the last button state for the given input"""
if not isinstance(device_guid, str):
device_guid = str(device_guid)
if device_guid in self._state_cache:
@@ -644,15 +677,11 @@ def _get_state(self, device_guid, input_type, input_id):
return self._state_cache[device_guid][input_type][input_id]
return None
-
-
-
-
- def _axis_state_change(self, event : gremlin.event_handler.Event):
+ def _axis_state_change(self, event: gremlin.event_handler.Event):
if gremlin.shared_state.is_running:
# do not update while profile is running
- return
-
+ return
+
device_guid = event.device_guid
input_type = event.event_type
input_id = event.identifier
@@ -670,12 +699,9 @@ def _axis_state_change(self, event : gremlin.event_handler.Event):
except:
# discarded by QT - ignore
pass
-
-
-
-
+
def getButtonWidget(self, device_guid, input_type, input_id):
- ''' gets the widget registered for a button state tracking'''
+ """gets the widget registered for a button state tracking"""
if not isinstance(device_guid, str):
device_guid = str(device_guid)
if device_guid in self._button_cache:
@@ -684,9 +710,9 @@ def getButtonWidget(self, device_guid, input_type, input_id):
if key in self._button_cache[device_guid][input_type]:
widget = self._button_cache[device_guid][input_type][key]
return widget
-
+
return None
-
+
def getAxisWidget(self, device_guid, input_type, input_id):
if not isinstance(device_guid, str):
device_guid = str(device_guid)
@@ -697,7 +723,7 @@ def getAxisWidget(self, device_guid, input_type, input_id):
widget = self._axis_cache[device_guid][input_type][key]
return widget
return None
-
+
@QtCore.Slot(object, object, object)
def _select_input_completed(self, device_guid, input_type, input_id):
state = self._get_state(device_guid, input_type, input_id)
@@ -706,10 +732,9 @@ def _select_input_completed(self, device_guid, input_type, input_id):
if state is not None:
self._update_widget(device_guid, input_type, input_id, state)
-
@QtCore.Slot(object)
def _update_input_state(self, device_guid):
- ''' updates all the state widgets related to a single device based on stored state '''
+ """updates all the state widgets related to a single device based on stored state"""
if not isinstance(device_guid, str):
device_guid = str(device_guid)
# buttons
@@ -732,14 +757,13 @@ def _update_input_state(self, device_guid):
state = self._get_device_state(device_guid, input_type, input_id)
if state is not None:
self._update_widget(device_guid, input_type, input_id, state)
-
_tabsplitter_tracker = WidgetTracker()
_state_tracker = StateTracker()
-class ContainerViewTypes(enum.Enum):
+class ContainerViewTypes(enum.Enum):
"""Enumeration of view types used by containers."""
Action = 1
@@ -768,19 +792,18 @@ def to_enum(value):
_ContainerView_to_enum_lookup = {
"action": ContainerViewTypes.Action,
"conditions": ContainerViewTypes.Conditions,
- "virtual button": ContainerViewTypes.VirtualButton
+ "virtual button": ContainerViewTypes.VirtualButton,
}
_ContainerView_to_string_lookup = {
ContainerViewTypes.Action: "Action",
ContainerViewTypes.Conditions: "Conditions",
- ContainerViewTypes.VirtualButton: "Virtual Button"
+ ContainerViewTypes.VirtualButton: "Virtual Button",
}
class AbstractModel(QtCore.QObject):
-
"""Base class for MVC models."""
data_changed = QtCore.Signal()
@@ -809,17 +832,19 @@ def data(self, index):
class AbstractView(QtWidgets.QWidget):
-
"""Base class for MVC views."""
# Signal emitted when a entry is selected
- item_selected = QtCore.Signal(int, bool) # index of the item being selected
+ item_selected = QtCore.Signal(int, bool) # index of the item being selected
item_edit = QtCore.Signal(object, int, object) # widget, index, model data object
- item_edit_curve = QtCore.Signal(object, int, object) # widget, index , model data object
- item_delete_curve = QtCore.Signal(object, int, object) # widget, index , model data object
+ item_edit_curve = QtCore.Signal(
+ object, int, object
+ ) # widget, index , model data object
+ item_delete_curve = QtCore.Signal(
+ object, int, object
+ ) # widget, index , model data object
item_closed = QtCore.Signal(object, int, object) # widget, index, model data object
-
def __init__(self, parent=None):
"""Creates a new view instance.
@@ -831,6 +856,7 @@ def __init__(self, parent=None):
@property
def model(self):
return self._model
+
@model.setter
def model(self, value):
if value != self._model:
@@ -864,7 +890,6 @@ def _model_changed(self):
class LeftRightPushButton(QtWidgets.QPushButton):
-
"""Implements a push button that distinguishes between left and right
mouse clicks."""
@@ -891,7 +916,6 @@ def mousePressEvent(self, event):
class NoKeyboardPushButton(QtWidgets.QPushButton):
-
"""Standard PushButton which does not react to keyboard input."""
def __init__(self, *args, **kwargs):
@@ -905,43 +929,50 @@ def keyPressEvent(self, event):
"""
pass
-
-
@property
def data(self):
return self._data
+
@data.setter
def data(self, value):
self._data = value
-
-
class QFloatLineEdit(QtWidgets.QLineEdit):
- ''' double input validator with optional range limits for input axis
+ """double input validator with optional range limits for input axis
- this line edit behaves like a spin box so it's interchangeable
+ this line edit behaves like a spin box so it's interchangeable
- '''
+ """
- valueChanged = QtCore.Signal(float) # fires when the value changes
- doubleClick = QtCore.Signal() # fires when the input is double clicked
+ valueChanged = QtCore.Signal(float) # fires when the value changes
+ doubleClick = QtCore.Signal() # fires when the input is double clicked
- def __init__(self, data = None, min_range = -1.0, max_range = 1.0, decimals = 3, step = 0.01, value = 0.0, chars = 8, parent = None):
+ def __init__(
+ self,
+ data=None,
+ min_range=-1.0,
+ max_range=1.0,
+ decimals=3,
+ step=0.01,
+ value=0.0,
+ chars=8,
+ parent=None,
+ ):
super().__init__(parent)
self._min_range = min_range
self._max_range = max_range
self._step = step
self._decimals = decimals
- #self._validator = QFloatLineEdit.FloatValidator(bottom=min_range, top=max_range)
+ # self._validator = QFloatLineEdit.FloatValidator(bottom=min_range, top=max_range)
# self._validator = QtGui.QDoubleValidator(bottom=min_range, top=max_range)
# self._validator.setLocale(self.locale()) # handle correct floating point separator
# self._validator.setNotation(QtGui.QDoubleValidator.Notation.StandardNotation)
- #self.setValidator(self._validator)
+ # self.setValidator(self._validator)
self.textChanged.connect(self._validate)
self.installEventFilter(self)
- #self.setText("0")
+ # self.setText("0")
self.setValue(value)
self._data = data
if chars > 0:
@@ -950,12 +981,12 @@ def __init__(self, data = None, min_range = -1.0, max_range = 1.0, decimals = 3,
else:
self.chars = 0
-
@property
def chars(self) -> int:
return self._chars
+
@chars.setter
- def chars(self, value : int):
+ def chars(self, value: int):
if value > 0 and value != self._chars:
self._chars = value
self._update_width(value)
@@ -964,19 +995,15 @@ def chars(self, value : int):
self.setMaximumWidth(QSize.maxQSize().width())
def _update_width(self, chars):
- w = get_text_width(str("m"*chars))
+ w = get_text_width(str("m" * chars))
self.setMaximumWidth(w)
-
-
-
-
def eventFilter(self, widget, event):
t = event.type()
if t == QtCore.QEvent.Type.Wheel:
# handle wheel up/down change
if self.isReadOnly():
- return True # cannot change the value if readonly
+ return True # cannot change the value if readonly
v = self._to_value()
if v is not None:
eh = gremlin.event_handler.EventListener()
@@ -991,18 +1018,18 @@ def eventFilter(self, widget, event):
v = gremlin.util.clamp(v, self._min_range, self._max_range)
self.setValue(v)
- return True # filter the wheel event
+ return True # filter the wheel event
elif t == QtCore.QEvent.Type.FocusAboutToChange:
value = self._to_value()
if value is None:
- return True # skip the event
+ return True # skip the event
elif t == QtCore.QEvent.Type.FocusOut:
# format the input to the correct decimals
self.setValue(self.value())
elif t == QtCore.QEvent.Type.MouseButtonDblClick:
self.doubleClick.emit()
return False
-
+
def keyPressEvent(self, event):
if event == QtGui.QKeySequence.StandardKey.Paste:
text = QtWidgets.QApplication.clipboard().text()
@@ -1012,12 +1039,11 @@ def keyPressEvent(self, event):
value = float(text)
self.setValue(value)
return True
- except:
+ except ValueError:
pass
return super().keyPressEvent(event)
-
def _update_value(self, value):
if value is None:
return
@@ -1025,27 +1051,25 @@ def _update_value(self, value):
self.setText(s_value)
self.valueChanged.emit(value)
-
-
@QtCore.Slot()
def _validate(self):
- ''' called whenever the text changes '''
+ """called whenever the text changes"""
text = self.text()
value = self._to_value(text)
return value is not None
- def setValue(self, value : float):
- ''' sets the value '''
+ def setValue(self, value: float):
+ """sets the value"""
self._update_value(value)
- def _to_value(self, text : str = None):
+ def _to_value(self, text: str = None):
if text is None:
text = self.text()
try:
value = float(text)
except:
return None
-
+
if value < self._min_range:
value = self._min_range
with QtCore.QSignalBlocker(self):
@@ -1055,21 +1079,20 @@ def _to_value(self, text : str = None):
with QtCore.QSignalBlocker(self):
self.setText(f"{value:0.{self._decimals}f}")
return value
-
def value(self) -> float:
- ''' current value, None if not a valid input'''
+ """current value, None if not a valid input"""
value = self._to_value()
if value is not None:
return value
return None
def isValid(self):
- ''' true if the input in the box is currently valid'''
+ """true if the input in the box is currently valid"""
return self.hasAcceptableInput()
def step(self):
- ''' mouse wheel step value'''
+ """mouse wheel step value"""
return self._step
def setStep(self, step):
@@ -1102,12 +1125,12 @@ def setRange(self, bottom, top):
def setMaximum(self, top):
self._max_range = top
- #self._validator.setTop(top)
+ # self._validator.setTop(top)
self._update_value(self.value())
def setMinimum(self, bottom):
self._min_range = bottom
- #self._validator.setBottom(bottom)
+ # self._validator.setBottom(bottom)
self._update_value(self.value())
def minimum(self):
@@ -1115,33 +1138,45 @@ def minimum(self):
def maximum(self):
return self._max_range
-
+
class QFloatLineEditEx(QtWidgets.QLineEdit):
- ''' double input validator with optional range limits for input axis
+ """double input validator with optional range limits for input axis
- this line edit behaves like a spin box so it's interchangeable
+ this line edit behaves like a spin box so it's interchangeable
- '''
+ """
- valueChanged = QtCore.Signal(float) # fires when the value changes
- doubleClick = QtCore.Signal() # fires when the input is double clicked
+ valueChanged = QtCore.Signal(float) # fires when the value changes
+ doubleClick = QtCore.Signal() # fires when the input is double clicked
- def __init__(self, data = None, min_range = -1.0, max_range = 1.0, decimals = 3, step = 0.01, value = 0.0, chars = 8, parent = None):
+ def __init__(
+ self,
+ data=None,
+ min_range=-1.0,
+ max_range=1.0,
+ decimals=3,
+ step=0.01,
+ value=0.0,
+ chars=8,
+ parent=None,
+ ):
super().__init__(parent)
self._min_range = min_range
self._max_range = max_range
self._step = step
self._decimals = decimals
- #self._validator = QFloatLineEdit.FloatValidator(bottom=min_range, top=max_range)
+ # self._validator = QFloatLineEdit.FloatValidator(bottom=min_range, top=max_range)
self._validator = QtGui.QDoubleValidator(bottom=min_range, top=max_range)
- self._validator.setLocale(self.locale()) # handle correct floating point separator
+ self._validator.setLocale(
+ self.locale()
+ ) # handle correct floating point separator
self._validator.setNotation(QtGui.QDoubleValidator.Notation.StandardNotation)
self.setValidator(self._validator)
self.textChanged.connect(self._validate)
self.installEventFilter(self)
- #self.setText("0")
+ # self.setText("0")
self.setValue(value)
self._data = data
if chars > 0:
@@ -1150,12 +1185,12 @@ def __init__(self, data = None, min_range = -1.0, max_range = 1.0, decimals = 3,
else:
self.chars = 0
-
@property
def chars(self) -> int:
return self._chars
+
@chars.setter
- def chars(self, value : int):
+ def chars(self, value: int):
if value > 0 and value != self._chars:
self._chars = value
self._update_width(value)
@@ -1164,19 +1199,15 @@ def chars(self, value : int):
self.setMaximumWidth(QSize.maxQSize().width())
def _update_width(self, chars):
- w = get_text_width(str("m"*chars))
+ w = get_text_width(str("m" * chars))
self.setMaximumWidth(w)
-
-
-
-
def eventFilter(self, widget, event):
t = event.type()
if t == QtCore.QEvent.Type.Wheel:
# handle wheel up/down change
if self.isReadOnly():
- return True # cannot change the value if readonly
+ return True # cannot change the value if readonly
v = self.value()
if v is not None:
eh = gremlin.event_handler.EventListener()
@@ -1191,20 +1222,19 @@ def eventFilter(self, widget, event):
v = gremlin.util.clamp(v, self._min_range, self._max_range)
self.setValue(v)
- return True # filter the wheel event
+ return True # filter the wheel event
elif t == QtCore.QEvent.Type.FocusAboutToChange:
if not self.hasAcceptableInput():
- return True # skip the event
+ return True # skip the event
elif t == QtCore.QEvent.Type.FocusOut:
if not self.hasAcceptableInput():
- return True # skip the event
+ return True # skip the event
# format the input to the correct decimals
self.setValue(self.value())
elif t == QtCore.QEvent.Type.MouseButtonDblClick:
self.doubleClick.emit()
return False
-
def _update_value(self, value):
if value is None:
return
@@ -1215,21 +1245,19 @@ def _update_value(self, value):
self.setText(s_value)
self.valueChanged.emit(value)
-
-
@QtCore.Slot()
def _validate(self):
- ''' called whenever the text changes '''
+ """called whenever the text changes"""
if self.hasAcceptableInput():
value = self.value()
self.valueChanged.emit(value)
- def setValue(self, value : float):
- ''' sets the value '''
+ def setValue(self, value: float):
+ """sets the value"""
self._update_value(value)
def value(self) -> float:
- ''' current value, None if not a valid input'''
+ """current value, None if not a valid input"""
if self.hasAcceptableInput():
return float(self.text())
try:
@@ -1242,11 +1270,11 @@ def value(self) -> float:
return None
def isValid(self):
- ''' true if the input in the box is currently valid'''
+ """true if the input in the box is currently valid"""
return self.hasAcceptableInput()
def step(self):
- ''' mouse wheel step value'''
+ """mouse wheel step value"""
return self._step
def setStep(self, step):
@@ -1291,19 +1319,29 @@ def minimum(self):
return self._min_range
def maximum(self):
- return self._max_range
+ return self._max_range
+
class QIntLineEdit(QtWidgets.QLineEdit):
- ''' integer input validator with optional range limits for input axis
+ """integer input validator with optional range limits for input axis
- this line edit behaves like a spin box so it's interchangeable
+ this line edit behaves like a spin box so it's interchangeable
- '''
+ """
- valueChanged = QtCore.Signal(float) # fires when the value changes
- doubleClick = QtCore.Signal() # fires when the input is double clicked
+ valueChanged = QtCore.Signal(float) # fires when the value changes
+ doubleClick = QtCore.Signal() # fires when the input is double clicked
- def __init__(self, data = None, min_range = -16383, max_range = 16384, step = 1, value = 0, chars = 8, parent = None):
+ def __init__(
+ self,
+ data=None,
+ min_range=-16383,
+ max_range=16384,
+ step=1,
+ value=0,
+ chars=8,
+ parent=None,
+ ):
super().__init__(parent)
if min_range > max_range:
max_range, min_range = min_range, max_range
@@ -1311,9 +1349,10 @@ def __init__(self, data = None, min_range = -16383, max_range = 16384, step = 1,
self._max_range = max_range
self._step = step
-
- self._validator = QtGui.QIntValidator(min_range, max_range)
- self._validator.setLocale(self.locale()) # handle correct floating point separator
+ self._validator = QtGui.QIntValidator(min_range, max_range)
+ self._validator.setLocale(
+ self.locale()
+ ) # handle correct floating point separator
self.textChanged.connect(self._validate)
self.setValidator(self._validator)
self.installEventFilter(self)
@@ -1325,13 +1364,12 @@ def __init__(self, data = None, min_range = -16383, max_range = 16384, step = 1,
else:
self.chars = 0
-
@property
def chars(self) -> int:
return self._chars
-
+
@chars.setter
- def chars(self, value : int):
+ def chars(self, value: int):
if value > 0 and value != self._chars:
self._chars = value
self._update_width(value)
@@ -1340,12 +1378,13 @@ def chars(self, value : int):
self.setMaximumWidth(QSize.maxQSize().width())
def _update_width(self, chars):
- w = get_text_width(str("m"*chars))
+ w = get_text_width(str("m" * chars))
self.setMaximumWidth(w)
@property
def data(self):
return self._data
+
@data.setter
def data(self, value):
self._data = value
@@ -1355,7 +1394,7 @@ def eventFilter(self, widget, event):
if t == QtCore.QEvent.Type.Wheel:
# handle wheel up/down change
if self.isReadOnly():
- return True # cannot change the value if readonly
+ return True # cannot change the value if readonly
v = self.value()
if v is not None:
eh = gremlin.event_handler.EventListener()
@@ -1370,21 +1409,20 @@ def eventFilter(self, widget, event):
v = gremlin.util.clamp(v, self._min_range, self._max_range)
self.setValue(v)
- return True # filter the wheel event
+ return True # filter the wheel event
elif t == QtCore.QEvent.Type.FocusAboutToChange:
if not self.hasAcceptableInput():
- return True # skip the event
+ return True # skip the event
elif t == QtCore.QEvent.Type.FocusOut:
if not self.hasAcceptableInput():
- return True # skip the event
+ return True # skip the event
# format the input to the correct decimals
self.setValue(self.value())
elif t == QtCore.QEvent.Type.MouseButtonDblClick:
self.doubleClick.emit()
return False
-
- def _update_value(self, value : int):
+ def _update_value(self, value: int):
other = self.value()
if value is None and other is None:
@@ -1395,21 +1433,19 @@ def _update_value(self, value : int):
if other is not None and other != value:
self.valueChanged.emit(int(value))
-
-
@QtCore.Slot()
def _validate(self):
- ''' called whenever the text changes '''
+ """called whenever the text changes"""
if self.hasAcceptableInput():
value = self.value()
self.valueChanged.emit(value)
- def setValue(self, value : int):
- ''' sets the value '''
+ def setValue(self, value: int):
+ """sets the value"""
self._update_value(int(value))
def value(self) -> int:
- ''' current value, None if not a valid input'''
+ """current value, None if not a valid input"""
if self.hasAcceptableInput():
return int(self.text())
try:
@@ -1417,16 +1453,18 @@ def value(self) -> int:
if text:
value = int(self.text())
return value
- except:
- pass
+ except ValueError as e:
+ logging.error(f"ValueError: {e}")
+ except Exception as e:
+ logging.error(f"Unexpected error: {e}")
return None
def isValid(self):
- ''' true if the input in the box is currently valid'''
+ """true if the input in the box is currently valid"""
return self.hasAcceptableInput()
def step(self):
- ''' mouse wheel step value'''
+ """mouse wheel step value"""
return self._step
def setStep(self, step):
@@ -1463,7 +1501,6 @@ def maximum(self):
return self._max_range
-
class DynamicDoubleSpinBox(QFloatLineEdit):
pass
@@ -1471,14 +1508,14 @@ class DynamicDoubleSpinBox(QFloatLineEdit):
def decimal_point(self):
return self.locale().decimalPoint
-class DynamicDoubleSpinBox_legacy(QtWidgets.QDoubleSpinBox):
+class DynamicDoubleSpinBox_legacy(QtWidgets.QDoubleSpinBox):
"""Implements a double spin box which dynamically overwrites entries."""
valid_chars = [str(v) for v in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] + ["-"]
decimal_point = "."
- def __init__(self, parent=None, data = None):
+ def __init__(self, parent=None, data=None):
"""Create a new instance with the specified parent.
:param parent the parent of this widget
@@ -1486,15 +1523,14 @@ def __init__(self, parent=None, data = None):
super().__init__(parent)
DynamicDoubleSpinBox.decimal_point = self.locale().decimalPoint()
if DynamicDoubleSpinBox.decimal_point not in DynamicDoubleSpinBox.valid_chars:
- DynamicDoubleSpinBox.valid_chars.append(
- DynamicDoubleSpinBox.decimal_point
- )
+ DynamicDoubleSpinBox.valid_chars.append(DynamicDoubleSpinBox.decimal_point)
self._data = data
@property
def data(self):
return self._data
+
@data.setter
def data(self, value):
self._data = value
@@ -1510,9 +1546,9 @@ def validate(self, text, pos):
"""
try:
# Discard invalid characters
- if 0 <= pos-1 < len(text):
- if text[pos-1] not in DynamicDoubleSpinBox.valid_chars:
- text = text[:pos-1] + text[pos:]
+ if 0 <= pos - 1 < len(text):
+ if text[pos - 1] not in DynamicDoubleSpinBox.valid_chars:
+ text = text[: pos - 1] + text[pos:]
pos -= 1
# Replace empty parts with the value 0
@@ -1533,7 +1569,11 @@ def validate(self, text, pos):
try:
value_string = format_string.format(float(value_string))
- except:
+ except ValueError as e:
+ logging.error(f"ValueError: {e}")
+ return False
+ except Exception as e:
+ logging.error(f"Unexpected error: {e}")
return False
# Use decimal place separator dictated by the locale settings
@@ -1544,9 +1584,7 @@ def validate(self, text, pos):
return super().validate(text, pos)
-
class AbstractInputSelector(QtWidgets.QWidget):
-
def __init__(self, change_cb, valid_types, parent=None):
super().__init__(parent)
@@ -1577,12 +1615,15 @@ def get_selection(self):
if input_index == -1:
input_index = 0
- input_value = self.input_item_dropdowns[device_index].itemText(
- input_index)
- else:
- input_value = self.input_item_dropdowns[device_index].currentText()
+ # input_value = self.input_item_dropdowns[device_index].itemText(
+ # input_index
+ # )
+ # else:
+ # input_value = self.input_item_dropdowns[device_index].currentText()
- input_type, input_id = self.input_item_dropdowns[device_index].itemData(input_index)
+ input_type, input_id = self.input_item_dropdowns[device_index].itemData(
+ input_index
+ )
# input_type = self._input_type_registry[device_index][input_index]
@@ -1591,11 +1632,7 @@ def get_selection(self):
# else:
# input_id = int(input_value.split()[-1])
- return {
- "device_id": device_id,
- "input_id": input_id,
- "input_type": input_type
- }
+ return {"device_id": device_id, "input_id": input_id, "input_type": input_type}
def set_selection(self, input_type, device_id, input_id):
if device_id not in self._device_id_registry:
@@ -1611,20 +1648,19 @@ def set_selection(self, input_type, device_id, input_id):
item_count = self.input_item_dropdowns[dev_id].count()
# print (f"looking for: {input_type} {input_id} count of items: {item_count}")
for index in range(item_count):
- match_input_type, match_input_id = self.input_item_dropdowns[dev_id].itemData(index)
+ match_input_type, match_input_id = self.input_item_dropdowns[
+ dev_id
+ ].itemData(index)
# print (f"match: type {match_input_type} id {match_input_id} ")
if match_input_type == input_type and match_input_id == input_id:
entry_id = index
# print ("found!")
break
-
-
# Select and display correct combo boxes and entries within
with QtCore.QSignalBlocker(self.device_dropdown):
self.device_dropdown.setCurrentIndex(dev_id)
-
for entry in self.input_item_dropdowns:
with QtCore.QSignalBlocker(entry):
entry.setVisible(False)
@@ -1634,8 +1670,6 @@ def set_selection(self, input_type, device_id, input_id):
entry.setVisible(True)
entry.setCurrentIndex(entry_id)
-
-
def _update_device(self, index):
# Hide all selection dropdowns
@@ -1650,7 +1684,6 @@ def _update_device(self, index):
entry.setCurrentIndex(0)
self._execute_callback()
-
def _initialize(self):
raise gremlin.error.MissingImplementationError(
"Missing implementation of AbstractInputSelector._initialize"
@@ -1674,13 +1707,11 @@ def _create_device_dropdown(self):
self.main_layout.addWidget(self.device_dropdown)
self.device_dropdown.activated.connect(self._update_device)
-
-
def _create_input_dropdown(self):
count_map = {
InputType.JoystickAxis: lambda x: x.axis_count,
InputType.JoystickButton: lambda x: x.button_count,
- InputType.JoystickHat: lambda x: x.hat_count
+ InputType.JoystickHat: lambda x: x.hat_count,
}
self.input_item_dropdowns = []
@@ -1696,22 +1727,18 @@ def _create_input_dropdown(self):
self._input_type_registry.append([])
self.selection_widget = selection
-
# Add items based on the input type
- max_col = 32
+ # max_col = 32
for input_type in self.valid_types:
item_count = count_map[input_type](device)
for i in range(item_count):
- input_id = i+1
+ input_id = i + 1
if input_type == InputType.JoystickAxis:
input_id = device.axis_map[i].axis_index
s_ui = f"Axis {device.axis_names[i]}"
else:
- s_ui = gremlin.common.input_to_ui_string(
- input_type,
- input_id
- )
+ s_ui = gremlin.common.input_to_ui_string(input_type, input_id)
selection.addItem(s_ui, (input_type, input_id))
self._input_type_registry[-1].append(input_type)
@@ -1728,21 +1755,17 @@ def _create_input_dropdown(self):
if len(self.input_item_dropdowns) > 0:
self.input_item_dropdowns[0].setVisible(True)
-
def _execute_callback(self):
self.change_cb(self.get_selection())
def sync(self):
- ''' forces the change cb to be called to update dependents based on values '''
+ """forces the change cb to be called to update dependents based on values"""
self._execute_callback()
-
class JoystickSelector(AbstractInputSelector):
-
"""Widget allowing the selection of input items on a physical joystick."""
-
def __init__(self, change_cb, valid_types, parent=None):
"""Creates a new JoystickSelector instance.
@@ -1752,17 +1775,16 @@ def __init__(self, change_cb, valid_types, parent=None):
"""
super().__init__(change_cb, valid_types, parent)
-
def _initialize(self):
potential_devices = sorted(
gremlin.joystick_handling.joystick_devices(),
- key=lambda x: (x.name, x.device_guid)
+ key=lambda x: (x.name, x.device_guid),
)
for dev in potential_devices:
input_counts = {
InputType.JoystickAxis: dev.axis_count,
InputType.JoystickButton: dev.button_count,
- InputType.JoystickHat: dev.hat_count
+ InputType.JoystickHat: dev.hat_count,
}
has_inputs = False
@@ -1781,13 +1803,8 @@ def _device_identifier(self, device):
class VJoySelector(AbstractInputSelector):
-
"""Widget allowing the selection of vJoy inputs."""
-
-
-
-
def __init__(self, change_cb, valid_types, invalid_ids={}, parent=None):
"""Creates a widget to select a vJoy output.
@@ -1801,14 +1818,13 @@ def __init__(self, change_cb, valid_types, invalid_ids={}, parent=None):
def _initialize(self):
potential_devices = sorted(
- gremlin.joystick_handling.vjoy_devices(),
- key=lambda x: x.vjoy_id
+ gremlin.joystick_handling.vjoy_devices(), key=lambda x: x.vjoy_id
)
for dev in potential_devices:
input_counts = {
InputType.JoystickAxis: dev.axis_count,
InputType.JoystickButton: dev.button_count,
- InputType.JoystickHat: dev.hat_count
+ InputType.JoystickHat: dev.hat_count,
}
has_inputs = False
@@ -1821,22 +1837,19 @@ def _initialize(self):
def _format_device_name(self, device):
return device.name
- #return f"{device.name} ({device.vjoy_id:d})"
- #return f"vJoy Device {device.vjoy_id:d}"
+ # return f"{device.name} ({device.vjoy_id:d})"
+ # return f"vJoy Device {device.vjoy_id:d}"
def _device_identifier(self, device):
return device.vjoy_id
-
class ActionSelector(QtWidgets.QWidget):
-
"""Widget permitting the selection of actions."""
# Signal emitted when an action is going to be added
action_added = QtCore.Signal(str) # add button pressed
- action_paste = QtCore.Signal(object, object) # paste button pressed
-
+ action_paste = QtCore.Signal(object, object) # paste button pressed
def __init__(self, input_type, container, parent=None):
"""Creates a new selector instance.
@@ -1855,7 +1868,7 @@ def __init__(self, input_type, container, parent=None):
self._container = container
self.action_dropdown = QComboBox()
-
+
for name in self._valid_action_list():
self.action_dropdown.addItem(name)
config = gremlin.config.Configuration()
@@ -1867,13 +1880,14 @@ def __init__(self, input_type, container, parent=None):
# clipboard
self.paste_button = QtWidgets.QPushButton()
prefix = "dark_" if gremlin.shared_state.is_dark_theme else ""
- icon = gremlin.util.load_icon(f"{prefix}button_paste.svg")
+ icon = gremlin.util.load_icon(f"gfx/{prefix}button_paste.svg")
self.paste_button.setIcon(icon)
self.paste_button.clicked.connect(self._paste_action)
- self.paste_button.setSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Minimum)
+ self.paste_button.setSizePolicy(
+ QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Minimum
+ )
self.paste_button.setToolTip("Paste Action")
-
self.main_layout.addWidget(self.action_dropdown)
self.main_layout.addWidget(self.add_button)
self.main_layout.addWidget(self.paste_button)
@@ -1883,10 +1897,10 @@ def __init__(self, input_type, container, parent=None):
eh.last_action_changed.connect(self._last_action_changed)
self._container = None
-
@property
def container(self):
return self._container
+
# @container.setter
# def container(self, container):
# self._container = container
@@ -1898,7 +1912,7 @@ def _last_action_changed(self, widget, name):
self.action_dropdown.setCurrentText(name)
def _action_changed(self):
- ''' remember the last selection '''
+ """remember the last selection"""
name = self.action_dropdown.currentText()
config = gremlin.config.Configuration()
config.last_action = name
@@ -1921,7 +1935,7 @@ def _valid_action_list(self):
convert_curve = config.convert_response_curve
control_enabled = config.show_input_enable
- #all_entries = [entry.name for entry in gremlin.plugin_manager.ActionPlugins().repository.values()]
+ # all_entries = [entry.name for entry in gremlin.plugin_manager.ActionPlugins().repository.values()]
for entry in gremlin.plugin_manager.ActionPlugins().repository.values():
if self.input_type in entry.input_types:
if convert_vjoy and entry.name == "Remap":
@@ -1933,7 +1947,6 @@ def _valid_action_list(self):
action_list.append(entry.name)
return sorted(action_list)
-
def _add_action(self, clicked=False):
"""Handles selecting of an action to be added.
@@ -1943,20 +1956,22 @@ def _add_action(self, clicked=False):
self.action_added.emit(self.action_dropdown.currentText())
def _paste_action(self):
- ''' handle paste action '''
+ """handle paste action"""
import gremlin.plugin_manager
+
container = self.container
if container is None:
# find the container if we can
parent = self
while parent is not None:
- if hasattr(parent,"profile_data"):
- if isinstance(parent.profile_data, gremlin.base_profile.AbstractContainer):
+ if hasattr(parent, "profile_data"):
+ if isinstance(
+ parent.profile_data, gremlin.base_profile.AbstractContainer
+ ):
container = parent.profile_data
break
parent = parent.parent()
-
action = gremlin.plugin_manager.ActionPlugins().fromClipboard(container)
if action is None:
return
@@ -1967,21 +1982,23 @@ def _paste_action(self):
self.action_paste.emit(action, self.container)
else:
# dish out a message
- MessageBox(title = f"Invalid Action type ({action.name})",
- prompt = "Unable to paste action because it is not valid for the current input")
-
+ MessageBox(
+ title=f"Invalid Action type ({action.name})",
+ prompt="Unable to paste action because it is not valid for the current input",
+ )
def _clipboard_changed(self, clipboard):
- ''' handles paste button state based on clipboard data '''
+ """handles paste button state based on clipboard data"""
self.paste_button.setEnabled(clipboard.is_action)
- ''' updates the paste button tooltip with the current clipboard contents'''
+ """ updates the paste button tooltip with the current clipboard contents"""
if clipboard.is_action:
self.paste_button.setToolTip(f"Paste action ({clipboard.data.name})")
else:
- self.paste_button.setToolTip(f"Paste action (not available)")
+ self.paste_button.setToolTip("Paste action (not available)")
+
class ModeStyle(anytree.AbstractStyle):
- """ style for anytree mode rendering """
+ """style for anytree mode rendering"""
def __init__(self):
super().__init__("\u2502 ", "\u251c\u2500 ", "\u2514\u2500 ")
@@ -1997,18 +2014,18 @@ def _inheritance_tree_to_labels(labels, tree, level):
# skip the root node
for child in tree.children:
for pre, _, node in anytree.RenderTree(child, style=ModeStyle()):
- labels.append((node.name,f"{pre}{node.name}"))
+ labels.append((node.name, f"{pre}{node.name}"))
+
def get_mode_list(profile_data):
- ''' gets a pairs (display_name, mode) '''
+ """gets a pairs (display_name, mode)"""
profile = profile_data
mode_list = []
-
+
# Create mode name labels visualizing the tree structure
inheritance_tree = profile.build_inheritance_tree()
labels = []
-
_inheritance_tree_to_labels(labels, inheritance_tree, 0)
# Filter the mode names such that they only occur once below
@@ -2020,18 +2037,14 @@ def get_mode_list(profile_data):
for display_name, mode_name in zip(display_names, mode_names):
mode_list.append((display_name, mode_name))
-
return mode_list
-
class ModeWidget(QtWidgets.QWidget):
-
"""Displays the ui for mode selection and management of a device."""
# Signal emitted when the mode changes
- edit_mode_changed = QtCore.Signal(str) # when the edit mode changes
-
+ edit_mode_changed = QtCore.Signal(str) # when the edit mode changes
def __init__(self, parent=None):
"""Creates a new instance.
@@ -2049,9 +2062,8 @@ def __init__(self, parent=None):
el = gremlin.event_handler.EventListener()
el.mode_list_update.connect(self._mode_list_update)
-
def setRuntimeDisabled(self, value):
- ''' enables or disables profile runtime behavior'''
+ """enables or disables profile runtime behavior"""
el = gremlin.event_handler.EventListener()
try:
@@ -2062,41 +2074,42 @@ def setRuntimeDisabled(self, value):
else:
el.profile_start.disconnect(self._profile_start_cb)
el.profile_stop.disconnect(self._profile_stop_cb)
- except:
+ except Exception:
pass
-
@QtCore.Slot()
def _profile_start_cb(self):
self.setEnabled(False)
+
@QtCore.Slot()
def _profile_stop_cb(self):
self.setEnabled(True)
@QtCore.Slot()
def _mode_list_update(self):
- ''' occurs when mode list may have changed '''
+ """occurs when mode list may have changed"""
profile = gremlin.shared_state.current_profile
mode = gremlin.shared_state.current_mode
self.populate_selector(profile, mode)
self.select_mode(mode)
-
def select_mode(self, mode: str):
- ''' selects the mode without firing a change event - ignored if the mode doesn't exist '''
+ """selects the mode without firing a change event - ignored if the mode doesn't exist"""
# syslog = logging.getLogger("system")
syslog.info(f"Mode: set edit selector mode to [{mode}]")
- index = self.edit_mode_selector.findData(mode)
+ index = self.edit_mode_selector.findData(mode)
if index >= 0:
- syslog.info(f"Mode: mode exists")
+ syslog.info("Mode: mode exists")
with QtCore.QSignalBlocker(self.edit_mode_selector):
self.edit_mode_selector.setCurrentIndex(index)
else:
# not found, update the selector
- syslog.info(f"Mode: mode does not exist, repopulating")
+ syslog.info("Mode: mode does not exist, repopulating")
self.populate_selector(gremlin.shared_state.current_profile, mode)
- def populate_selector(self, profile, mode_to_select : str = None, emit : bool = False):
+ def populate_selector(
+ self, profile, mode_to_select: str = None, emit: bool = False
+ ):
"""Adds entries for every mode present in the profile.
:param profile_data the device for which the mode selection is generated
@@ -2109,7 +2122,7 @@ def populate_selector(self, profile, mode_to_select : str = None, emit : bool =
modes = gremlin.shared_state.current_profile.get_modes()
while self.edit_mode_selector.count() > 0:
- self.edit_mode_selector.removeItem(0)
+ self.edit_mode_selector.removeItem(0)
mode_list_pairs = get_mode_list(profile)
self.mode_list = [x[1] for x in mode_list_pairs]
@@ -2140,26 +2153,29 @@ def populate_selector(self, profile, mode_to_select : str = None, emit : bool =
select_index = None
last_edit_mode = gremlin.config.Configuration().get_profile_last_edit_mode()
- if not last_edit_mode in modes:
+ if last_edit_mode not in modes:
last_edit_mode = gremlin.shared_state.current_profile.get_default_mode()
for display_name, mode_name in mode_list_pairs:
self.edit_mode_selector.addItem(display_name, mode_name)
# self.mode_list.append(mode_name)
- if mode_to_select and select_index is None and mode_to_select == mode_name:
+ if (
+ mode_to_select
+ and select_index is None
+ and mode_to_select == mode_name
+ ):
select_index = index
if mode_name == last_edit_mode:
current_index = index
index += 1
if select_index:
- self.edit_mode_selector.setCurrentIndex(select_index)
+ self.edit_mode_selector.setCurrentIndex(select_index)
else:
self.edit_mode_selector.setCurrentIndex(current_index)
if emit:
self._edit_mode_changed_cb(current_index)
-
@QtCore.Slot(int)
def _edit_mode_changed_cb(self, idx):
"""Callback function executed when the mode selection changes.
@@ -2172,20 +2188,16 @@ def _edit_mode_changed_cb(self, idx):
syslog.info(f"Mode: edit selector request change to [{new_mode}]")
self.edit_mode_changed.emit(new_mode)
-
-
-
def _create_widget(self):
"""Creates the mode selection and management dialog."""
# Size policies used
from gremlin.util import load_icon
+
min_min_sp = QtWidgets.QSizePolicy(
- QtWidgets.QSizePolicy.Minimum,
- QtWidgets.QSizePolicy.Minimum
+ QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum
)
exp_min_sp = QtWidgets.QSizePolicy(
- QtWidgets.QSizePolicy.MinimumExpanding,
- QtWidgets.QSizePolicy.Minimum
+ QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum
)
self.profile_options_button_widget = QtWidgets.QPushButton()
@@ -2193,21 +2205,22 @@ def _create_widget(self):
self.profile_options_button_widget.setToolTip("Profile Options")
self.profile_options_button_widget.clicked.connect(self._profile_options_cb)
-
-
# Create mode selector and related widgets
self.edit_label = QtWidgets.QLabel("Profile Edit Mode")
self.edit_label.setSizePolicy(min_min_sp)
self.edit_mode_selector = QComboBox()
self.edit_mode_selector.setSizePolicy(exp_min_sp)
self.edit_mode_selector.setMinimumContentsLength(20)
- self.edit_mode_selector.setToolTip("Selects the active profile mode being edited")
-
+ self.edit_mode_selector.setToolTip(
+ "Selects the active profile mode being edited"
+ )
# add the mode change button
self.mode_change = QtWidgets.QPushButton()
- is_dark = gremlin.shared_state.is_dark_theme
- manage_modes_icon = "gfx/dark_manage_modes.svg" if is_dark else "gfx/manage_modes.svg"
+ is_dark = gremlin.shared_state.is_dark_theme
+ manage_modes_icon = (
+ "gfx/dark_manage_modes.svg" if is_dark else "gfx/manage_modes.svg"
+ )
self.mode_change.setIcon(load_icon(manage_modes_icon))
self.mode_change.setToolTip("Manage Profile Modes")
self.mode_change.clicked.connect(self._manage_modes_cb)
@@ -2224,35 +2237,39 @@ def _create_widget(self):
self.main_layout.addWidget(self.profile_options_button_widget)
def _manage_modes_cb(self):
- ''' calls up the mode change dialog '''
-
- if not self.profile.profile_file or not os.path.isfile(self.profile.profile_file):
- MessageBox(prompt = "Please save the profile before configuring modes.")
+ """calls up the mode change dialog"""
+
+ if not self.profile.profile_file or not os.path.isfile(
+ self.profile.profile_file
+ ):
+ MessageBox(prompt="Please save the profile before configuring modes.")
return
import gremlin.shared_state
+
ui = gremlin.shared_state.ui
ui.manage_modes()
def _profile_options_cb(self):
- import gremlin.ui.dialogs
- import gremlin.ui.ui_common
- if not self.profile.profile_file or not os.path.isfile(self.profile.profile_file):
- gremlin.ui.ui_common.MessageBox(prompt = "Please save the profile before setting options.")
+ if not self.profile.profile_file or not os.path.isfile(
+ self.profile.profile_file
+ ):
+ gremlin.ui.ui_common.MessageBox(
+ prompt="Please save the profile before setting options."
+ )
return
dialog = gremlin.ui.dialogs.ProfileOptionsUi()
dialog.exec()
def currentIndex(self) -> int:
- ''' current selector index '''
+ """current selector index"""
return self.edit_mode_selector.currentIndex()
def currentMode(self) -> str:
- ''' gets the current mode '''
+ """gets the current mode"""
return self.edit_mode_selector.currentData()
-
def setCurrentIndex(self, index):
self.edit_mode_selector.setCurrentIndex(index)
@@ -2265,25 +2282,27 @@ def setCurrentMode(self, current_mode):
syslog.error(f"SetModeError: mode '{current_mode}' is not defined")
def setShowModeEdit(self, value):
- ''' determines if the mode edit button is visible or not '''
+ """determines if the mode edit button is visible or not"""
self.mode_change.setVisible(value)
def setShowProfileOptions(self, value):
- ''' determines if the profile option button is visible or not '''
+ """determines if the profile option button is visible or not"""
self.profile_options_button_widget.setVisible(value)
def setLabelText(self, text):
- ''' changes the label text if needed '''
+ """changes the label text if needed"""
self.edit_label.setText(text)
+
class QBoxFrame(QtWidgets.QFrame):
- ''' boxed frame widget '''
- def __init__(self, data = None, parent = None, selected = False):
+ """boxed frame widget"""
+
+ def __init__(self, data=None, parent=None, selected=False):
super().__init__(parent)
border_color = Color.borderColor()
background_color = Color.backgroundColor()
- css = f'''
+ css = f"""
QFrame {{
border: 1px solid {border_color};
background: {background_color};
@@ -2291,12 +2310,11 @@ def __init__(self, data = None, parent = None, selected = False):
QLabel {{
border: none;
}}
- '''
-
+ """
+
self.setFrameStyle(QtWidgets.QFrame.Plain | QtWidgets.QFrame.Box)
self.setStyleSheet(css)
-
@property
def data(self):
return self._data
@@ -2307,19 +2325,18 @@ def data(self, value):
class InputListenerWidget(QBoxFrame):
-
"""Widget overlaying the main gui while waiting for the user
- to press a key or a joystick button """
+ to press a key or a joystick button"""
- item_selected = QtCore.Signal(object) # called when the items are selected
+ item_selected = QtCore.Signal(object) # called when the items are selected
def __init__(
- self,
- event_types,
- return_kb_event=False,
- multi_keys=False,
- filter_func=None,
- parent=None
+ self,
+ event_types,
+ return_kb_event=False,
+ multi_keys=False,
+ filter_func=None,
+ parent=None,
):
"""Creates a new instance.
@@ -2335,7 +2352,8 @@ def __init__(
:param parent the parent widget of this widget
"""
super().__init__(parent)
- from gremlin.keyboard import key_from_code, key_from_name
+ from gremlin.keyboard import key_from_name
+
self._event_types = event_types
self._return_kb_event = return_kb_event
self._multi_keys = multi_keys
@@ -2345,13 +2363,18 @@ def __init__(
self._abort_timer = threading.Timer(1.0, self._abort_request)
self._multi_key_storage = []
- self._close_on_key = not (InputType.Keyboard in event_types or InputType.KeyboardLatched in event_types)
+ self._close_on_key = not (
+ InputType.Keyboard in event_types
+ or InputType.KeyboardLatched in event_types
+ )
self._esc_key = key_from_name("esc")
# Create and configure the ui overlay
self.main_layout = QtWidgets.QVBoxLayout(self)
self.main_layout.addWidget(
- QtWidgets.QLabel(f"""Please press the desired {self._valid_event_types_string()}.
Hold ESC{'' if self._close_on_key else ' for one second'} to abort.""")
+ QtWidgets.QLabel(
+ f"""Please press the desired {self._valid_event_types_string()}.
Hold ESC{"" if self._close_on_key else " for one second"} to abort."""
+ )
)
gremlin.shared_state.push_suspend_highlighting()
@@ -2361,15 +2384,19 @@ def __init__(
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.setFrameStyle(QtWidgets.QFrame.Plain | QtWidgets.QFrame.Box)
palette = QtGui.QPalette()
- palette.setColor(QtGui.QPalette.ColorRole.Window, QtGui.QColorConstants.DarkGray)
+ palette.setColor(
+ QtGui.QPalette.ColorRole.Window, QtGui.QColorConstants.DarkGray
+ )
self.setPalette(palette)
# Start listening to user key presses
event_listener = gremlin.event_handler.EventListener()
event_listener.keyboard_event.connect(self._kb_event_cb)
- if InputType.JoystickAxis in self._event_types or \
- InputType.JoystickButton in self._event_types or \
- InputType.JoystickHat in self._event_types:
+ if (
+ InputType.JoystickAxis in self._event_types
+ or InputType.JoystickButton in self._event_types
+ or InputType.JoystickHat in self._event_types
+ ):
event_listener.joystick_event.connect(self._joy_event_cb)
elif InputType.Mouse in self._event_types:
if not event_listener.mouseEnabled():
@@ -2379,7 +2406,6 @@ def __init__(
gremlin.windows_event_hook.MouseHook().start()
event_listener.mouse_event.connect(self._mouse_event_cb)
-
def _joy_event_cb(self, event):
"""Passes the pressed joystick event to the provided callback
and closes the overlay.
@@ -2395,7 +2421,9 @@ def _joy_event_cb(self, event):
return
# Ensure the event corresponds to a significant enough change in input
- process_event = gremlin.input_devices.JoystickInputSignificant().should_process(event)
+ process_event = gremlin.input_devices.JoystickInputSignificant().should_process(
+ event
+ )
if event.event_type == InputType.JoystickButton:
process_event &= event.is_pressed
@@ -2419,20 +2447,20 @@ def _kb_event_cb(self, event):
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_keyboard
- if verbose: syslog.info(f"LISTEN: Keyboard event: {event} {key}")
+ if verbose:
+ syslog.info(f"LISTEN: Keyboard event: {event} {key}")
if self._close_on_key:
if key == self._esc_key:
self.close()
- return # ignore keys otherwise
+ return # ignore keys otherwise
# Return immediately once the first key press is detected
if not self._multi_keys:
if event.is_pressed and key == self._esc_key:
if not self._abort_timer.is_alive():
self._abort_timer.start()
- elif not event.is_pressed and \
- InputType.Keyboard in self._event_types:
+ elif not event.is_pressed and InputType.Keyboard in self._event_types:
if not self._return_kb_event:
self.item_selected.emit(key)
else:
@@ -2453,7 +2481,6 @@ def _kb_event_cb(self, event):
if not self._abort_timer.is_alive():
self._abort_timer.start()
else:
-
self._abort_timer.cancel()
if not self._aborting:
self.item_selected.emit(self._multi_key_storage)
@@ -2471,19 +2498,21 @@ def _mouse_event_cb(self, event):
def _abort_request(self):
import time
+
self._aborting = True
if self._abort_timer.is_alive():
self._abort_timer.cancel()
time.sleep(0.1)
-
def closeEvent(self, evt):
"""Closes the overlay window."""
event_listener = gremlin.event_handler.EventListener()
event_listener.keyboard_event.disconnect(self._kb_event_cb)
- if InputType.JoystickAxis in self._event_types or \
- InputType.JoystickButton in self._event_types or \
- InputType.JoystickHat in self._event_types:
+ if (
+ InputType.JoystickAxis in self._event_types
+ or InputType.JoystickButton in self._event_types
+ or InputType.JoystickHat in self._event_types
+ ):
event_listener.joystick_event.disconnect(self._joy_event_cb)
elif InputType.Mouse in self._event_types:
event_listener.mouse_event.disconnect(self._mouse_event_cb)
@@ -2498,8 +2527,6 @@ def closeEvent(self, evt):
# print ("input widget close")
super().closeEvent(evt)
-
-
def _valid_event_types_string(self):
"""Returns a formatted string containing the valid event types.
@@ -2529,14 +2556,15 @@ def clear_layout(layout):
clear_layout(child.layout())
elif child.widget():
widget = child.widget()
- if hasattr(widget,"_cleanup_ui"):
+ if hasattr(widget, "_cleanup_ui"):
widget._cleanup_ui()
widget.hide()
widget.deleteLater()
layout.removeItem(child)
+
def get_layout_widgets(layout) -> list:
- ''' returns a list of layout widgets '''
+ """returns a list of layout widgets"""
widgets = []
while layout.count() > 0:
child = layout.takeAt(0)
@@ -2548,47 +2576,54 @@ def get_layout_widgets(layout) -> list:
return widgets
+class QComboBox(QtWidgets.QComboBox):
+ """a max limited combo box"""
-class QComboBox (QtWidgets.QComboBox):
- ''' a max limited combo box '''
- def __init__(self, parent = None):
+ def __init__(self, parent=None):
super().__init__(parent)
# hack to ensure maximum items property is respected
- #self.setEditable(True) # this is so max items works
+ # self.setEditable(True) # this is so max items works
# self.lineEdit().setFrame(False)
# self.lineEdit().setReadOnly(True)
- self.setStyleSheet('QComboBox {combobox-popup: 0}')
-
+ self.setStyleSheet("QComboBox {combobox-popup: 0}")
self.setMaxVisibleItems(20)
-class NoWheelComboBox (QComboBox):
- ''' implements a combo box with no-wheel scrolling to avoid inadvertent switching of entries while scolling containers '''
- def __init__(self, parent = None):
+class NoWheelComboBox(QComboBox):
+ """implements a combo box with no-wheel scrolling to avoid inadvertent switching of entries while scolling containers"""
+
+ def __init__(self, parent=None):
super().__init__(parent)
self.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
-
def wheelEvent(self, event) -> None:
# blitz wheel events if the box is not in focus
if self.hasFocus():
return super().wheelEvent(event)
+
class ConfirmPushButton(QtWidgets.QPushButton):
- ''' confirmation push button '''
+ """confirmation push button"""
confirmed = QtCore.Signal(object)
- def __init__(self, text = None, title = "Confirmation Required", prompt = "Are you sure?", show_callback = None, parent = None ) -> None:
- ''' shows a confirm dialog box on click
+ def __init__(
+ self,
+ text=None,
+ title="Confirmation Required",
+ prompt="Are you sure?",
+ show_callback=None,
+ parent=None,
+ ) -> None:
+ """shows a confirm dialog box on click
:param text button text
:param title dialog title
:param prompt dialog body (question)
:param show_callback boolean callback that determines if the dialog should show (return true if it should)
- '''
+ """
super().__init__(parent)
if text:
@@ -2600,7 +2635,6 @@ def __init__(self, text = None, title = "Confirmation Required", prompt = "Are y
self.clicked.connect(self._clicked_cb)
-
def _clicked_cb(self):
if self.show_callback is not None:
result = self.show_callback()
@@ -2608,6 +2642,7 @@ def _clicked_cb(self):
return
from gremlin.util import load_pixmap
+
message_box = QtWidgets.QMessageBox()
pixmap = load_pixmap("warning.svg")
pixmap = pixmap.scaled(32, 32, QtCore.Qt.KeepAspectRatio)
@@ -2615,37 +2650,41 @@ def _clicked_cb(self):
message_box.setText(self.title)
message_box.setInformativeText(self.prompt)
message_box.setStandardButtons(
- QtWidgets.QMessageBox.StandardButton.Ok |
- QtWidgets.QMessageBox.StandardButton.Cancel
- )
+ QtWidgets.QMessageBox.StandardButton.Ok
+ | QtWidgets.QMessageBox.StandardButton.Cancel
+ )
message_box.setDefaultButton(QtWidgets.QMessageBox.StandardButton.Ok)
gremlin.util.centerDialog(message_box)
result = message_box.exec()
if result == QtWidgets.QMessageBox.StandardButton.Ok:
self.confirmed.emit(self)
-class ConfirmBox():
- def __init__(self, title = "Confirmation Required", prompt = "Are you sure?", parent = None):
+class ConfirmBox:
+ def __init__(
+ self, title="Confirmation Required", prompt="Are you sure?", parent=None
+ ):
from gremlin.util import load_pixmap
- self._message_box = QtWidgets.QMessageBox(parent = parent)
+
+ self._message_box = QtWidgets.QMessageBox(parent=parent)
pixmap = load_pixmap("warning.svg")
pixmap = pixmap.scaled(32, 32, QtCore.Qt.KeepAspectRatio)
self._message_box.setIconPixmap(pixmap)
self._message_box.setText(title)
self._message_box.setInformativeText(prompt)
self._message_box.setStandardButtons(
- QtWidgets.QMessageBox.StandardButton.Ok |
- QtWidgets.QMessageBox.StandardButton.Cancel
- )
+ QtWidgets.QMessageBox.StandardButton.Ok
+ | QtWidgets.QMessageBox.StandardButton.Cancel
+ )
self._message_box.setDefaultButton(QtWidgets.QMessageBox.StandardButton.Ok)
gremlin.util.centerDialog(self._message_box)
def show(self):
return self._message_box.exec()
+
class QMessageBox(QtWidgets.QMessageBox):
- def __init__(self, width = 400, height = 100, parent = None):
+ def __init__(self, width=400, height=100, parent=None):
super().__init__(parent)
self._width = width
self._height = height
@@ -2655,11 +2694,13 @@ def resizeEvent(self, event):
self.setFixedHeight(self._height)
-class MessageBox():
- def __init__(self, title = "Notice", prompt = "Operation", is_warning = True, parent = None):
-
+class MessageBox:
+ def __init__(
+ self, title="Notice", prompt="Operation", is_warning=True, parent=None
+ ):
from gremlin.util import load_pixmap
- self._message_box = QMessageBox(parent = parent)
+
+ self._message_box = QMessageBox(parent=parent)
if is_warning:
pixmap = load_pixmap("warning.svg")
@@ -2667,42 +2708,51 @@ def __init__(self, title = "Notice", prompt = "Operation", is_warning = True, pa
self._message_box.setIconPixmap(pixmap)
self._message_box.setText(title)
self._message_box.setInformativeText(prompt)
- self._message_box.setStandardButtons(
- QtWidgets.QMessageBox.StandardButton.Ok
- )
+ self._message_box.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok)
self._message_box.setDefaultButton(QtWidgets.QMessageBox.StandardButton.Ok)
gremlin.util.centerDialog(self._message_box)
self._message_box.exec()
-
-
class QHLine(QtWidgets.QFrame):
- ''' horizontal line '''
- def __init__(self, parent = None):
+ """horizontal line"""
+
+ def __init__(self, parent=None):
super().__init__(parent)
- self.setContentsMargins(0,1,0,1)
+ self.setContentsMargins(0, 1, 0, 1)
self.setFrameShape(QtWidgets.QFrame.Shape.HLine)
self.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
+
class QWrapableLabel(QtWidgets.QLabel):
- ''' wrappable label '''
+ """wrappable label"""
+
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def setWordWrapAt(self, char):
- ''' sets the word wrap on a given character '''
+ """sets the word wrap on a given character"""
newtext = self.text().replace(char, f"{char}\u200b")
self.setText(newtext)
self.setWordWrap(True)
class QIconLabel(QtWidgets.QWidget):
- ''' label with an icon using the QAWESEOME lib '''
+ """label with an icon using the QAWESEOME lib"""
HorizontalSpacing = 2
- def __init__(self, icon_path = None, text = None, stretch=True, use_qta = False, icon_color = None, use_wrap = True, icon_size = 16, parent = None):
+ def __init__(
+ self,
+ icon_path=None,
+ text=None,
+ stretch=True,
+ use_qta=False,
+ icon_color=None,
+ use_wrap=True,
+ icon_size=16,
+ parent=None,
+ ):
super().__init__(parent)
if text is None:
@@ -2713,13 +2763,13 @@ def __init__(self, icon_path = None, text = None, stretch=True, use_qta = False,
container_layout = QtWidgets.QHBoxLayout(container_widget)
container_layout.setContentsMargins(0, 0, 0, 0)
- w = get_text_width("M")*80
+ w = get_text_width("M") * 80
container_widget.setMaximumWidth(w)
self._icon_size = QtCore.QSize(icon_size, icon_size)
self._icon_widget = QtWidgets.QLabel()
if icon_path:
- self.setIcon(icon_path, use_qta, color = icon_color)
+ self.setIcon(icon_path, use_qta, color=icon_color)
if use_wrap:
self._label_widget = QWrapableLabel(text)
@@ -2731,18 +2781,18 @@ def __init__(self, icon_path = None, text = None, stretch=True, use_qta = False,
container_layout.addStretch()
layout = QtWidgets.QGridLayout(self)
- layout.setContentsMargins(0,0,0,0)
- layout.addWidget(self._icon_widget,0,0,alignment= QtCore.Qt.AlignmentFlag.AlignTop)
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.addWidget(
+ self._icon_widget, 0, 0, alignment=QtCore.Qt.AlignmentFlag.AlignTop
+ )
layout.addWidget(container_widget, 0, 1)
- layout.addWidget(QtWidgets.QWidget(),0,2)
- layout.setColumnStretch(2,2)
-
- self.setContentsMargins(0,0,0,0)
+ layout.addWidget(QtWidgets.QWidget(), 0, 2)
+ layout.setColumnStretch(2, 2)
+ self.setContentsMargins(0, 0, 0, 0)
-
- def setIcon(self, icon_or_path = None, use_qta = True, color = None):
- ''' sets the icon of the label, pass a blank or None path to clear the icon'''
+ def setIcon(self, icon_or_path=None, use_qta=True, color=None):
+ """sets the icon of the label, pass a blank or None path to clear the icon"""
if isinstance(icon_or_path, QtGui.QIcon):
pixmap = icon_or_path.pixmap(self._icon_size)
@@ -2757,14 +2807,16 @@ def setIcon(self, icon_or_path = None, use_qta = True, color = None):
else:
pixmap = None
if pixmap:
- pixmap = pixmap.scaled(self._icon_size, QtCore.Qt.AspectRatioMode.KeepAspectRatio)
+ pixmap = pixmap.scaled(
+ self._icon_size, QtCore.Qt.AspectRatioMode.KeepAspectRatio
+ )
self._icon_widget.setPixmap(pixmap)
else:
# clear the pixmap
self._icon_widget.setPixmap(QtGui.QPixmap())
- def setText(self, text = None):
- ''' sets the text of the label '''
+ def setText(self, text=None):
+ """sets the text of the label"""
if text:
self._label_widget.setText(text)
else:
@@ -2774,20 +2826,22 @@ def setTextMinWidth(self, value):
self._label_widget.setMinimumWidth(value)
def showIcon(self):
- ''' hides the icon '''
+ """hides the icon"""
self._icon_widget.setVisible(True)
def hideIcon(self):
- ''' shows the icon '''
+ """shows the icon"""
self._icon_widget.setVisible(False)
def text(self):
- ''' gets the text of the widget '''
+ """gets the text of the widget"""
return self._icon_widget.text()
+
class QDataWidget(QtWidgets.QWidget):
- ''' data widgets '''
- def __init__(self, data = None, parent = None):
+ """data widgets"""
+
+ def __init__(self, data=None, parent=None):
super().__init__(parent)
self._data = data
@@ -2801,8 +2855,9 @@ def data(self, value):
class QDataLabel(QtWidgets.QLabel):
- ''' data enabled label widget '''
- def __init__(self, data = None, parent = None):
+ """data enabled label widget"""
+
+ def __init__(self, data=None, parent=None):
super().__init__(parent)
self._data = data
@@ -2814,9 +2869,11 @@ def data(self):
def data(self, value):
self._data = value
+
class QDataCheckbox(QtWidgets.QCheckBox):
- ''' a checkbox that has a data property to track an object associated with the checkbox '''
- def __init__(self, text = None, data = None, parent = None):
+ """a checkbox that has a data property to track an object associated with the checkbox"""
+
+ def __init__(self, text=None, data=None, parent=None):
super().__init__(text, parent)
self._data = data
self._ignore_keyboard = False
@@ -2834,7 +2891,6 @@ def __init__(self, text = None, data = None, parent = None):
# def _update_state(self):
# icon = self._icon_checked if self.isChecked() else self._icon_unchecked
# self.setIcon(icon)
-
@property
def data(self):
@@ -2844,19 +2900,20 @@ def data(self):
def data(self, value):
self._data = value
-
def eventFilter(self, widget, event):
t = event.type()
if t == QtCore.QEvent.Type.KeyPress and self._ignore_keyboard:
return True
return super().eventFilter(widget, event)
- def setIgnoreKeyboard(self, value : bool):
+ def setIgnoreKeyboard(self, value: bool):
self._ignore_keyboard = value
+
class QDataRadioButton(QtWidgets.QRadioButton):
- ''' a radio button that has a data property to track an object associated with the checkbox '''
- def __init__(self, text = None, data = None, parent = None):
+ """a radio button that has a data property to track an object associated with the checkbox"""
+
+ def __init__(self, text=None, data=None, parent=None):
super().__init__(text, parent)
self._data = data
@@ -2868,9 +2925,11 @@ def data(self):
def data(self, value):
self._data = value
+
class QDataPushButton(QtWidgets.QPushButton):
- ''' a checkbox that has a data property to track an object associated with the checkbox '''
- def __init__(self, text = None, data = None, parent = None):
+ """a checkbox that has a data property to track an object associated with the checkbox"""
+
+ def __init__(self, text=None, data=None, parent=None):
super().__init__(text, parent)
self._data = data
@@ -2884,36 +2943,38 @@ def data(self, value):
class QDataLineEdit(QtWidgets.QLineEdit):
- ''' a checkbox that has a data property to track an object associated with the checkbox '''
- valueChanged = QtCore.Signal() # fires when the text has changed AND we lost the focus
- lostFocus = QtCore.Signal() # fires when the input looses focus
+ """a checkbox that has a data property to track an object associated with the checkbox"""
+
+ valueChanged = (
+ QtCore.Signal()
+ ) # fires when the text has changed AND we lost the focus
+ lostFocus = QtCore.Signal() # fires when the input looses focus
- def __init__(self, text = None, data = None, parent = None):
+ def __init__(self, text=None, data=None, parent=None):
super().__init__(text, parent)
self._data = data
self._text_changed = True
self.setAlignment(Qt.AlignLeft)
- #self.setStyleSheet("QLineEdit{border: #8FBC8F;}")
+ # self.setStyleSheet("QLineEdit{border: #8FBC8F;}")
super().textChanged.connect(self._text_changed_cb)
-
def _text_changed_cb(self):
self._text_changed = True
-
def focusOutEvent(self, event):
if self._text_changed:
self.lostFocus.emit()
self.valueChanged.emit()
return super().focusOutEvent(event)
-
def setText(self, value):
- ''' sets the text '''
+ """sets the text"""
super().setText(value)
if self.isReadOnly():
- self.home(True) # move the cursor left to left align the box in readonly mode
- self.deselect() # deselect the text selected by the home command
+ self.home(
+ True
+ ) # move the cursor left to left align the box in readonly mode
+ self.deselect() # deselect the text selected by the home command
@property
def data(self):
@@ -2924,39 +2985,35 @@ def data(self, value):
self._data = value
-
-
-
class QDataIPLineEdit(QDataLineEdit):
- ''' IP input text box '''
- def __init__(self, text = None, data = None, parent = None):
+ """IP input text box"""
+
+ def __init__(self, text=None, data=None, parent=None):
super().__init__(text, data, parent)
- regex = r'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'
+ regex = r"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
validator = QtGui.QRegularExpressionValidator(regex)
self.setValidator(validator)
-
class QDataComboBox(QComboBox):
- ''' a combo box that has a data property to track an object associated with the checkbox '''
- def __init__(self, data = None, parent = None, wheel_enabled = True):
+ """a combo box that has a data property to track an object associated with the checkbox"""
+
+ def __init__(self, data=None, parent=None, wheel_enabled=True):
super().__init__(parent)
self._data = data
self._wheel_enabled = wheel_enabled
self.installEventFilter(self)
-
def eventFilter(self, widget, event):
if not self._wheel_enabled:
t = event.type()
if t == QtCore.QEvent.Type.Wheel:
- return True # skip the event
+ return True # skip the event
return super().eventFilter(widget, event)
-
- def setWheelEnabled(self, value : bool):
- ''' enables/disables the wheel function to change'''
+
+ def setWheelEnabled(self, value: bool):
+ """enables/disables the wheel function to change"""
self._wheel_enabled = value
-
@property
def data(self):
@@ -2966,20 +3023,22 @@ def data(self):
def data(self, value):
self._data = value
+
class QLimitedComboBox(QDataComboBox):
- ''' a row limited combo box '''
- def __init__(self, data = None, parent = None):
+ """a row limited combo box"""
+
+ def __init__(self, data=None, parent=None):
super().__init__(data, parent)
self.setMaxVisibleItems(20)
self.setStyleSheet("QComboBox { combobox-popup: 0; }")
-class QHatSelectorComboBox(QDataComboBox):
- ''' a combo box for hat directions '''
- valueChanged = QtCore.Signal(HatDirection) # fires when a value is selected
+class QHatSelectorComboBox(QDataComboBox):
+ """a combo box for hat directions"""
- def __init__(self, data = None, parent = None):
+ valueChanged = QtCore.Signal(HatDirection) # fires when a value is selected
+ def __init__(self, data=None, parent=None):
super().__init__(data, parent)
self._direction = HatDirection.Center
@@ -3002,28 +3061,32 @@ def __init__(self, data = None, parent = None):
case HatDirection.SouthEast:
png = f"{prefix}hat_se.png"
case HatDirection.SouthWest:
- png = f"{prefix}hat_sw.png"
+ png = f"{prefix}hat_sw.png"
case HatDirection.West:
- png = f"{prefix}hat_w.png"
- icon = load_icon(png)
- #icon_active = load_icon(png_active)
- self.addItem(icon, HatDirection.to_display_name(position), HatDirection.to_enum(position))
-
+ png = f"{prefix}hat_w.png"
+ icon = load_icon(png)
+ # icon_active = load_icon(png_active)
+ self.addItem(
+ icon,
+ HatDirection.to_display_name(position),
+ HatDirection.to_enum(position),
+ )
+
self.currentIndexChanged.connect(self._update_value)
@property
def direction(self) -> str:
- ''' direction selected '''
+ """direction selected"""
return self.currentData(self.currentIndex())
-
+
@property
def value(self):
- ''' direction as a tuple '''
+ """direction as a tuple"""
direction = HatDirection.to_enum(self.currentData(self.currentIndex()))
return direction.value
-
- def setValue(self, value, emit = False):
- ''' sets the value as a tuple '''
+
+ def setValue(self, value, emit=False):
+ """sets the value as a tuple"""
with QtCore.QSignalBlocker(self):
if isinstance(value, tuple):
value = HatDirection(value)
@@ -3037,38 +3100,48 @@ def setValue(self, value, emit = False):
@QtCore.Slot()
def _update_value(self):
- ''' index changed '''
+ """index changed"""
self.valueChanged.emit(self.currentData())
-
class QPathLineItem(QtWidgets.QWidget):
- ''' An editable text input line with a file selector button '''
+ """An editable text input line with a file selector button"""
- open = QtCore.Signal(object) # event that fires when the open button is clicked, and passes the control
- pathChanged = QtCore.Signal(object, str) # fires when the line item changes
+ open = QtCore.Signal(
+ object
+ ) # event that fires when the open button is clicked, and passes the control
+ pathChanged = QtCore.Signal(object, str) # fires when the line item changes
IconSize = QtCore.QSize(16, 16)
- def __init__(self, header = None, text = None, data = None, dir_mode = False, parent = None, open_tooltip_text = "Browse"):
- '''
+ def __init__(
+ self,
+ header=None,
+ text=None,
+ data=None,
+ dir_mode=False,
+ parent=None,
+ open_tooltip_text="Browse",
+ ):
+ """
displays the path to a file or a folder
:param: header - the header text
:param: text - the default content
:data: optional data parameters
:dir_mode: true if the entry is a folder, false if it's a file
- '''
+ """
super().__init__(parent)
self._text = text
self._header = header
self._dir_mode = dir_mode
-
self._file_widget = QtWidgets.QLineEdit()
self._file_widget.installEventFilter(self)
- self._file_widget.returnPressed.connect(self._open_button_cb) # open the dialog on enter
+ self._file_widget.returnPressed.connect(
+ self._open_button_cb
+ ) # open the dialog on enter
self._file_widget.setText(text)
self._file_widget.textChanged.connect(self._file_changed)
self._open_button = QtWidgets.QPushButton("...")
@@ -3087,12 +3160,11 @@ def __init__(self, header = None, text = None, data = None, dir_mode = False, pa
self._layout.addWidget(self._icon_widget)
self._layout.addWidget(self._file_widget)
self._layout.addWidget(self._open_button)
- self._layout.setContentsMargins(0,0,0,0)
+ self._layout.setContentsMargins(0, 0, 0, 0)
self._data = data
self._file_changed()
-
self.setLayout(self._layout)
@@ -3117,8 +3189,8 @@ def eventFilter(self, widget, event):
self.pathChanged.emit(self, self._text)
return super().eventFilter(widget, event)
- def _setIcon(self, icon_path = None, use_qta = True, color = None):
- ''' sets the icon of the label, pass a blank or None path to clear the icon'''
+ def _setIcon(self, icon_path=None, use_qta=True, color=None):
+ """sets the icon of the label, pass a blank or None path to clear the icon"""
if icon_path:
if use_qta:
if color:
@@ -3130,14 +3202,16 @@ def _setIcon(self, icon_path = None, use_qta = True, color = None):
else:
pixmap = None
if pixmap:
- pixmap = pixmap.scaled(QPathLineItem.IconSize, QtCore.Qt.AspectRatioMode.KeepAspectRatio)
+ pixmap = pixmap.scaled(
+ QPathLineItem.IconSize, QtCore.Qt.AspectRatioMode.KeepAspectRatio
+ )
self._icon_widget.setPixmap(pixmap)
else:
# clear the pixmap
self._icon_widget.setPixmap(QtGui.QPixmap())
- def setText(self, text = None):
- ''' sets the text of the label '''
+ def setText(self, text=None):
+ """sets the text of the label"""
with QtCore.QSignalBlocker(self._file_widget):
if text:
self._text = text
@@ -3147,38 +3221,36 @@ def setText(self, text = None):
self._file_widget.setText("")
self._file_changed()
-
def text(self):
return self._text
def showIcon(self):
- ''' hides the icon '''
+ """hides the icon"""
self._icon_widget.setVisible(True)
def hideIcon(self):
- ''' shows the icon '''
+ """shows the icon"""
self._icon_widget.setVisible(False)
-
def _file_changed(self):
fname = self._file_widget.text()
valid = os.path.isdir(fname) if self._dir_mode else os.path.isfile(fname)
if valid:
- self._setIcon("mdi.checkbox-marked-outline", color= Color.activeColor())
+ self._setIcon("mdi.checkbox-marked-outline", color=Color.activeColor())
else:
- self._setIcon("fa6s.circle-exclamation", color = Color.warningColor())
+ self._setIcon("fa6s.circle-exclamation", color=Color.warningColor())
self._text = fname
self.pathChanged.emit(self, self._text)
@property
def valid(self):
- ''' true if the file exists '''
+ """true if the file exists"""
return os.path.isfile(self._text)
@property
def data(self):
- ''' object reference for this widget '''
+ """object reference for this widget"""
return self._data
@data.setter
@@ -3187,61 +3259,60 @@ def data(self, value):
class ButtonStateWidget(QtWidgets.QWidget):
- ''' visualizes the state of a button '''
+ """visualizes the state of a button"""
- deleted = QtCore.Signal() # triggers on delete
+ deleted = QtCore.Signal() # triggers on delete
- def __init__(self, parent = None):
+ def __init__(self, parent=None):
super().__init__(parent)
-
- self.setContentsMargins(0,0,0,0)
+ self.setContentsMargins(0, 0, 0, 0)
self.main_layout = QtWidgets.QHBoxLayout(self)
self.main_layout.setSpacing(0)
- self.main_layout.setContentsMargins(0,0,0,0)
+ self.main_layout.setContentsMargins(0, 0, 0, 0)
- self._icon_size = QtCore.QSize(16,16)
+ self._icon_size = QtCore.QSize(16, 16)
self._device_guid = None
self._input_id = None
self._input_type = None
self._button_widget = QtWidgets.QLabel()
- self._button_widget.setContentsMargins(0,0,0,0)
- on_icon = load_icon("mdi.checkbox-blank-circle",use_qta=True,qta_color=Color.activeColor())
+ self._button_widget.setContentsMargins(0, 0, 0, 0)
+ on_icon = load_icon(
+ "mdi.checkbox-blank-circle", use_qta=True, qta_color=Color.activeColor()
+ )
self._on_pixmap = on_icon.pixmap(self._icon_size)
- off_icon = load_icon("mdi.checkbox-blank-circle",use_qta=True,qta_color=Color.inactiveColor())
+ off_icon = load_icon(
+ "mdi.checkbox-blank-circle", use_qta=True, qta_color=Color.inactiveColor()
+ )
self._off_pixmap = off_icon.pixmap(self._icon_size)
- height = self._icon_size.height()+2
+ height = self._icon_size.height() + 2
self._button_widget.setMinimumHeight(height)
self._button_widget.setMaximumHeight(height)
self._button_widget.setStyleSheet("")
- self._hat_icons = {} # icon hats, keyed by position
-
+ self._hat_icons = {} # icon hats, keyed by position
+
self.main_layout.addWidget(self._button_widget)
self._handler_connected = False
el = gremlin.event_handler.EventListener()
el.tab_selected.connect(self._tab_selected)
el.tab_unselected.connect(self._tab_unselected)
-
-
def _cleanup_ui(self):
self.unhookDevice()
self.deleted.emit()
-
def hookDevice(self, device_guid, input_type, input_id):
- ''' hooks the input '''
- import gremlin.joystick_handling
+ """hooks the input"""
self._device_guid = device_guid
self._input_id = input_id
self._input_type = input_type
self.updateState()
self._tab_selected(device_guid)
-
+
def updateState(self):
- ''' updates the widget state with the cached state '''
+ """updates the widget state with the cached state"""
tracker = StateTracker()
state = tracker._get_state(self._device_guid, self._input_type, self._input_id)
if state:
@@ -3249,128 +3320,141 @@ def updateState(self):
def unhookDevice(self):
self._tab_unselected(self._device_guid)
-
-
@QtCore.Slot(str)
def _tab_selected(self, device_guid):
- ''' triggered when a tab is selected
-
+ """triggered when a tab is selected
+
:param device_guid: the device selected
-
- '''
+
+ """
if self._handler_connected:
# already connected
return
if self._device_guid:
# syslog = logging.getLogger("system")
- device_name = gremlin.shared_state.get_device_name(device_guid)
+ # device_name = gremlin.shared_state.get_device_name(device_guid)
if isinstance(device_guid, str):
device_guid = gremlin.util.parse_guid(device_guid)
- #el = gremlin.event_handler.EventListener()
+ # el = gremlin.event_handler.EventListener()
if self._device_guid == device_guid:
# connect the handler
- #input_id = self._input_id
- #syslog.info(f"ButtonState: {device_name} type {InputType.to_display_name(self._input_type)} input {self._input_id} connect")
- _state_tracker.registerButtonState(self, self._device_guid, self._input_type, self._input_id)
+ # input_id = self._input_id
+ # syslog.info(f"ButtonState: {device_name} type {InputType.to_display_name(self._input_type)} input {self._input_id} connect")
+ _state_tracker.registerButtonState(
+ self, self._device_guid, self._input_type, self._input_id
+ )
self._handler_connected = True
-
@property
def enabled(self) -> bool:
return self._handler_connected
-
+
@property
def input_id(self) -> object:
return self._input_id
+
@property
def device_guid(self) -> str:
return self._device_guid
+
@property
def input_type(self) -> InputType:
return self._input_type
-
-
@QtCore.Slot(str)
def _tab_unselected(self, device_guid):
- ''' triggered when a device tab is deselected, also used to force a disconnect
-
+ """triggered when a device tab is deselected, also used to force a disconnect
+
:param device_guid: the device to deselect - if None - deselect all
-
- '''
+
+ """
if not self._handler_connected:
# not connected
- return
+ return
# # syslog = logging.getLogger("system")
# el = gremlin.event_handler.EventListener()
if device_guid:
if isinstance(device_guid, str):
device_guid = gremlin.util.parse_guid(device_guid)
disconnect = self._device_guid == device_guid
- #device_name = gremlin.shared_state.get_device_name(device_guid)
+ # device_name = gremlin.shared_state.get_device_name(device_guid)
else:
disconnect = True
- #device_name = 'reset'
-
+ # device_name = 'reset'
+
if disconnect:
- #input_id = self._input_id
+ # input_id = self._input_id
# syslog.info(f"ButtonState: (unselect) {device_name} button {input_id} disconnect")
- _state_tracker.unregisterButtonState(self._device_guid, self._input_type, self._input_id)
+ _state_tracker.unregisterButtonState(
+ self._device_guid, self._input_type, self._input_id
+ )
self._handler_connected = False
-
def _update_value(self, is_pressed):
- ''' updates a button position '''
+ """updates a button position"""
if is_pressed:
self._button_widget.setPixmap(self._on_pixmap)
# syslog.info(f"button {self.input_id} pressed")
# self._button_widget.update()
- #self._button_widget.setText("pressed")
+ # self._button_widget.setText("pressed")
else:
self._button_widget.setPixmap(self._off_pixmap)
# syslog.info(f"button {self.input_id} released")
# self._button_widget.update()
- #self._button_widget.setText(" ")
+ # self._button_widget.setText(" ")
def _update_hat(self, position):
- ''' updates a hat position '''
+ """updates a hat position"""
prefix = "dark_" if gremlin.shared_state.is_dark_theme else ""
- if not isinstance(position,tuple):
+ if not isinstance(position, tuple):
# convert from value to position tuple
import vjoy.vjoy
- position = vjoy.vjoy.Hat.to_continuous_position[position]
- position = HatDirection.to_enum(position)
-
- if not position in self._hat_icons:
+
+ position = vjoy.vjoy.Hat.to_continuous_position[position]
+ position = HatDirection.to_enum(position)
+
+ if position not in self._hat_icons:
match position:
+ # fixme Icons are not correct
case HatDirection.Center:
- png = "hat_ctr_inactive.png"
- png_active = "hat_ctr_active.png"
+ # png = "hat_ctr_inactive.png"
+ # png_active = "hat_ctr_active.png"
+ png = png_active = "mdi.arrow-all"
case HatDirection.North:
png = f"{prefix}hat_n.png"
png_active = "hat_n_active.png"
case HatDirection.NorthEast:
- png = f"{prefix}hat_ne.png"
- png_active = "hat_ne_active.png"
+ # png = f"{prefix}hat_ne.png"
+ # png_active = "hat_ne_active.png"
+ png = png_active = "mdi.arrow-up"
case HatDirection.NorthWest:
- png = f"{prefix}hat_nw.png"
- png_active = "hat_nw_active.png"
+ # png = f"{prefix}hat_nw.png"
+ # png_active = "hat_nw_active.png"
+ png, png_active = "mdi.arrow-top-right"
case HatDirection.East:
- png = f"{prefix}hat_e.png"
- png_active = "hat_e_active.png"
+ # png = f"{prefix}hat_e.png"
+ # png_active = "hat_e_active.png"
+ png = png_active = "mdi.arrow-right"
case HatDirection.South:
- png = f"{prefix}hat_s.png"
- png_active = "hat_s_active.png"
+ # png = f"{prefix}hat_s.png"
+ # png_active = "hat_s_active.png"
+ png = png_active = "mdi.arrow-down"
case HatDirection.SouthEast:
- png = f"{prefix}hat_se.png"
- png_active = "hat_se_active.png"
+ # png = f"{prefix}hat_se.png"
+ # png_active = "hat_se_active.png"
+ png = png_active = "mdi.arrow-bottom-right"
case HatDirection.SouthWest:
- png = f"{prefix}hat_sw.png"
- png_active = "hat_sw_active.png"
+ # png = f"{prefix}hat_sw.png"
+ # png_active = "hat_sw_active.png"
+ png = png_active = "mdi.arrow-bottom-left"
case HatDirection.West:
- png = f"{prefix}hat_w.png"
- png_active = "hat_w_active.png"
+ # png = f"{prefix}hat_w.png"
+ # png_active = "hat_w_active.png"
+ png = png_active = "mdi.arrow-left"
+ case _:
+ png = "mdi.arrow-all"
+ png_active = "mdi.arrow-all"
on_pixmap = load_icon(png_active).pixmap(self._icon_size)
off_pixmap = load_icon(png).pixmap(self._icon_size)
self._hat_icons[position] = (off_pixmap, on_pixmap)
@@ -3381,32 +3465,36 @@ def _update_hat(self, position):
else:
self._button_widget.setPixmap(off_pixmap)
-
def setValue(self, is_pressed):
- ''' value '''
+ """value"""
self._update_value(is_pressed)
-
-
_widget_cache = []
-class AxisStateWidget(QtWidgets.QWidget):
+class AxisStateWidget(QtWidgets.QWidget):
"""Visualizes the current state of an axis."""
-
-
- #css_vertical = r"QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,stop: 0 #78d,stop: 0.4999 #46a,stop: 0.5 #45a,stop: 1 #238 ); border-radius: 7px; border: 1px solid black;}"
+ # css_vertical = r"QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,stop: 0 #78d,stop: 0.4999 #46a,stop: 0.5 #45a,stop: 1 #238 ); border-radius: 7px; border: 1px solid black;}"
css_vertical = r"QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #69e060, stop: 1 #1f8c33 ); border-radius: 7px; border: 1px solid black;}"
- #css_horizontal = r"QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 #78d,stop: 0.4999 #46a,stop: 0.5 #45a,stop: 1 #238 ); border-radius: 7px; border: 1px solid black;}"
- #css_horizontal = r"QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 #77a ,stop: 0.4999 #477,stop: 0.5 #45a,stop: 1 #238 ); border-radius: 7px; border: 1px solid black;}"
+ # css_horizontal = r"QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 #78d,stop: 0.4999 #46a,stop: 0.5 #45a,stop: 1 #238 ); border-radius: 7px; border: 1px solid black;}"
+ # css_horizontal = r"QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 #77a ,stop: 0.4999 #477,stop: 0.5 #45a,stop: 1 #238 ); border-radius: 7px; border: 1px solid black;}"
css_horizontal = r"QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,stop: 0 #69e060 stop: 1 #1f8c33 ); border-radius: 7px; border: 1px solid black;}"
- valueChanged = QtCore.Signal(float, float) # (input_value, curved_value)
- deleted = QtCore.Signal(object) # indicates the item is being deleted
+ valueChanged = QtCore.Signal(float, float) # (input_value, curved_value)
+ deleted = QtCore.Signal(object) # indicates the item is being deleted
- def __init__(self, axis_id = None, show_percentage = True, show_value = True, show_label = True, show_curve = True, orientation = QtCore.Qt.Orientation.Vertical, parent=None):
+ def __init__(
+ self,
+ axis_id=None,
+ show_percentage=True,
+ show_value=True,
+ show_label=True,
+ show_curve=True,
+ orientation=QtCore.Qt.Orientation.Vertical,
+ parent=None,
+ ):
"""Creates a new instance.
:param axis_id id of the axis, used in the label
@@ -3414,7 +3502,9 @@ def __init__(self, axis_id = None, show_percentage = True, show_value = True, sh
"""
super().__init__(parent)
- self._joystick_hooked = False # true if joystick input is directly hooked to this widget
+ self._joystick_hooked = (
+ False # true if joystick input is directly hooked to this widget
+ )
self._scale_factor = 1000
self.main_layout = QtWidgets.QVBoxLayout(self)
@@ -3422,58 +3512,56 @@ def __init__(self, axis_id = None, show_percentage = True, show_value = True, sh
self.container_layout = QtWidgets.QGridLayout()
else:
self.container_layout = QtWidgets.QHBoxLayout()
-
- self.setContentsMargins(0,0,0,0)
- self.main_layout.setContentsMargins(0,0,0,0)
+
+ self.setContentsMargins(0, 0, 0, 0)
+ self.main_layout.setContentsMargins(0, 0, 0, 0)
self.container_layout.setSpacing(0)
- self.container_layout.setContentsMargins(0,0,0,0)
-
-
+ self.container_layout.setContentsMargins(0, 0, 0, 0)
+
self._data = None
self._progress_widget = QtWidgets.QProgressBar()
-
-
self._orientation = orientation
self._show_percentage = show_percentage
self._show_value = show_value
self._show_label = show_label
self._show_curved = show_curve
-
self._display_value_widget = QtWidgets.QLabel()
self._display_percent_widget = QtWidgets.QLabel()
self._display_curve_widget = QtWidgets.QLabel()
self._display_label_widget = QtWidgets.QLabel()
- widget_list = [self._display_label_widget,
- self._display_value_widget,
- self._display_percent_widget,
- self._display_curve_widget,
- ]
+ widget_list = [
+ self._display_label_widget,
+ self._display_value_widget,
+ self._display_percent_widget,
+ self._display_curve_widget,
+ ]
if orientation == QtCore.Qt.Orientation.Vertical:
widget, layout = getVContainer(widget_list)
else:
widget, layout = getHContainer(widget_list)
-
+
self._readout_widget = widget
self._readout_layout = layout
-
-
-
-
if axis_id:
self.setLabel(f"Axis {axis_id}")
-
if orientation == QtCore.Qt.Orientation.Vertical:
- self.container_layout.addWidget(self._display_label_widget,0,0, alignment=QtCore.Qt.AlignCenter)
- self.container_layout.addWidget(self._progress_widget,1,0, alignment=QtCore.Qt.AlignCenter)
- self.container_layout.addWidget(self._readout_widget,2,0, alignment=QtCore.Qt.AlignCenter)
-
+ self.container_layout.addWidget(
+ self._display_label_widget, 0, 0, alignment=QtCore.Qt.AlignCenter
+ )
+ self.container_layout.addWidget(
+ self._progress_widget, 1, 0, alignment=QtCore.Qt.AlignCenter
+ )
+ self.container_layout.addWidget(
+ self._readout_widget, 2, 0, alignment=QtCore.Qt.AlignCenter
+ )
+
else:
self.container_layout.addWidget(self._display_label_widget)
self.container_layout.addWidget(self._progress_widget)
@@ -3487,8 +3575,10 @@ def __init__(self, axis_id = None, show_percentage = True, show_value = True, sh
self._raw_value = 0
self._reverse = False
self._decimals = 3
- self._is_hardware_input = False # true if the device is a hardware device, set in HookDevice()
- self._handler_connected = False # not connected
+ self._is_hardware_input = (
+ False # true if the device is a hardware device, set in HookDevice()
+ )
+ self._handler_connected = False # not connected
self._width = 10
self._update_css()
@@ -3501,8 +3591,8 @@ def __init__(self, axis_id = None, show_percentage = True, show_value = True, sh
if orientation == QtCore.Qt.Orientation.Horizontal:
self.container_layout.addStretch()
-
- self._progress_widget.setContentsMargins(0,0,0,0)
+
+ self._progress_widget.setContentsMargins(0, 0, 0, 0)
self._progress_widget.setOrientation(orientation)
self._progress_widget.setTextVisible(False)
@@ -3510,28 +3600,30 @@ def __init__(self, axis_id = None, show_percentage = True, show_value = True, sh
el = gremlin.event_handler.EventListener()
el.ui_ready.connect(self._ui_ready)
-
+
@QtCore.Slot()
def _ui_ready(self):
- ''' fires when the UI is ready '''
+ """fires when the UI is ready"""
self._setValue(self._value, self._curve_value)
def _cleanup_ui(self):
- ''' item is being deleted '''
+ """item is being deleted"""
self.unhookDevice()
self.deleted.emit(self)
@property
def data(self):
return self._data
+
@data.setter
def data(self, value):
- self._data = value
+ self._data = value
@property
def show_curved(self) -> bool:
- ''' true if repeater shows curved data '''
+ """true if repeater shows curved data"""
return self._show_curved
+
@show_curved.setter
def show_curved(self, value: bool):
if value != self._show_curved:
@@ -3541,34 +3633,39 @@ def show_curved(self, value: bool):
@property
def input_id(self) -> object:
return self._input_id
+
@property
def device_guid(self) -> str:
return self._device_guid
+
@property
def input_type(self) -> InputType:
return self._input_type
-
-
def _create_primitives(self):
self._marker = [
- QtCore.QPoint(0,0),
- QtCore.QPoint(-10,-5),
- QtCore.QPoint(-5,10)
+ QtCore.QPoint(0, 0),
+ QtCore.QPoint(-10, -5),
+ QtCore.QPoint(-5, 10),
]
def _update_visible(self):
- ''' updates visible state for data label'''
+ """updates visible state for data label"""
if gremlin.shared_state.ui_ready and self.parent() is not None:
self._display_value_widget.setVisible(self._show_value)
self._display_percent_widget.setVisible(self._show_percentage)
self._display_curve_widget.setVisible(self._show_curved)
self._display_label_widget.setVisible(self._show_label)
- visible = self._show_label or self._show_percentage or self._show_value or self._show_curved
+ visible = (
+ self._show_label
+ or self._show_percentage
+ or self._show_value
+ or self._show_curved
+ )
self._readout_widget.setVisible(visible)
def setPercentageVisible(self, value: bool):
- ''' shows or hides the percentage value on the axis '''
+ """shows or hides the percentage value on the axis"""
self._show_percentage = value
self._update_visible()
@@ -3582,13 +3679,13 @@ def _update_css(self):
self._progress_widget.setMaximumWidth(self._width)
elif self._orientation == QtCore.Qt.Orientation.Horizontal:
- css = AxisStateWidget.css_horizontal+ f";height {self._width}px"
+ css = AxisStateWidget.css_horizontal + f";height {self._width}px"
self._progress_widget.setMaximumHeight(self._width)
self._progress_widget.setStyleSheet(css)
- def setLabel(self, value : str):
- ''' sets the label for the axis '''
+ def setLabel(self, value: str):
+ """sets the label for the axis"""
self._display_label_widget.setText(value)
def setLabelVisible(self, value: bool):
@@ -3604,21 +3701,21 @@ def setWidth(self, value):
def value(self):
return self._value
- def setValue(self, value, curve_value = None, percent_value = None, other_value = None):
+ def setValue(self, value, curve_value=None, percent_value=None, other_value=None):
"""Sets the value shown by the widget.
:param value new value to show
"""
- assert not self._joystick_hooked,"Cannot set value on a hooked input"
+ assert not self._joystick_hooked, "Cannot set value on a hooked input"
self._setValue(value, curve_value, percent_value, other_value)
- def _setValue(self, value, curve_value = None, percent_value = None, other_value = None):
- ''' internal set value '''
+ def _setValue(self, value, curve_value=None, percent_value=None, other_value=None):
+ """internal set value"""
try:
if value < self._min_range:
value = self._min_range
if value > self._max_range:
value = self._max_range
- value += 0 # avoid negative 0 (WHY?)
+ value += 0 # avoid negative 0 (WHY?)
self._value = value
if curve_value is not None:
@@ -3630,7 +3727,6 @@ def _setValue(self, value, curve_value = None, percent_value = None, other_value
display_value = value
self._curve_value = value
-
if self._reverse:
display_value = gremlin.util.scale_to_range(display_value, invert=True)
@@ -3638,7 +3734,6 @@ def _setValue(self, value, curve_value = None, percent_value = None, other_value
display_value = None
else:
scaled_value = self._scale_factor * display_value
-
self._progress_widget.setValue(scaled_value)
self._progress_widget.update()
@@ -3652,23 +3747,23 @@ def _setValue(self, value, curve_value = None, percent_value = None, other_value
if self._show_percentage:
if percent_value is None:
if curve_value is None:
- percent = gremlin.util.scale_to_range(display_value, target_min=0, target_max = 100)
+ percent = gremlin.util.scale_to_range(
+ display_value, target_min=0, target_max=100
+ )
else:
- percent = gremlin.util.scale_to_range(curve_value, target_min=0, target_max = 100)
+ percent = gremlin.util.scale_to_range(
+ curve_value, target_min=0, target_max=100
+ )
else:
percent = percent_value
self._display_percent_widget.setText(f"{percent:0.1f} %")
self.valueChanged.emit(self._value, self._curve_value)
- except:
- pass # C++ QT exception because of sync issues with Python/QT
+ except Exception:
+ pass # C++ QT exception because of sync issues with Python/QT
- def value(self):
- ''' gets the current value '''
- return self._value
-
- def setRange(self, min = -1.0, max = 1.0, decimals = 3):
- ''' sets the range of the widget '''
+ def setRange(self, min=-1.0, max=1.0, decimals=3):
+ """sets the range of the widget"""
if min > max:
max, min = min, max
self._min_range = min
@@ -3678,17 +3773,16 @@ def setRange(self, min = -1.0, max = 1.0, decimals = 3):
def _update_range(self):
self._progress_widget.setRange(
- self._scale_factor * self._min_range,
- self._scale_factor * self._max_range
+ self._scale_factor * self._min_range, self._scale_factor * self._max_range
)
self._setValue(self._value)
def setMaximum(self, value):
- ''' sets the upper range value '''
+ """sets the upper range value"""
self.setRange(self._min_range, value)
def setMinimum(self, value):
- ''' sets the lower range value'''
+ """sets the lower range value"""
self.setRange(value, self._max_range)
def setReverse(self, value):
@@ -3696,26 +3790,28 @@ def setReverse(self, value):
self._setValue(self._value)
def reverse(self):
- ''' reverse flag '''
+ """reverse flag"""
return self._reverse
def hookDevice(self, device_guid, input_type, input_id):
- ''' hooks an axis (manual)'''
+ """hooks an axis (manual)"""
import gremlin.joystick_handling
import gremlin.event_handler
- if device_guid is None:
+
+ if device_guid is None:
# not a valid device to hook
return
-
+
self._device_guid = device_guid
self._input_id = input_id
self._input_type = input_type
self._scale_factor = 1000
self._value = -1
self.setRange(-1, 1)
-
-
- self._is_hardware_input = gremlin.joystick_handling.is_hardware_device(device_guid)
+
+ self._is_hardware_input = gremlin.joystick_handling.is_hardware_device(
+ device_guid
+ )
if self._input_type in (InputType.OpenSoundControl, InputType.Midi):
self._value = input_id.axis_value
elif self._is_hardware_input:
@@ -3728,8 +3824,8 @@ def hookDevice(self, device_guid, input_type, input_id):
self._update_value(self._value)
self._handler_connected = False
-
- #self._tab_selected(device_guid)
+
+ # self._tab_selected(device_guid)
@QtCore.Slot()
def _profile_start(self):
@@ -3737,17 +3833,16 @@ def _profile_start(self):
if self._joystick_hooked:
el = gremlin.event_handler.EventListener()
el.joystick_event.disconnect(self._joystick_event)
-
-
+
@QtCore.Slot()
def _profile_stop(self):
# re-attach when profile stops
if self._joystick_hooked:
el = gremlin.event_handler.EventListener()
el.joystick_event.connect(self._joystick_event)
- self._value = gremlin.joystick_handling.get_axis(self._device_guid, self._input_id)
-
-
+ self._value = gremlin.joystick_handling.get_axis(
+ self._device_guid, self._input_id
+ )
@QtCore.Slot(object)
def _joystick_event(self, event):
@@ -3755,9 +3850,9 @@ def _joystick_event(self, event):
# do not update while profile is running
return
if self._device_guid is None:
- return
+ return
if not event.is_axis:
- return
+ return
if self._device_guid != event.device_guid:
return
if self._input_type != event.event_type:
@@ -3768,95 +3863,92 @@ def _joystick_event(self, event):
def unhookDevice(self):
import gremlin.event_handler
+
if self._joystick_hooked:
el = gremlin.event_handler.EventListener()
el.joystick_event.disconnect(self._joystick_event)
self._tab_unselected(self._device_guid)
self._device_guid = None
-
@property
def enabled(self) -> bool:
- return self._handler_connected
+ return self._handler_connected
@QtCore.Slot(str)
def _tab_selected(self, device_guid):
- ''' triggered when a tab is selected
-
+ """triggered when a tab is selected
+
:param device_guid: the device selected
-
- '''
+
+ """
if self._handler_connected:
# already connected
return
-
+
device_name = gremlin.shared_state.get_device_name(device_guid)
if isinstance(device_guid, str):
device_guid = gremlin.util.parse_guid(device_guid)
-
+
if self._device_guid == device_guid:
# connect the handler
input_id = self._input_id
verbose = gremlin.config.Configuration().verbose_mode_inputs
- if verbose:
+ if verbose:
# syslog = logging.getLogger("system")
syslog.info(f"AxisState: {device_name} axis {str(input_id)} connect")
- _state_tracker.registerAxisState(self, self._device_guid, self._input_type, self._input_id)
+ _state_tracker.registerAxisState(
+ self, self._device_guid, self._input_type, self._input_id
+ )
self._handler_connected = True
-
-
@QtCore.Slot(str)
def _tab_unselected(self, device_guid):
- ''' triggered when a device tab is deselected, also used to force a disconnect
-
+ """triggered when a device tab is deselected, also used to force a disconnect
+
:param device_guid: the device to deselect - if None - deselect all
-
- '''
+
+ """
if not self._handler_connected:
- # not connected
+ # not connected
return
# syslog = logging.getLogger("system")
- el = gremlin.event_handler.EventListener()
+ # el = gremlin.event_handler.EventListener()
if device_guid:
if isinstance(device_guid, str):
device_guid = gremlin.util.parse_guid(device_guid)
disconnect = self._device_guid == device_guid
- device_name = gremlin.shared_state.get_device_name(device_guid)
+ # device_name = gremlin.shared_state.get_device_name(device_guid)
else:
disconnect = True
- device_name = "reset"
-
+ # device_name = "reset"
+
if disconnect:
# disconnect the handler
- input_id = self._input_id
+ # input_id = self._input_id
# syslog.info(f"AxisState: (unselect) {device_name} axis {input_id} disconnect")
- _state_tracker.unregisterAxisState(self._device_guid, self._input_type, self._input_id)
+ _state_tracker.unregisterAxisState(
+ self._device_guid, self._input_type, self._input_id
+ )
self._handler_connected = False
-
-
def _update_value(self, value):
# invert the input if needed
if self._is_hardware_input:
- #eh = gremlin.event_handler.EventListener()
+ # eh = gremlin.event_handler.EventListener()
# value = eh._apply_calibration_ex(self._device_guid, self._input_id, raw_value)
# curve_value = eh._apply_curve_ex(self._device_guid, self._input_id, value)
- #print (f"raw: {raw_value:0.3f} calibrated: {value:0.3f} curved: {curve_value:0.3f}")
- #self.setValue(value, curve_value)
+ # print (f"raw: {raw_value:0.3f} calibrated: {value:0.3f} curved: {curve_value:0.3f}")
+ # self.setValue(value, curve_value)
self._setValue(value)
else:
self._setValue(value)
- #self.setValue(raw_value)
-
-
+ # self.setValue(raw_value)
class AxesCurrentState(QtWidgets.QGroupBox):
-
"""Displays the current state of all axes on a device (input viewer)"""
- def __init__(self, device : DeviceSummary, parent=None):
+ def __init__(self, device: DeviceSummary, parent=None):
"""Creates a new instance.
:param device the device of which to display the axes sate
@@ -3876,25 +3968,31 @@ def __init__(self, device : DeviceSummary, parent=None):
self.index_map = {}
axes_layout = QtWidgets.QGridLayout()
axes_layout.setSpacing(0)
- axis_list = device.axis_index_list()
+ axis_list = device.axis_index_list()
- for i in range(8):
+ for i in range(8):
index = i + 1
-
- widget,layout = getVContainer()
+
+ widget, layout = getVContainer()
# widget.setStyleSheet("border: 1px solid;")
widget.setFixedWidth(80)
if index in axis_list:
- axis_id = gremlin.joystick_handling.linear_axis_index(self.device.axis_map,index)
+ axis_id = gremlin.joystick_handling.linear_axis_index(
+ self.device.axis_map, index
+ )
self.index_map[axis_id] = index
- axis = AxisStateWidget(index, show_value = False, show_label=False, show_percentage=False)
+ axis = AxisStateWidget(
+ index, show_value=False, show_label=False, show_percentage=False
+ )
value = gremlin.joystick_handling.get_axis(device.device_guid, index)
- #print (f"Axis {axis_id} value: {value:0.3f}")
+ # print (f"Axis {axis_id} value: {value:0.3f}")
value_label = QtWidgets.QLabel(f"{value:+0.3f}")
- #axis.setValue(value)
+ # axis.setValue(value)
self.axes[index] = axis
self.value_labels[index] = value_label
- percent = gremlin.util.scale_to_range(value,target_min=0, target_max=100)
+ percent = gremlin.util.scale_to_range(
+ value, target_min=0, target_max=100
+ )
value_label = QtWidgets.QLabel(f"{value:+0.3f}")
percent_label = QtWidgets.QLabel(f"{percent:0.1f} %")
self.percent_labels[index] = percent_label
@@ -3903,13 +4001,13 @@ def __init__(self, device : DeviceSummary, parent=None):
else:
value_label = QtWidgets.QLabel(" ")
percent_label = QtWidgets.QLabel(" ")
-
+
axes_layout.addWidget(widget, 0, i, alignment=QtCore.Qt.AlignCenter)
axes_layout.addWidget(value_label, 1, i, alignment=QtCore.Qt.AlignCenter)
axes_layout.addWidget(percent_label, 2, i, alignment=QtCore.Qt.AlignCenter)
- #axes_layout.addStretch()
- axes_layout.setColumnStretch(i+1,2)
+ # axes_layout.addStretch()
+ axes_layout.setColumnStretch(i + 1, 2)
self.setLayout(axes_layout)
def process_event(self, event):
@@ -3919,19 +4017,17 @@ def process_event(self, event):
"""
if event.event_type == InputType.JoystickAxis:
axis_id = gremlin.joystick_handling.linear_axis_index(
- self.device.axis_map,
- event.identifier
+ self.device.axis_map, event.identifier
)
index = self.index_map[axis_id]
value = event.value
self.axes[index].setValue(value)
self.value_labels[index].setText(f"{value:+0.3f}")
- percent = gremlin.util.scale_to_range(value,target_min=0, target_max=100)
+ percent = gremlin.util.scale_to_range(value, target_min=0, target_max=100)
self.percent_labels[index].setText(f"{percent:0.1f} %")
class HatWidget(QtWidgets.QWidget):
-
"""Widget visualizing the state of a hat."""
# Polygon path for a triangle
@@ -3949,7 +4045,7 @@ class HatWidget(QtWidgets.QWidget):
(0, -1): 0,
(-1, -1): 45,
(-1, 0): 90,
- (-1, 1): 135
+ (-1, 1): 135,
}
def __init__(self, parent=None):
@@ -3984,9 +4080,9 @@ def paintEvent(self, event):
# Define pens and brushes
active_color = Color.activeColor()
- border_color = Color.borderColor()
+ # border_color = Color.borderColor()
inactive_color = Color.inactiveColor()
-
+
normal_color = Color.normalColor()
# pen_default = QtGui.QPen(QtGui.QColor("#8f8f91"))
@@ -4006,7 +4102,9 @@ def paintEvent(self, event):
# Prepare painter instance
p = QtGui.QPainter(self)
# p.begin(self)
- p.setRenderHint(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform)
+ p.setRenderHint(
+ QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform
+ )
p.setPen(pen_default)
p.setBrush(brush_default)
@@ -4030,11 +4128,10 @@ def paintEvent(self, event):
p.drawPolygon(HatWidget.triangle)
p.restore()
-
p.end()
-class HatState(QtWidgets.QGroupBox):
+class HatState(QtWidgets.QGroupBox):
"""Visualizes the sate of a device's hats."""
def __init__(self, device, parent=None):
@@ -4072,7 +4169,6 @@ def process_event(self, event):
class AxesTimeline(QtWidgets.QGroupBox):
-
"""Visualizes axes state as a timeline."""
def __init__(self, device, parent=None):
@@ -4096,7 +4192,9 @@ def __init__(self, device, parent=None):
for i in range(device.axis_count):
index = device.axis_map[i].axis_index
label = QtWidgets.QLabel(f"Axis {index:d}")
- css = f"QLabel {{ color: {colors.get(index,"#000000")}; font-weight: bold }}"
+ css = (
+ f"QLabel {{ color: {colors.get(index, '#000000')}; font-weight: bold }}"
+ )
label.setStyleSheet(css)
self.legend_layout.addWidget(label)
self.layout().addWidget(self.plot_widget)
@@ -4111,16 +4209,9 @@ def add_point(self, value, series_id):
self.plot_widget.add_point(value, series_id)
-
-
-
-
class TimeLinePlotWidget(QtWidgets.QWidget):
-
"""Visualizes temporal data as a line graph."""
-
-
def __init__(self, parent=None):
"""Creates a new instance.
@@ -4130,13 +4221,15 @@ def __init__(self, parent=None):
self._background_color = Color.actionBackgroundColor()
- self._render_flags = QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform
+ self._render_flags = (
+ QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform
+ )
# Plotting canvas
self._pens = Color.Pens()
self._pixmap = QtGui.QPixmap(1000, 200)
self._pixmap.fill()
- self._rect = QtCore.QRect(0,0,1000,200)
+ self._rect = QtCore.QRect(0, 0, 1000, 200)
self._background_qcolor = QtGui.QColor(self._background_color)
self._background_brush = QtGui.QBrush(self._background_qcolor)
@@ -4150,7 +4243,7 @@ def __init__(self, parent=None):
# Step size per update
self._step_size = 1
- interval = int(1000/60)
+ interval = int(1000 / 60)
# Update the plot
self._update_timer = QtCore.QTimer(self)
@@ -4185,14 +4278,10 @@ def paintEvent(self, event):
:param event the paint event
"""
p = QtGui.QPainter(self)
-
-
+
p.drawPixmap(0, 0, self._pixmap)
p.end()
-
-
-
def add_point(self, value, series_id=0):
"""Adds a data point to a time series.
@@ -4207,32 +4296,34 @@ def _update_pixmap(self):
"""Updates the pixmap that contains the moving timeline."""
p = QtGui.QPainter(self._pixmap)
p.setBackground(QtGui.QBrush(QtGui.QColor(self._background_color)))
-
# p.begin(self)
p.setRenderHint(self._render_flags)
- self._pixmap.scroll(-self._step_size, 0, QtCore.QRect(0, 0, self._pixmap.width(), self._pixmap.height())
+ self._pixmap.scroll(
+ -self._step_size,
+ 0,
+ QtCore.QRect(0, 0, self._pixmap.width(), self._pixmap.height()),
)
- p.eraseRect(self._pixmap.width() - self._step_size, 0, 1,self._pixmap.height())
+ p.eraseRect(self._pixmap.width() - self._step_size, 0, 1, self._pixmap.height())
# Draw vertical line in one second intervals
p.setPen(self._pens[0])
- if self._vertical_timestep < time.time()-1:
+ if self._vertical_timestep < time.time() - 1:
p.drawLine(
- self._pixmap.width()-1,
+ self._pixmap.width() - 1,
0,
self._pixmap.width() - 1,
- self._pixmap.height()
+ self._pixmap.height(),
)
self._vertical_timestep = time.time()
self._horizontal_steps += 1
if self._horizontal_steps <= 5:
quarter = int(self._pixmap.height() / 4)
- x = self._pixmap.width()-1
+ x = self._pixmap.width() - 1
p.drawPoint(x, quarter)
- p.drawPoint(x, 2*quarter)
- p.drawPoint(x, 3*quarter)
+ p.drawPoint(x, 2 * quarter)
+ p.drawPoint(x, 3 * quarter)
elif self._horizontal_steps > 10:
self._horizontal_steps = 0
@@ -4240,19 +4331,18 @@ def _update_pixmap(self):
for key, value in self._series.items():
p.setPen(self._pens[key])
p.drawLine(
- self._pixmap.width()-self._step_size-1,
- int(2 + (self._pixmap.height()-4) * (value[0] + 1) / 2.0),
- self._pixmap.width()-1,
- int(2 + (self._pixmap.height()-4) * (value[1] + 1) / 2.0)
+ self._pixmap.width() - self._step_size - 1,
+ int(2 + (self._pixmap.height() - 4) * (value[0] + 1) / 2.0),
+ self._pixmap.width() - 1,
+ int(2 + (self._pixmap.height() - 4) * (value[1] + 1) / 2.0),
)
value[0] = value[1]
-
p.end()
-class VigemDeviceWidget(QtWidgets.QWidget):
- """ joystick visualization widget """
+class VigemDeviceWidget(QtWidgets.QWidget):
+ """joystick visualization widget"""
def __init__(self, device, vis_type, parent=None):
super().__init__(parent)
@@ -4260,14 +4350,13 @@ def __init__(self, device, vis_type, parent=None):
self.vis_type = vis_type
self.widgets = []
layout = QtWidgets.QHBoxLayout()
- layout.setContentsMargins(0,0,0,0)
+ layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
self.vis_type = vis_type
self._hooked = False
-
def unhook(self):
- ''' unhooks events '''
+ """unhooks events"""
if not self._hooked:
return
vis_type = self.vis_type
@@ -4281,16 +4370,13 @@ def unhook(self):
self._hooked = False
def _clear_ui(self):
- self.unhook()
-
-
+ self.unhook()
class JoystickDeviceWidget(QtWidgets.QWidget):
+ """joystick visualization widget"""
- """ joystick visualization widget """
-
- def __init__(self, device_data : DeviceSummary, vis_type, parent=None):
+ def __init__(self, device_data: DeviceSummary, vis_type, parent=None):
"""Creates a new instance.
:param device_data information about the device itself
@@ -4304,16 +4390,13 @@ def __init__(self, device_data : DeviceSummary, vis_type, parent=None):
self.vis_type = vis_type
self.widgets = []
layout = QtWidgets.QHBoxLayout()
- layout.setContentsMargins(0,0,0,0)
+ layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
self.vis_type = vis_type
self._hooked = False
-
-
-
def hook(self):
- ''' hooks events '''
+ """hooks events"""
if self._hooked:
return
vis_type = self.vis_type
@@ -4335,7 +4418,7 @@ def hook(self):
self._hooked = True
def unhook(self):
- ''' unhooks events '''
+ """unhooks events"""
if not self._hooked:
return
vis_type = self.vis_type
@@ -4366,10 +4449,7 @@ def minimumSizeHint(self):
def _create_button_hat(self):
"""Creates display for button and hat data."""
- self.widgets = [
- ButtonState(self.device_data),
- HatState(self.device_data)
- ]
+ self.widgets = [ButtonState(self.device_data), HatState(self.device_data)]
for widget in self.widgets:
self.layout().addWidget(widget)
self.layout().addStretch(1)
@@ -4418,10 +4498,9 @@ def _temporal_axis_update(self, event):
class ButtonState(QtWidgets.QGroupBox):
-
"""Widget representing the state of a device's buttons."""
- def __init__(self, device : DeviceSummary, parent=None):
+ def __init__(self, device: DeviceSummary, parent=None):
"""Creates a new instance.
:param device the device of which to display the button sate
@@ -4440,19 +4519,17 @@ def __init__(self, device : DeviceSummary, parent=None):
self.buttons = [None]
button_layout = QtWidgets.QGridLayout()
for i in range(device.button_count):
- btn = QtWidgets.QPushButton(str(i+1))
+ btn = QtWidgets.QPushButton(str(i + 1))
btn.setStyleSheet(css)
btn.setDisabled(True)
# read the current state
- is_pressed = gremlin.joystick_handling.get_button(device.device_guid, i+1)
+ is_pressed = gremlin.joystick_handling.get_button(device.device_guid, i + 1)
btn.setDown(is_pressed)
self.buttons.append(btn)
button_layout.addWidget(btn, int(i / 10), int(i % 10))
button_layout.setColumnStretch(10, 1)
self.setLayout(button_layout)
-
-
def process_event(self, event):
"""Updates state visualization based on the given event.
@@ -4464,16 +4541,13 @@ def process_event(self, event):
self._event_times[event.identifier] = time.time()
-
-
class QRowSelectorFrame(QtWidgets.QFrame):
-
selected_changed = QtCore.Signal(object)
- def __init__(self, data = None, parent = None, selected = False):
+ def __init__(self, data=None, parent=None, selected=False):
super().__init__(parent)
self._emit = False
- self._selected = not selected # force an update to the stylesheet
+ self._selected = not selected # force an update to the stylesheet
self.selected = selected
self._data = data
self._emit = True
@@ -4485,7 +4559,6 @@ def __init__(self, data = None, parent = None, selected = False):
css = f"Qframe {{ border 1px solid {border_color}; border-top: none; background-color:{background_color} }}"
self.setStyleSheet(css)
-
def setSelectable(self, value):
self._selectable = value
@@ -4493,7 +4566,7 @@ def getSelectable(self):
return self._selectable
def eventFilter(self, widget, event):
- ''' ensure line changes are saved '''
+ """ensure line changes are saved"""
t = event.type()
if self._selectable and t == QtCore.QEvent.Type.MouseButtonPress:
self.selected = not self.selected
@@ -4508,7 +4581,7 @@ def selected(self, value):
# change selection mode
if value != self._selected:
self._selected = value
-
+
if value:
background_color = Color.selectColor()
else:
@@ -4529,37 +4602,37 @@ def data(self, value):
def get_text_width(text):
- ''' gets the average text width '''
+ """gets the average text width"""
lbl = QtWidgets.QLabel("M")
char_width = lbl.fontMetrics().averageCharWidth()
return char_width * (len(text) if text else 1)
-def get_text_height(text = None):
- ''' gets the average text width '''
+
+def get_text_height(text=None):
+ """gets the average text width"""
lbl = QtWidgets.QLabel(text if text else "M")
fm = lbl.fontMetrics()
- rect = fm.boundingRect(QtCore.QRect(0,0,100,100), QtCore.Qt.TextWordWrap, lbl.text())
+ rect = fm.boundingRect(
+ QtCore.QRect(0, 0, 100, 100), QtCore.Qt.TextWordWrap, lbl.text()
+ )
return rect.height()
-
-def get_char_width(count = 1):
+def get_char_width(count=1):
return get_text_width("w") * count
-
-
class QToggle(QCheckBox):
-
_transparent_pen = QPen(Qt.transparent)
_light_grey_pen = QPen(Qt.lightGray)
- def __init__(self,
+ def __init__(
+ self,
parent=None,
bar_color=Qt.gray,
checked_color="#8FBC8F",
handle_color=Qt.white,
- ):
+ ):
super().__init__(parent)
# Save our properties on the object via self, so we can access them later
@@ -4584,7 +4657,6 @@ def hitButton(self, pos: QPoint):
return self.contentsRect().contains(pos)
def paintEvent(self, e: QPaintEvent):
-
contRect = self.contentsRect()
handleRadius = round(0.24 * contRect.height())
@@ -4594,8 +4666,7 @@ def paintEvent(self, e: QPaintEvent):
p.setPen(self._transparent_pen)
barRect = QRectF(
- 0, 0,
- contRect.width() - handleRadius, 0.40 * contRect.height()
+ 0, 0, contRect.width() - handleRadius, 0.40 * contRect.height()
)
barRect.moveCenter(contRect.center())
rounding = barRect.height() / 2
@@ -4615,9 +4686,7 @@ def paintEvent(self, e: QPaintEvent):
p.setPen(self._light_grey_pen)
p.setBrush(self._handle_brush)
- p.drawEllipse(
- QPointF(xPos, barRect.center().y()),
- handleRadius, handleRadius)
+ p.drawEllipse(QPointF(xPos, barRect.center().y()), handleRadius, handleRadius)
p.end()
@@ -4649,15 +4718,17 @@ def pulse_radius(self, pos):
self.update()
-
class QAnimatedToggle(QToggle):
-
_transparent_pen = QPen(Qt.transparent)
_light_grey_pen = QPen(Qt.lightGray)
- def __init__(self, *args, pulse_unchecked_color="#44999999",
- pulse_checked_color="#4400B0EE", **kwargs):
-
+ def __init__(
+ self,
+ *args,
+ pulse_unchecked_color="#44999999",
+ pulse_checked_color="#4400B0EE",
+ **kwargs,
+ ):
self._pulse_radius = 0
super().__init__(*args, **kwargs)
@@ -4678,8 +4749,6 @@ def __init__(self, *args, pulse_unchecked_color="#44999999",
self._pulse_unchecked_animation = QBrush(QColor(pulse_unchecked_color))
self._pulse_checked_animation = QBrush(QColor(pulse_checked_color))
-
-
@Slot(int)
def handle_state_change(self, value):
self.animations_group.stop()
@@ -4690,7 +4759,6 @@ def handle_state_change(self, value):
self.animations_group.start()
def paintEvent(self, e: QPaintEvent):
-
contRect = self.contentsRect()
handleRadius = round(0.24 * contRect.height())
@@ -4700,8 +4768,7 @@ def paintEvent(self, e: QPaintEvent):
p.setPen(self._transparent_pen)
barRect = QRectF(
- 0, 0,
- contRect.width() - handleRadius, 0.40 * contRect.height()
+ 0, 0, contRect.width() - handleRadius, 0.40 * contRect.height()
)
barRect.moveCenter(contRect.center())
rounding = barRect.height() / 2
@@ -4713,10 +4780,15 @@ def paintEvent(self, e: QPaintEvent):
if self.pulse_anim.state() == QPropertyAnimation.Running:
p.setBrush(
- self._pulse_checked_animation if
- self.isChecked() else self._pulse_unchecked_animation)
- p.drawEllipse(QPointF(xPos, barRect.center().y()),
- self._pulse_radius, self._pulse_radius)
+ self._pulse_checked_animation
+ if self.isChecked()
+ else self._pulse_unchecked_animation
+ )
+ p.drawEllipse(
+ QPointF(xPos, barRect.center().y()),
+ self._pulse_radius,
+ self._pulse_radius,
+ )
if self.isChecked():
p.setBrush(self._bar_checked_brush)
@@ -4729,19 +4801,17 @@ def paintEvent(self, e: QPaintEvent):
p.setPen(self._light_grey_pen)
p.setBrush(self._handle_brush)
- p.drawEllipse(
- QPointF(xPos, barRect.center().y()),
- handleRadius, handleRadius)
+ p.drawEllipse(QPointF(xPos, barRect.center().y()), handleRadius, handleRadius)
p.end()
-
class QToggleText(QtWidgets.QWidget):
- ''' switched checkbox '''
+ """switched checkbox"""
+
clicked = QtCore.Signal()
- def __init__(self, text = None, parent = None):
+ def __init__(self, text=None, parent=None):
super().__init__(parent)
self.main_layout = QtWidgets.QHBoxLayout(self)
self._button = QToggle()
@@ -4753,41 +4823,43 @@ def __init__(self, text = None, parent = None):
self._label.setText(text)
self._button.clicked.connect(self._clicked_cb)
-
@QtCore.Slot()
def _clicked_cb(self):
self.clicked.emit()
def text(self):
return self._label.text()
+
def setText(self, value):
self._label.setText(value)
def isChecked(self):
return self._button.isChecked()
+
def setChecked(self, value):
self._button.setChecked(value)
@property
def value(self):
return self._button.isChecked()
+
@value.setter
def value(self, checked):
self._button.setChecked(checked)
class QDelayWidget(QtWidgets.QWidget):
- ''' widget to collect a delay time in milliseconds '''
+ """widget to collect a delay time in milliseconds"""
- valueChanged = QtCore.Signal() # fired when the value changes
+ valueChanged = QtCore.Signal() # fired when the value changes
- def __init__(self, value = 250, parent = None):
- '''
+ def __init__(self, value=250, parent=None):
+ """
- :params value: default delay in milliseconds '''
+ :params value: default delay in milliseconds"""
super().__init__(parent)
self.main_layout = QtWidgets.QHBoxLayout(self)
- self.main_layout.setContentsMargins(0,0,0,0)
+ self.main_layout.setContentsMargins(0, 0, 0, 0)
self.delay_container_widget = QtWidgets.QWidget()
self.delay_container_layout = QtWidgets.QHBoxLayout()
@@ -4797,9 +4869,9 @@ def __init__(self, value = 250, parent = None):
delay_label = QtWidgets.QLabel("Delay (ms)")
self._delay_widget = QIntLineEdit()
- self._delay_widget.setRange(0, 20000) # up to 20 seconds
+ self._delay_widget.setRange(0, 20000) # up to 20 seconds
self._delay_widget.setMaximumWidth(width)
- self._delay_widget.setValue(value) # default
+ self._delay_widget.setValue(value) # default
self._delay_widget.valueChanged.connect(self._value_changed)
quarter_sec_button = QtWidgets.QPushButton("1/4s")
@@ -4810,7 +4882,6 @@ def __init__(self, value = 250, parent = None):
half_sec_button.clicked.connect(self._half_sec_delay)
sec_button.clicked.connect(self._sec_delay)
-
self.delay_container_layout.addWidget(delay_label)
self.delay_container_layout.addWidget(self._delay_widget)
self.delay_container_layout.addWidget(quarter_sec_button)
@@ -4821,10 +4892,10 @@ def __init__(self, value = 250, parent = None):
self.main_layout.addWidget(self.delay_container_widget)
def value(self):
- ''' gets the delay in milliseconds '''
+ """gets the delay in milliseconds"""
return self._delay_widget.value()
- def setValue(self, value : int):
+ def setValue(self, value: int):
if value >= 0 and value != self._delay_widget.value():
self._delay_widget.setValue(value)
self.valueChanged.emit()
@@ -4846,18 +4917,15 @@ def _sec_delay(self):
self._delay_widget.setValue(1000)
-import gremlin.singleton_decorator
@gremlin.singleton_decorator.SingletonDecorator
-class QHelper():
-
- def __init__(self, show_percent = False, decimals = 3, single_step = 0.01):
+class QHelper:
+ def __init__(self, show_percent=False, decimals=3, single_step=0.01):
self._show_percent = show_percent
self._decimals = decimals
self._single_step = single_step
self._min_range = -1.0
self._max_range = 1.0
-
@property
def decimals(self):
if self.show_percent:
@@ -4876,7 +4944,7 @@ def single_step(self):
@property
def min_range(self):
- ''' current min range '''
+ """current min range"""
return self._min_range
@min_range.setter
@@ -4885,7 +4953,7 @@ def min_range(self, value):
@property
def max_range(self):
- ''' current max range '''
+ """current max range"""
return self._max_range
@max_range.setter
@@ -4895,15 +4963,18 @@ def max_range(self, value):
@property
def show_percent(self):
return self._show_percent
+
@show_percent.setter
def show_percent(self, value):
self._show_percent = value
- def get_double_spinbox(self, id, value, min_range = -1.0, max_range = 1.0) -> DynamicDoubleSpinBox:
- ''' creates a double spin box formatted for the display mode '''
+ def get_double_spinbox(
+ self, id, value, min_range=-1.0, max_range=1.0
+ ) -> DynamicDoubleSpinBox:
+ """creates a double spin box formatted for the display mode"""
show_percent = self.show_percent
assert isinstance(id, str)
- sb_widget = DynamicDoubleSpinBox(data = id)
+ sb_widget = DynamicDoubleSpinBox(data=id)
if show_percent:
sb_widget.setMinimum(0)
sb_widget.setMaximum(100)
@@ -4919,19 +4990,22 @@ def get_double_spinbox(self, id, value, min_range = -1.0, max_range = 1.0) -> Dy
return sb_widget
def to_value(self, value):
- ''' returns a [-1,+1] value converted to the range output'''
+ """returns a [-1,+1] value converted to the range output"""
if self.show_percent:
- return gremlin.util.scale_to_range(value, target_min = 0, target_max = 100)
+ return gremlin.util.scale_to_range(value, target_min=0, target_max=100)
else:
- return gremlin.util.scale_to_range(value, target_min = self.min_range, target_max = self.max_range)
+ return gremlin.util.scale_to_range(
+ value, target_min=self.min_range, target_max=self.max_range
+ )
class QDoubleClickSpinBox(QtWidgets.QSpinBox):
- ''' double click to reset spinbox '''
+ """double click to reset spinbox"""
+
doubleClick = QtCore.Signal()
- def __init__(self, parent = None):
- super().__init__(parent = None)
+ def __init__(self, parent=None):
+ super().__init__(parent=None)
self.installEventFilter(self)
def eventFilter(self, object, event):
@@ -4942,7 +5016,6 @@ def eventFilter(self, object, event):
class DualSlider(QtWidgets.QWidget):
-
"""Slider widget which provides two sliders to define a range. The
lower and upper slider cannot pass through each other."""
@@ -5070,20 +5143,20 @@ def _constrain_value(self, handle, value):
slider = self.style().subControlRect(
QtWidgets.QStyle.CC_Slider,
self._get_common_option(),
- QtWidgets.QStyle.SC_SliderHandle
+ QtWidgets.QStyle.SC_SliderHandle,
)
if handle == self.LowerHandle:
return gremlin.util.clamp(
value,
self._range[0],
- self._upper_position - self._width_to_logical(slider.width())
+ self._upper_position - self._width_to_logical(slider.width()),
)
else:
return gremlin.util.clamp(
value,
self._lower_position + self._width_to_logical(slider.width()),
- self._range[1]
+ self._range[1],
)
def _width_to_logical(self, value):
@@ -5095,11 +5168,11 @@ def _width_to_logical(self, value):
groove_rect = self.style().subControlRect(
QtWidgets.QStyle.CC_Slider,
self._get_common_option(),
- QtWidgets.QStyle.SC_SliderGroove
+ QtWidgets.QStyle.SC_SliderGroove,
+ )
+ return int(
+ round((value / groove_rect.width()) * (self._range[1] - self._range[0]))
)
- return int(round(
- (value / groove_rect.width()) * (self._range[1] - self._range[0])
- ))
def _position_to_logical(self, pos):
"""Converts a pixel position on a slider to it's logical
@@ -5111,14 +5184,14 @@ def _position_to_logical(self, pos):
groove_rect = self.style().subControlRect(
QtWidgets.QStyle.CC_Slider,
self._get_common_option(),
- QtWidgets.QStyle.SC_SliderGroove
+ QtWidgets.QStyle.SC_SliderGroove,
)
return QtWidgets.QStyle.sliderValueFromPosition(
self._range[0],
self._range[1],
pos - groove_rect.left(),
- groove_rect.right() - groove_rect.left()
+ groove_rect.right() - groove_rect.left(),
)
def sizeHint(self):
@@ -5147,9 +5220,7 @@ def mousePressEvent(self, evt):
option.subControls = QtWidgets.QStyle.SC_SliderHandle
control = self.style().hitTestComplexControl(
- QtWidgets.QStyle.CC_Slider,
- option,
- position
+ QtWidgets.QStyle.CC_Slider, option, position
)
lower_clicked = False
if control == QtWidgets.QStyle.SC_SliderHandle:
@@ -5158,9 +5229,7 @@ def mousePressEvent(self, evt):
option.sliderPosition = self._upper_position
option.sliderValue = self._upper_position
control = self.style().hitTestComplexControl(
- QtWidgets.QStyle.CC_Slider,
- option,
- position
+ QtWidgets.QStyle.CC_Slider, option, position
)
upper_clicked = False
if control == QtWidgets.QStyle.SC_SliderHandle:
@@ -5195,12 +5264,10 @@ def mouseMoveEvent(self, evt):
if self._active_handle:
value = self._position_to_logical(evt.pos().x())
if self._active_handle == self.LowerHandle:
- self._lower_position =\
- self._constrain_value(self.LowerHandle, value)
+ self._lower_position = self._constrain_value(self.LowerHandle, value)
value = self._lower_position
elif self._active_handle == self.UpperHandle:
- self._upper_position =\
- self._constrain_value(self.UpperHandle, value)
+ self._upper_position = self._constrain_value(self.UpperHandle, value)
value = self._upper_position
self.valueChanged.emit(self._active_handle, value)
self.sliderMoved.emit(self._active_handle, value)
@@ -5244,7 +5311,6 @@ def paintEvent(self, evt):
painter.drawComplexControl(QtWidgets.QStyle.CC_Slider, option_upper)
-
class QCustomFlowLayout(QtWidgets.QLayout):
def __init__(self, parent=None):
super().__init__(parent)
@@ -5300,7 +5366,9 @@ def minimumSize(self):
for item in self._item_list:
size = size.expandedTo(item.minimumSize())
- size += QSize(2 * self.contentsMargins().top(), 2 * self.contentsMargins().top())
+ size += QSize(
+ 2 * self.contentsMargins().top(), 2 * self.contentsMargins().top()
+ )
return size
def _do_layout(self, rect, test_only):
@@ -5312,10 +5380,14 @@ def _do_layout(self, rect, test_only):
for item in self._item_list:
style = item.widget().style()
layout_spacing_x = style.layoutSpacing(
- QtWidgets.QSizePolicy.PushButton, QtWidgets.QSizePolicy.PushButton, Qt.Orientation.Horizontal
+ QtWidgets.QSizePolicy.PushButton,
+ QtWidgets.QSizePolicy.PushButton,
+ Qt.Orientation.Horizontal,
)
layout_spacing_y = style.layoutSpacing(
- QtWidgets.QSizePolicy.PushButton, QtWidgets.QSizePolicy.PushButton, Qt.Vertical
+ QtWidgets.QSizePolicy.PushButton,
+ QtWidgets.QSizePolicy.PushButton,
+ Qt.Vertical,
)
space_x = spacing + layout_spacing_x
space_y = spacing + layout_spacing_y
@@ -5335,18 +5407,16 @@ def _do_layout(self, rect, test_only):
return y + line_height - rect.y()
-
-
class QFlowLayout(QtWidgets.QLayout):
def __init__(self, parent=None, margin=-1, hspacing=-1, vspacing=-1):
- '''
+ """
:params:
parent = parent of the object
margin = margin, -1 for auto
hspacing = horizontal spacing, -1 for auto
vspacing = vertical spacing, -1 for auto
sort_property = name of the index member of the item to set the display order, None to disable
- '''
+ """
super().__init__(parent)
self._hspacing = hspacing
self._vspacing = vspacing
@@ -5356,7 +5426,6 @@ def __init__(self, parent=None, margin=-1, hspacing=-1, vspacing=-1):
self._row = 0
self._col = 0
-
def __del__(self):
del self._items[:]
@@ -5364,22 +5433,20 @@ def addItem(self, item):
self._items.append(item)
def sortItems(self, callback):
- ''' sorts the items based on the given sort property '''
- self._items.sort(key = lambda item: callback(item))
+ """sorts the items based on the given sort property"""
+ self._items.sort(key=lambda item: callback(item))
def horizontalSpacing(self):
if self._hspacing >= 0:
return self._hspacing
else:
- return self.smartSpacing(
- QtWidgets.QStyle.PM_LayoutHorizontalSpacing)
+ return self.smartSpacing(QtWidgets.QStyle.PM_LayoutHorizontalSpacing)
def verticalSpacing(self):
if self._vspacing >= 0:
return self._vspacing
else:
- return self.smartSpacing(
- QtWidgets.QStyle.PM_LayoutVerticalSpacing)
+ return self.smartSpacing(QtWidgets.QStyle.PM_LayoutVerticalSpacing)
def count(self):
return len(self._items)
@@ -5415,7 +5482,7 @@ def minimumSize(self):
lineheight = max(lineheight, item.sizeHint().height())
for item in self._items:
size = size.expandedTo(item.minimumSize())
- #size = size.expandedTo(item.sizeHint()) + QSize(item.geometry().x(), item.geometry().y())
+ # size = size.expandedTo(item.sizeHint()) + QSize(item.geometry().x(), item.geometry().y())
left, top, right, bottom = self.getContentsMargins()
size += QtCore.QSize(left + right, top + bottom)
size += QSize(0, lineheight * self._row)
@@ -5428,7 +5495,6 @@ def doLayout(self, rect, testonly):
y = effective.y()
lineheight = 0
-
# visible_count = len(self._items)
# invisible_count = 0
@@ -5441,7 +5507,7 @@ def doLayout(self, rect, testonly):
for item in self._items:
widget = item.widget()
if not widget.isVisible():
- #invisible_count+=1
+ # invisible_count+=1
continue
# if hasattr(widget,"display_name"):
# print (f"layout: {str(widget.display_name())}")
@@ -5449,14 +5515,18 @@ def doLayout(self, rect, testonly):
if hspace == -1:
hspace = widget.style().layoutSpacing(
QtWidgets.QSizePolicy.PushButton,
- QtWidgets.QSizePolicy.PushButton, QtCore.Qt.Horizontal)
+ QtWidgets.QSizePolicy.PushButton,
+ QtCore.Qt.Horizontal,
+ )
vspace = self.verticalSpacing()
if vspace == -1:
vspace = widget.style().layoutSpacing(
QtWidgets.QSizePolicy.PushButton,
- QtWidgets.QSizePolicy.PushButton, QtCore.Qt.Vertical)
+ QtWidgets.QSizePolicy.PushButton,
+ QtCore.Qt.Vertical,
+ )
item_w = item.sizeHint().width() + hspace
- max_w = max(max_w,item_w)
+ max_w = max(max_w, item_w)
lineheight = max(lineheight, item.sizeHint().height())
# compute columns
@@ -5480,10 +5550,9 @@ def doLayout(self, rect, testonly):
x = pos_x[col]
if not testonly:
- item.setGeometry(
- QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint()))
+ item.setGeometry(QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint()))
# print (f"flow [{index}] position {x} {y}")
- index+=1
+ index += 1
col += 1
if col == max_col:
@@ -5494,25 +5563,28 @@ def doLayout(self, rect, testonly):
self._row = row
self._col = max_col
-
- #print (f"layout visible: {visible_count} invisible: {invisible_count}")
+ # print (f"layout visible: {visible_count} invisible: {invisible_count}")
return y + lineheight - rect.y() + bottom
else:
- item : QtWidgets.QWidgetItem
+ item: QtWidgets.QWidgetItem
for item in self._items:
widget = item.widget()
hspace = self.horizontalSpacing()
if hspace == -1:
hspace = widget.style().layoutSpacing(
QtWidgets.QSizePolicy.PushButton,
- QtWidgets.QSizePolicy.PushButton, QtCore.Qt.Horizontal)
+ QtWidgets.QSizePolicy.PushButton,
+ QtCore.Qt.Horizontal,
+ )
vspace = self.verticalSpacing()
if vspace == -1:
vspace = widget.style().layoutSpacing(
QtWidgets.QSizePolicy.PushButton,
- QtWidgets.QSizePolicy.PushButton, QtCore.Qt.Vertical)
+ QtWidgets.QSizePolicy.PushButton,
+ QtCore.Qt.Vertical,
+ )
nextX = x + item.sizeHint().width() + hspace
if nextX - hspace > effective.right() and lineheight > 0:
@@ -5522,8 +5594,7 @@ def doLayout(self, rect, testonly):
lineheight = 0
if not testonly:
- item.setGeometry(
- QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint()))
+ item.setGeometry(QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint()))
x = nextX
lineheight = max(lineheight, item.sizeHint().height())
@@ -5539,6 +5610,7 @@ def smartSpacing(self, pm):
else:
return parent.spacing()
+
class QBubble(QtWidgets.QLabel):
def __init__(self, text):
super(QBubble, self).__init__(text)
@@ -5549,20 +5621,14 @@ def paintEvent(self, event):
p = QtGui.QPainter(self)
# p.begin(self)
p.setRenderHint(QtGui.QPainter.Antialiasing, True)
- p.drawRoundedRect(
- 0, 0, self.width() - 1, self.height() - 1, 5, 5)
+ p.drawRoundedRect(0, 0, self.width() - 1, self.height() - 1, 5, 5)
super(QBubble, self).paintEvent(event)
p.end()
-
-
class ActionLabel(QtWidgets.QLabel):
-
"""Handles showing the correct icon for the given action. This control is used to display action icons in the input item."""
-
-
def __init__(self, action_entry, parent=None):
"""Creates a new label for the given entry.
@@ -5594,8 +5660,9 @@ def __init__(self, action_entry, parent=None):
# el.icon_changed.connect(self._icon_change)
background_color = Color.actionIconBackgroundColor()
border_color = Color.keyBorderColor()
- self.setStyleSheet(f"QLabel {{ border: 1px solid {border_color}; border-radius: 4px; padding: 1px; background-color: {background_color}; }}")
-
+ self.setStyleSheet(
+ f"QLabel {{ border: 1px solid {border_color}; border-radius: 4px; padding: 1px; background-color: {background_color}; }}"
+ )
def _icon_change(self, event):
icon = self.action_entry.icon()
@@ -5607,12 +5674,12 @@ def _icon_change(self, event):
self.setPixmap(QtGui.QPixmap(icon))
-
class QContentWidget(QtWidgets.QWidget):
- ''' a widget that fires a resize event when its size changes '''
+ """a widget that fires a resize event when its size changes"""
resized = QtCore.Signal(QtCore.QSize)
- def __init__(self, parent = None):
+
+ def __init__(self, parent=None):
super().__init__(parent)
def resizeEvent(self, event):
@@ -5620,67 +5687,67 @@ def resizeEvent(self, event):
return super().resizeEvent(event)
-
-
class QSplitTabWidget(QDataWidget):
- ''' tab content widgeth split '''
- def __init__(self, parent = None):
+ """tab content widgeth split"""
+
+ def __init__(self, parent=None):
super().__init__(parent)
self._lock = False
self.main_layout = QtWidgets.QVBoxLayout(self)
- self.main_layout.setContentsMargins(0,0,0,0)
- self.setContentsMargins(0,0,0,0)
+ self.main_layout.setContentsMargins(0, 0, 0, 0)
+ self.setContentsMargins(0, 0, 0, 0)
self._content_widget = QContentWidget()
self._content_widget.resized.connect(self._content_resized)
- self._content_widget.setContentsMargins(0,0,0,0)
-
- self._splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal, self._content_widget)
+ self._content_widget.setContentsMargins(0, 0, 0, 0)
+ self._splitter = QtWidgets.QSplitter(
+ QtCore.Qt.Orientation.Horizontal, self._content_widget
+ )
self._left_panel_widget = QtWidgets.QWidget()
- #self._left_panel_widget.setStyleSheet("background: green")
- self._left_panel_widget.setContentsMargins(0,0,0,0)
+ # self._left_panel_widget.setStyleSheet("background: green")
+ self._left_panel_widget.setContentsMargins(0, 0, 0, 0)
self._left_panel_widget.setMinimumWidth(200)
self._right_panel_widget = QtWidgets.QWidget()
- #self._right_panel_widget.setStyleSheet("background: blue")
- self._right_panel_widget.setContentsMargins(0,0,0,0)
+ # self._right_panel_widget.setStyleSheet("background: blue")
+ self._right_panel_widget.setContentsMargins(0, 0, 0, 0)
self._left_panel_layout = QtWidgets.QVBoxLayout(self._left_panel_widget)
- self._left_panel_layout.setContentsMargins(0,0,0,0)
+ self._left_panel_layout.setContentsMargins(0, 0, 0, 0)
self._right_panel_layout = QtWidgets.QVBoxLayout(self._right_panel_widget)
- self._right_panel_layout.setContentsMargins(0,0,0,0)
-
-
+ self._right_panel_layout.setContentsMargins(0, 0, 0, 0)
# left panel, list view on top, buttons on bottom
self._left_container_widget = QtWidgets.QWidget()
- self._left_container_widget.setContentsMargins(0,0,0,0)
+ self._left_container_widget.setContentsMargins(0, 0, 0, 0)
self._left_container_layout = QtWidgets.QVBoxLayout(self._left_container_widget)
- self._left_container_layout.setContentsMargins(0,0,0,0)
+ self._left_container_layout.setContentsMargins(0, 0, 0, 0)
# right panel
self._right_container_widget = QtWidgets.QWidget()
- self._right_container_widget.setContentsMargins(0,0,0,0)
- self._right_container_layout = QtWidgets.QVBoxLayout(self._right_container_widget)
- self._right_container_layout.setContentsMargins(0,0,0,0)
-
+ self._right_container_widget.setContentsMargins(0, 0, 0, 0)
+ self._right_container_layout = QtWidgets.QVBoxLayout(
+ self._right_container_widget
+ )
+ self._right_container_layout.setContentsMargins(0, 0, 0, 0)
+
# place items in left_container_layout or right_container_layout
self._left_panel_layout.addWidget(self._left_container_widget)
self._right_panel_layout.addWidget(self._right_container_widget)
self._splitter.addWidget(self._left_panel_widget)
self._splitter.addWidget(self._right_panel_widget)
- self._splitter.setStretchFactor(0,1)
- self._splitter.setStretchFactor(1,3)
+ self._splitter.setStretchFactor(0, 1)
+ self._splitter.setStretchFactor(1, 3)
width = self.frameGeometry().width()
w1 = width // 5
- self._splitter.setSizes((w1, w1*4))
+ self._splitter.setSizes((w1, w1 * 4))
self._splitter.setCollapsible(0, False)
self._splitter.setCollapsible(1, False)
@@ -5689,24 +5756,22 @@ def __init__(self, parent = None):
_tabsplitter_tracker.registerWidget(self)
def _cleanup_ui(self):
- ''' remove '''
+ """remove"""
if not self._lock:
self._lock = True
_tabsplitter_tracker.unregisterWidget(self)
self._lock = False
def _select_item_cb(self, index):
- assert False,"Must be implemented by subclass"
+ assert False, "Must be implemented by subclass"
def select_item(self, index):
# implemented by a subclass
self._select_item_cb(index)
-
-
@QtCore.Slot(QtCore.QSize)
- def _content_resized(self, size : QtCore.QSize):
- ''' called when the container object is resized '''
+ def _content_resized(self, size: QtCore.QSize):
+ """called when the container object is resized"""
# resize the splitter to the container's size as it doesn't happen by itself for some reason
width = self._content_widget.frameGeometry().width()
@@ -5715,80 +5780,81 @@ def _content_resized(self, size : QtCore.QSize):
self._splitter.setFixedWidth(width)
self._splitter.setFixedHeight(height)
-
- def setLeftPanelWidget(self, widget : QtWidgets.QWidget):
- ''' sets the left panel widget '''
+ def setLeftPanelWidget(self, widget: QtWidgets.QWidget):
+ """sets the left panel widget"""
gremlin.util.clear_layout(self._left_container_layout)
if widget is not None:
self._left_container_layout.addWidget(widget)
- def addLeftPanelWidget(self, widget : QtWidgets.QWidget):
- ''' sets the left panel widget '''
+ def addLeftPanelWidget(self, widget: QtWidgets.QWidget):
+ """sets the left panel widget"""
if widget is not None:
self._left_container_layout.addWidget(widget)
- def setRightPanelWidget(self, widget : QtWidgets.QWidget):
- ''' sets the right panel widget '''
- #print ("set right panel")
+ def setRightPanelWidget(self, widget: QtWidgets.QWidget):
+ """sets the right panel widget"""
+ # print ("set right panel")
widgets = gremlin.util.get_layout_widgets(self._right_container_layout)
if widget in widgets:
return
self.clearRightPanel()
self.addRightPanelWidget(widget)
-
- def addRightPanelWidget(self, widget : QtWidgets.QWidget):
- ''' sets the left panel widget '''
- #print ("add right panel")
+ def addRightPanelWidget(self, widget: QtWidgets.QWidget):
+ """sets the left panel widget"""
+ # print ("add right panel")
if widget is not None:
self._right_container_layout.addWidget(widget)
- def removeRightPanelWidget(self, widget : QtWidgets.QWidget):
- ''' removes a widget from the right panel '''
+ def removeRightPanelWidget(self, widget: QtWidgets.QWidget):
+ """removes a widget from the right panel"""
widgets = gremlin.util.get_layout_widgets(self._right_container_layout)
if widget and widget in widgets:
self._right_container_layout.removeWidget(widget)
def clearLeftPanel(self):
- ''' removes all widgets from the left panel '''
+ """removes all widgets from the left panel"""
gremlin.util.clear_layout(self._left_container_layout)
def clearRightPanel(self):
- ''' removes all widgets from the right panel '''
- #print ("clear right panel")
+ """removes all widgets from the right panel"""
+ # print ("clear right panel")
gremlin.util.clear_layout(self._right_container_layout)
def getRightPanelWidgets(self):
- ''' gets the widgets in the right panel'''
+ """gets the widgets in the right panel"""
return gremlin.util.get_layout_widgets(self._right_container_layout)
def hasRightContent(self):
- ''' true if the widget has contents on the right '''
+ """true if the widget has contents on the right"""
widgets = gremlin.util.get_layout_widgets(self._right_container_layout)
return len(widgets) > 0
+
class QRememberDialog(QtWidgets.QDialog):
- ''' a dialog window that remembers its size and location '''
+ """a dialog window that remembers its size and location"""
- def __init__(self, key: str, parent = None):
+ def __init__(self, key: str, parent=None):
super().__init__(parent)
self._resize_count = 0
- assert key,"unique key must be provided"
+ assert key, "unique key must be provided"
self.window_key = key
self.apply_window_settings()
-
-
def getResizable(self) -> bool:
return self._resizable
+
def setResizable(self, value: bool):
self._resizable = value
if value:
- self.layout().setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetNoConstraint)
+ self.layout().setSizeConstraint(
+ QtWidgets.QLayout.SizeConstraint.SetNoConstraint
+ )
else:
- self.layout().setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetFixedSize)
-
+ self.layout().setSizeConstraint(
+ QtWidgets.QLayout.SizeConstraint.SetFixedSize
+ )
def apply_window_settings(self):
"""Restores the stored window geometry settings."""
@@ -5816,21 +5882,21 @@ def resizeEvent(self, evt):
"""
if self._resize_count > 1:
config = gremlin.config.Configuration()
- config.setWindowSize(self.window_key, evt.size().width(), evt.size().height())
+ config.setWindowSize(
+ self.window_key, evt.size().width(), evt.size().height()
+ )
self._resize_count += 1
super().resizeEvent(evt)
-
-
-
class MarkdownDialog(QRememberDialog):
- '''
+ """
Dialog box for instructions in markdown format
- '''
- def __init__(self, title = "Markdown Instructions", source = None, parent = None):
- super().__init__(self.__class__.__name__, parent = parent)
+ """
+
+ def __init__(self, title="Markdown Instructions", source=None, parent=None):
+ super().__init__(self.__class__.__name__, parent=parent)
self.setWindowTitle(title)
self.setWindowModality(QtCore.Qt.ApplicationModal)
self._view = QtWidgets.QTextEdit()
@@ -5839,9 +5905,8 @@ def __init__(self, title = "Markdown Instructions", source = None, parent = None
if source is not None:
self.load(source)
-
- def load(self, source : str):
- ''' loads a source '''
+ def load(self, source: str):
+ """loads a source"""
if source is not None and os.path.isfile(source):
location = source
else:
@@ -5849,17 +5914,14 @@ def load(self, source : str):
if location is not None and os.path.isfile(location):
syslog.info(f"dialog: found file : {location}")
self._source = location
- with open(location,"+rt") as f:
+ with open(location, "+rt") as f:
md = f.read()
self._view.setMarkdown(md)
return True
return False
-
-
class BaseDialogUi(QRememberDialog):
-
"""Base class for all UI dialogs.
The main purpose of this class is to provide the closed signal to dialogs
@@ -5875,7 +5937,7 @@ def __init__(self, key, parent=None):
:param parent the parent of this widget
"""
- super().__init__(key, parent = parent)
+ super().__init__(key, parent=parent)
def closeEvent(self, event):
"""Closes the calibration window.
@@ -5887,30 +5949,34 @@ def closeEvent(self, event):
if event.isAccepted():
self.closed.emit()
+
class QDataTab(QtWidgets.QTabWidget):
- ''' tab header with a data field '''
- def __init__(self, data = None, parent = None):
+ """tab header with a data field"""
+
+ def __init__(self, data=None, parent=None):
super().__init__(parent)
self._data = data
-
@property
def data(self):
return self._data
+
@data.setter
def data(self, value):
self._data = value
-
-
class QTabHeader(QtWidgets.QTabBar):
- ''' wrapper for tab bar to catch mouse events on tab bar '''
+ """wrapper for tab bar to catch mouse events on tab bar"""
- tabMoveCompleted = QtCore.Signal(int, int) # triggers once a tab moved has been completed
- tabChanged = QtCore.Signal(int) # triggers when a tab is selected, aware of tab drag ops
+ tabMoveCompleted = QtCore.Signal(
+ int, int
+ ) # triggers once a tab moved has been completed
+ tabChanged = QtCore.Signal(
+ int
+ ) # triggers when a tab is selected, aware of tab drag ops
- def __init__(self, parent = None):
+ def __init__(self, parent=None):
super().__init__(parent)
self.installEventFilter(self)
@@ -5920,12 +5986,12 @@ def __init__(self, parent = None):
self._mouse_down_index = None
self._move_in_progress = False
self.tabMoved.connect(self._tab_moved)
- #self.currentChanged.connect(self._tab_selected)
+ # self.currentChanged.connect(self._tab_selected)
@property
def moveInProgress(self) -> bool:
return self._move_in_progress or self._mouse_down
-
+
@QtCore.Slot(int)
def _tab_selected(self, index):
# print (f"internal tab selected {index}")
@@ -5940,13 +6006,12 @@ def _tab_moved(self, from_index, to_index):
self._to_index = to_index
# print (f"internal tab move {from_index} {to_index}")
-
def eventFilter(self, widget, event):
t = event.type()
if t == QtCore.QEvent.Type.MouseButtonPress:
self._mouse_down = True
self._mouse_down_index = self.currentIndex()
- #print (f"mouse down - {self._mouse_down_index}")
+ # print (f"mouse down - {self._mouse_down_index}")
elif t == QtCore.QEvent.Type.MouseButtonRelease:
self._mouse_down = False
index = self.currentIndex()
@@ -5958,24 +6023,27 @@ def eventFilter(self, widget, event):
elif index != self._mouse_down_index:
# fire the tab change on release if there is a tab change
self.tabChanged.emit(index)
-
- return False # allow further processing
-
-def getRadioContainer(label_data_pairs, callback, default = None, horizontal = True, label = None, parent = None):
- ''' returns an H container for radio buttons
+
+ return False # allow further processing
+
+
+def getRadioContainer(
+ label_data_pairs, callback, default=None, horizontal=True, label=None, parent=None
+):
+ """returns an H container for radio buttons
:param label_data_pairs: list of tuples of (label, data, [tooltip]) for each radio button to create - tooltip is optional
- :param callback: the callback for each radio button - the data component will indicate which radio button was selected
+ :param callback: the callback for each radio button - the data component will indicate which radio button was selected
:param default: the default value to select, set to None to not select anything, if the default doesn't exist, nothing is selected
:param label: label text to add, if any
:param horizontal: creates an H container, if false, creates a V container
- '''
+ """
widget = QtWidgets.QWidget(parent=parent)
if horizontal:
layout = QtWidgets.QHBoxLayout(widget)
else:
layout = QtWidgets.QVBoxLayout(widget)
- widget.setContentsMargins(0,0,0,0)
- layout.setContentsMargins(0,0,0,0)
+ widget.setContentsMargins(0, 0, 0, 0)
+ layout.setContentsMargins(0, 0, 0, 0)
if label:
layout.addWidget(QtWidgets.QLabel(label))
for data in label_data_pairs:
@@ -5985,7 +6053,7 @@ def getRadioContainer(label_data_pairs, callback, default = None, horizontal = T
elif len(data) == 3:
text, data, tooltip = data
else:
- continue # malformed
+ continue # malformed
rb = QDataRadioButton(text, data)
if tooltip:
rb.setToolTip(tooltip)
@@ -5996,26 +6064,26 @@ def getRadioContainer(label_data_pairs, callback, default = None, horizontal = T
layout.addStretch()
return (widget, layout)
-
-def getHContainer(widget_or_list = None, label = None, parent = None, left_stretch = False):
- ''' gets a qt H container widget
-
+
+def getHContainer(widget_or_list=None, label=None, parent=None, left_stretch=False):
+ """gets a qt H container widget
+
:param widget_or_list: list of widgets, or a single widget to add to the container
:param label: label to add to the container (appears first if provided)
:param parent: parent widget if any
:param left_stretch: adds the stretch at the start of the container to right align it on the row
-
- '''
+
+ """
widget = QtWidgets.QWidget(parent=parent)
layout = QtWidgets.QHBoxLayout(widget)
- widget.setContentsMargins(0,0,0,0)
- layout.setContentsMargins(0,0,0,0)
+ widget.setContentsMargins(0, 0, 0, 0)
+ layout.setContentsMargins(0, 0, 0, 0)
stretch = left_stretch
if label:
layout.addWidget(QtWidgets.QLabel(label))
stretch = True
if widget_or_list:
- if isinstance(widget_or_list, list) or isinstance(widget_or_list, tuple):
+ if isinstance(widget_or_list, list) or isinstance(widget_or_list, tuple):
for item in widget_or_list:
layout.addWidget(item)
else:
@@ -6027,23 +6095,23 @@ def getHContainer(widget_or_list = None, label = None, parent = None, left_stret
else:
layout.addStretch()
return (widget, layout)
-
-def getVContainer(widget_or_list = None, label = None, alignment = None, parent = None):
- ''' gets a qt H container widget '''
+
+def getVContainer(widget_or_list=None, label=None, alignment=None, parent=None):
+ """gets a qt H container widget"""
widget = QtWidgets.QWidget(parent=parent)
layout = QtWidgets.QVBoxLayout(widget)
- widget.setContentsMargins(0,0,0,0)
- layout.setContentsMargins(0,0,0,0)
+ widget.setContentsMargins(0, 0, 0, 0)
+ layout.setContentsMargins(0, 0, 0, 0)
if alignment is not None:
layout.setAlignment(widget, alignment)
stretch = False
if label:
layout.addWidget(QtWidgets.QLabel(label))
-
+
stretch = True
if widget_or_list:
- if isinstance(widget_or_list, list) or isinstance(widget_or_list, tuple):
+ if isinstance(widget_or_list, list) or isinstance(widget_or_list, tuple):
for item in widget_or_list:
layout.addWidget(item)
else:
@@ -6053,43 +6121,49 @@ def getVContainer(widget_or_list = None, label = None, alignment = None, parent
layout.addStretch()
return (widget, layout)
-def getGridContainer(widget_or_list = None, alignment = QtCore.Qt.AlignmentFlag.AlignLeft, start_col = 0, start_row = None, stretch_col = None, add_to_widget = None ):
- ''' gets a qt grid container widget
-
+
+def getGridContainer(
+ widget_or_list=None,
+ alignment=QtCore.Qt.AlignmentFlag.AlignLeft,
+ start_col=0,
+ start_row=None,
+ stretch_col=None,
+ add_to_widget=None,
+):
+ """gets a qt grid container widget
+
:param widget_or_list: the widget or widgets to add to the next row
:param alignment: cell alignment
:param start_col: starting column where to add the new widget, starting from the left column
:param start_row: starting row where to add the new widgets
:param add_to_widget: add widgets to an existing grid widget
-
- '''
+
+ """
if add_to_widget is not None:
widget = add_to_widget
- layout : QtWidgets.QGridLayout = widget.layout()
+ layout: QtWidgets.QGridLayout = widget.layout()
row = layout.rowCount() if start_row is None else start_row
stretch = False
else:
widget = QtWidgets.QWidget()
layout = QtWidgets.QGridLayout(widget)
- widget.setContentsMargins(0,0,0,0)
- layout.setContentsMargins(0,0,0,0)
+ widget.setContentsMargins(0, 0, 0, 0)
+ layout.setContentsMargins(0, 0, 0, 0)
row = 0 if start_row is None else start_row
stretch = True
-
col = 0 if start_col is None else start_col
-
-
+
if widget_or_list:
if isinstance(widget_or_list, list) or isinstance(widget_or_list, tuple):
for item in widget_or_list:
layout.addWidget(item, row, col, alignment)
- col+=1
+ col += 1
else:
layout.addWidget(widget_or_list, row, col, alignment)
- col+=1
+ col += 1
if stretch:
if stretch_col is not None:
@@ -6101,23 +6175,23 @@ def getGridContainer(widget_or_list = None, alignment = QtCore.Qt.AlignmentFlag.
return (widget, layout)
-def synchronize_grids(grid_widget_list : list, fill_buttons = True):
- ''' synchronizes cell widths between multiple grid layouts
+def synchronize_grids(grid_widget_list: list, fill_buttons=True):
+ """synchronizes cell widths between multiple grid layouts
:param widget_or_list: the widget or widgets to add to the next row
:param fill_buttons: if set, button widgets fill the column width
-
- '''
+
+ """
if len(grid_widget_list) < 2:
- return # nothing to do
-
+ return # nothing to do
+
g: QtWidgets.QGridLayout
max_cols = 0
layouts = [g.layout() for g in grid_widget_list]
max_cols = max(g.columnCount() for g in layouts)
-
+
for col in range(max_cols):
- width = 0
- widgets = []
+ width = 0
+ widgets = []
for g in layouts:
rows = g.rowCount()
if col < g.columnCount():
@@ -6126,60 +6200,68 @@ def synchronize_grids(grid_widget_list : list, fill_buttons = True):
if widget_item is not None:
widget = widget_item.wid
if fill_buttons:
- if isinstance(widget, QtWidgets.QPushButton) and widget.text():
+ if (
+ isinstance(widget, QtWidgets.QPushButton)
+ and widget.text()
+ ):
# push button with text
widgets.append(widget)
width = max(width, widget_item.minimumSize().width())
for g in layouts:
g.setColumnMinimumWidth(col, width)
- widget : QtWidgets.QWidget
+ widget: QtWidgets.QWidget
for widget in widgets:
widget.setMinimumWidth(width)
-
-
class QJoystickRangeWidget(QtWidgets.QWidget):
- ''' a widget that displays and collects range information for a joytick '''
-
-
- valueChanged = QtCore.Signal(object) # occurs when the data range value changes ((min,max)) or (value) - passes the normalized values or single value
- modeChanged = QtCore.Signal() # occurs if the mode changes from single value to range mode
- rangeChanged = QtCore.Signal(object) # occurs when the range (command) data changes ((min,max)) or (value) - passes the new command data or single value
- invertChanged = QtCore.Signal() # occurs when inversion flag is changed
-
- def __init__(self,
- data = None,
- min_cmd = -1,
- max_cmd = 1,
- min_norm = -1,
- max_norm = 1,
- decimals = 3,
- min_range = -1,
- max_range = 1,
- is_range = True,
- show_mode_change = False,
- show_inverted = True,
- show_command = True,
- inverted = False,
- parent = None):
- '''
+ """a widget that displays and collects range information for a joytick"""
+
+ valueChanged = QtCore.Signal(
+ object
+ ) # occurs when the data range value changes ((min,max)) or (value) - passes the normalized values or single value
+ modeChanged = (
+ QtCore.Signal()
+ ) # occurs if the mode changes from single value to range mode
+ rangeChanged = QtCore.Signal(
+ object
+ ) # occurs when the range (command) data changes ((min,max)) or (value) - passes the new command data or single value
+ invertChanged = QtCore.Signal() # occurs when inversion flag is changed
+
+ def __init__(
+ self,
+ data=None,
+ min_cmd=-1,
+ max_cmd=1,
+ min_norm=-1,
+ max_norm=1,
+ decimals=3,
+ min_range=-1,
+ max_range=1,
+ is_range=True,
+ show_mode_change=False,
+ show_inverted=True,
+ show_command=True,
+ inverted=False,
+ parent=None,
+ ):
+ """
:param data: the data object if any
:param min_range: the default min range of the widget
:param max_range: the default max range of the widget
- :param decimals: the number of decimal places to display
+ :param decimals: the number of decimal places to display
:param is_range: if set, the widget displays a min/max range, if false displays a single value range
- '''
+ """
super().__init__(parent)
-
+
self.data = data
- assert gremlin.util.valueInRange(min_norm,-1,1)
- assert gremlin.util.valueInRange(max_norm,-1,1)
+ assert gremlin.util.valueInRange(min_norm, -1, 1)
+ assert gremlin.util.valueInRange(max_norm, -1, 1)
- self._min_range = min_range # min possible input range
- self._max_range = max_range # max possible input range
+ self._min_range = min_range # min possible input range
+ self._max_range = max_range # max possible input range
self._showCommandRange = True
self._showNormalizedRange = True
self._showPercentRange = True
@@ -6199,16 +6281,16 @@ def __init__(self,
self._last_min = min_cmd
self._last_max = max_cmd
-
main_layout = QtWidgets.QVBoxLayout(self)
- w = gremlin.shared_state.char_width * 12 # gremlin.ui.ui_common.get_text_width("0000000.0000")
+ w = (
+ gremlin.shared_state.char_width * 12
+ ) # gremlin.ui.ui_common.get_text_width("0000000.0000")
- output_data_entry_widget = QtWidgets.QWidget()
- output_data_entry_layout = QtWidgets.QGridLayout(output_data_entry_widget)
+ QtWidgets.QWidget()
+ # output_data_entry_layout = QtWidgets.QGridLayout(output_data_entry_widget)
-
- # output range
+ # output range
self._command_min_widget = QFloatLineEdit()
self._command_min_widget.setRange(min_range, max_range)
self._command_min_widget.setValue(min_cmd)
@@ -6216,20 +6298,27 @@ def __init__(self,
self._command_min_widget.setMinimumWidth(w)
# output value
- min_output = gremlin.util.scale_to_range(min_norm, target_min = min_cmd, target_max = max_cmd)
- max_output = gremlin.util.scale_to_range(max_norm, target_min = min_cmd, target_max = max_cmd)
+ min_output = gremlin.util.scale_to_range(
+ min_norm, target_min=min_cmd, target_max=max_cmd
+ )
+ max_output = gremlin.util.scale_to_range(
+ max_norm, target_min=min_cmd, target_max=max_cmd
+ )
# output percentage
- min_percent = gremlin.util.scale_to_range(min_norm, target_min = 0, target_max = 100)
- max_percent = gremlin.util.scale_to_range(max_norm, target_min = 0, target_max = 100)
+ min_percent = gremlin.util.scale_to_range(
+ min_norm, target_min=0, target_max=100
+ )
+ max_percent = gremlin.util.scale_to_range(
+ max_norm, target_min=0, target_max=100
+ )
- # output range
+ # output range
self._command_max_widget = QFloatLineEdit()
self._command_max_widget.setRange(min_range, max_range)
self._command_max_widget.setValue(max_cmd)
self._command_max_widget.setMinimumWidth(w)
self._command_max_widget.valueChanged.connect(self._update_from_command)
-
# output min
self._data_min_widget = QFloatLineEdit()
@@ -6245,42 +6334,33 @@ def __init__(self,
self._data_max_widget.setMinimumWidth(w)
self._data_max_widget.valueChanged.connect(self._update_from_output)
-
-
-
# normalized is -1 to + 1
self._normalized_min_widget = gremlin.ui.ui_common.QFloatLineEdit()
- self._normalized_min_widget.setRange(-1,1)
+ self._normalized_min_widget.setRange(-1, 1)
self._normalized_min_widget.setValue(min_norm)
self._normalized_min_widget.setMinimumWidth(w)
self._normalized_min_widget.valueChanged.connect(self._update_from_normalized)
-
-
-
+
self._normalized_max_widget = gremlin.ui.ui_common.QFloatLineEdit()
- self._normalized_max_widget.setRange(-1,1)
+ self._normalized_max_widget.setRange(-1, 1)
self._normalized_max_widget.setValue(max_norm)
self._normalized_max_widget.setMinimumWidth(w)
-
+
self._normalized_max_widget.valueChanged.connect(self._update_from_normalized)
-
-
self._percent_min_widget = gremlin.ui.ui_common.QFloatLineEdit(decimals=2)
- #self._output_min_percent_range_widget.setReadOnly(True)
- self._percent_min_widget.setRange(0,100)
+ # self._output_min_percent_range_widget.setReadOnly(True)
+ self._percent_min_widget.setRange(0, 100)
self._percent_min_widget.setValue(min_percent)
self._percent_min_widget.setMinimumWidth(w)
self._percent_min_widget.valueChanged.connect(self._update_from_percent)
self._percent_max_widget = gremlin.ui.ui_common.QFloatLineEdit(decimals=2)
- #self._output_max_percent_range_widget.setReadOnly(True)
- self._percent_max_widget.setRange(0,100)
+ # self._output_max_percent_range_widget.setReadOnly(True)
+ self._percent_max_widget.setRange(0, 100)
self._percent_max_widget.setValue(max_percent)
self._percent_max_widget.setMinimumWidth(w)
self._percent_max_widget.valueChanged.connect(self._update_from_percent)
-
-
# inverted flag
self._invert_output_widget = QtWidgets.QCheckBox("Invert Output")
@@ -6295,15 +6375,14 @@ def __init__(self,
# single or range mode
widgets = []
- widget = QDataRadioButton("Single Value",data=False)
+ widget = QDataRadioButton("Single Value", data=False)
widget.clicked.connect(self._mode_changed)
widgets.append(widget)
- widget = QDataRadioButton("Range Mode",data=True)
+ widget = QDataRadioButton("Range Mode", data=True)
widget.clicked.connect(self._mode_changed)
widgets.append(widget)
- self._output_mode_widget, _ = getHContainer(widgets,"Output Mode:")
-
+ self._output_mode_widget, _ = getHContainer(widgets, "Output Mode:")
options_layout.addWidget(self._output_mode_widget)
options_layout.addStretch()
@@ -6311,7 +6390,7 @@ def __init__(self,
grids = []
self.grid_header, _ = getGridContainer(
- [
+ [
QtWidgets.QLabel(""),
QtWidgets.QLabel("Min:"),
QtWidgets.QLabel("Max:"),
@@ -6321,7 +6400,7 @@ def __init__(self,
grids.append(self.grid_header)
self.grid_data, _ = getGridContainer(
- [
+ [
QtWidgets.QLabel("Output Value:"),
self._data_min_widget,
self._data_max_widget,
@@ -6331,7 +6410,7 @@ def __init__(self,
grids.append(self.grid_data)
self.grid_normalized, _ = getGridContainer(
- [
+ [
QtWidgets.QLabel("Normalized:"),
self._normalized_min_widget,
self._normalized_max_widget,
@@ -6341,7 +6420,7 @@ def __init__(self,
grids.append(self.grid_normalized)
self.grid_percent, _ = getGridContainer(
- [
+ [
QtWidgets.QLabel("Percent:"),
self._percent_min_widget,
self._percent_max_widget,
@@ -6350,10 +6429,8 @@ def __init__(self,
grids.append(self.grid_percent)
-
-
self.grid_command, _ = getGridContainer(
- [
+ [
QtWidgets.QLabel("Command Range:"),
self._command_min_widget,
self._command_max_widget,
@@ -6362,7 +6439,6 @@ def __init__(self,
grids.append(self.grid_command)
-
for grid in grids:
main_layout.addWidget(grid)
@@ -6380,9 +6456,10 @@ def __init__(self,
self._data_max_widget.setVisible(is_range)
self._invert_output_widget.setVisible(is_range)
-
- if self._verbose: syslog.info(f"JRANGE: init(): output: {min_output:0.3f} {max_output:0.3f} normalized: {min_norm:0.3f} {max_norm:0.3f} percent: {min_percent:0.3f} {max_percent:0.3f} cmd: {min_cmd:0.3f} {max_cmd:0.3f} ")
-
+ if self._verbose:
+ syslog.info(
+ f"JRANGE: init(): output: {min_output:0.3f} {max_output:0.3f} normalized: {min_norm:0.3f} {max_norm:0.3f} percent: {min_percent:0.3f} {max_percent:0.3f} cmd: {min_cmd:0.3f} {max_cmd:0.3f} "
+ )
@QtCore.Slot(bool)
def _inverted_changed(self, checked):
@@ -6391,42 +6468,61 @@ def _inverted_changed(self, checked):
@QtCore.Slot()
def _mode_changed(self):
- ''' called when the mode changes from range (true) to single (false)'''
+ """called when the mode changes from range (true) to single (false)"""
widget = self.sender()
self.isRange = widget.data
is_range = widget.data
self.isRange = is_range
-
-
- def _update_from_normalized(self, emit = True):
- min_cmd = self._command_min_widget.value() # minimum range
+ def _update_from_normalized(self, emit=True):
+ min_cmd = self._command_min_widget.value() # minimum range
if min_cmd is None:
- return # bad data
-
- max_cmd = self._command_max_widget.value() # maximum range
+ return # bad data
+
+ max_cmd = self._command_max_widget.value() # maximum range
if max_cmd is None:
- return # bad data
+ return # bad data
min_norm = self._normalized_min_widget.value()
if min_norm is None:
- return # bad data
+ return # bad data
max_norm = self._normalized_max_widget.value()
if max_norm is None:
- return # bad data
- min_cmd = self._command_min_widget.value() # minimum range
- max_cmd = self._command_max_widget.value() # maximum range
-
- min_value = gremlin.util.scale_to_range(min_norm, source_min = min_norm, source_max = max_norm, target_min=min_cmd, target_max=max_cmd)
- max_value = gremlin.util.scale_to_range(max_norm, source_min = min_norm, source_max = max_norm, target_min=min_cmd, target_max=max_cmd)
-
- if self._last_min != min_value or \
- self._last_max != max_value:
+ return # bad data
+ min_cmd = self._command_min_widget.value() # minimum range
+ max_cmd = self._command_max_widget.value() # maximum range
+
+ min_value = gremlin.util.scale_to_range(
+ min_norm,
+ source_min=min_norm,
+ source_max=max_norm,
+ target_min=min_cmd,
+ target_max=max_cmd,
+ )
+ max_value = gremlin.util.scale_to_range(
+ max_norm,
+ source_min=min_norm,
+ source_max=max_norm,
+ target_min=min_cmd,
+ target_max=max_cmd,
+ )
+ if self._last_min != min_value or self._last_max != max_value:
+ min_percent = gremlin.util.scale_to_range(
+ min_value,
+ source_min=min_cmd,
+ source_max=max_cmd,
+ target_min=0,
+ target_max=100,
+ )
+ max_percent = gremlin.util.scale_to_range(
+ max_value,
+ source_min=min_cmd,
+ source_max=max_cmd,
+ target_min=0,
+ target_max=100,
+ )
- min_percent = gremlin.util.scale_to_range(min_value, source_min=min_cmd, source_max=max_cmd, target_min = 0, target_max = 100)
- max_percent = gremlin.util.scale_to_range(max_value, source_min=min_cmd, source_max=max_cmd, target_min = 0, target_max = 100)
-
self._last_min = min_value
self._last_max = max_value
@@ -6440,8 +6536,10 @@ def _update_from_normalized(self, emit = True):
with QtCore.QSignalBlocker(self._percent_max_widget):
self._percent_max_widget.setValue(max_percent)
-
- if self._verbose: syslog.info(f"JRANGE: update from normalized: output: {min_value:0.3f} {max_value:0.3f} normalized: {min_norm:0.3f} {max_norm:0.3f} percent: {min_percent:0.3f} {max_percent:0.3f} cmd: {min_cmd:0.3f} {max_cmd:0.3f} ")
+ if self._verbose:
+ syslog.info(
+ f"JRANGE: update from normalized: output: {min_value:0.3f} {max_value:0.3f} normalized: {min_norm:0.3f} {max_norm:0.3f} percent: {min_percent:0.3f} {max_percent:0.3f} cmd: {min_cmd:0.3f} {max_cmd:0.3f} "
+ )
if emit:
if self._is_range:
@@ -6449,28 +6547,41 @@ def _update_from_normalized(self, emit = True):
else:
self.valueChanged.emit(min_norm)
-
- def _update_from_percent(self, value : float, emit = True):
+ def _update_from_percent(self, value: float, emit=True):
min_percent = self._percent_min_widget.value()
if min_percent is None:
- return # bad data
+ return # bad data
max_percent = self._percent_max_widget.value()
if max_percent is None:
- return # bad data
- min_cmd = self._command_min_widget.value() # minimum range
+ return # bad data
+ min_cmd = self._command_min_widget.value() # minimum range
if min_cmd is None:
- return # bad data
- max_cmd = self._command_max_widget.value() # maximum range
+ return # bad data
+ max_cmd = self._command_max_widget.value() # maximum range
if max_cmd is None:
- return # bad data
- min_value = gremlin.util.scale_to_range(min_percent, source_min = 0, source_max = 100, target_min=min_cmd, target_max=max_cmd)
- max_value = gremlin.util.scale_to_range(max_percent, source_min = 0, source_max = 100, target_min=min_cmd, target_max=max_cmd)
- min_norm = gremlin.util.scale_to_range(min_value, source_min=min_cmd, source_max=max_cmd) # to -1, 1
- max_norm = gremlin.util.scale_to_range(max_value, source_min=min_cmd, source_max=max_cmd) # to -1, 1
-
- if self._last_min != min_value or \
- self._last_max != max_value:
-
+ return # bad data
+ min_value = gremlin.util.scale_to_range(
+ min_percent,
+ source_min=0,
+ source_max=100,
+ target_min=min_cmd,
+ target_max=max_cmd,
+ )
+ max_value = gremlin.util.scale_to_range(
+ max_percent,
+ source_min=0,
+ source_max=100,
+ target_min=min_cmd,
+ target_max=max_cmd,
+ )
+ min_norm = gremlin.util.scale_to_range(
+ min_value, source_min=min_cmd, source_max=max_cmd
+ ) # to -1, 1
+ max_norm = gremlin.util.scale_to_range(
+ max_value, source_min=min_cmd, source_max=max_cmd
+ ) # to -1, 1
+
+ if self._last_min != min_value or self._last_max != max_value:
self._last_min = min_value
self._last_max = max_value
@@ -6486,7 +6597,10 @@ def _update_from_percent(self, value : float, emit = True):
pass
self._normalized_max_widget.setValue(max_norm)
- if self._verbose: syslog.info(f"JRANGE: update from percent: output: {min_value:0.3f} {max_value:0.3f} normalized: {min_norm:0.3f} {max_norm:0.3f} percent: {min_percent:0.3f} {max_percent:0.3f} cmd: {min_cmd:0.3f} {max_cmd:0.3f} ")
+ if self._verbose:
+ syslog.info(
+ f"JRANGE: update from percent: output: {min_value:0.3f} {max_value:0.3f} normalized: {min_norm:0.3f} {max_norm:0.3f} percent: {min_percent:0.3f} {max_percent:0.3f} cmd: {min_cmd:0.3f} {max_cmd:0.3f} "
+ )
if emit:
if self._is_range:
@@ -6494,46 +6608,61 @@ def _update_from_percent(self, value : float, emit = True):
else:
self.valueChanged.emit(min_norm)
-
- def _update_from_output(self, value, emit = True):
- min_cmd = self._command_min_widget.value() # minimum range
+ def _update_from_output(self, value, emit=True):
+ min_cmd = self._command_min_widget.value() # minimum range
if min_cmd is None:
- return # bad data
-
- max_cmd = self._command_max_widget.value() # maximum range
+ return # bad data
+
+ max_cmd = self._command_max_widget.value() # maximum range
if max_cmd is None:
- return # bad data
+ return # bad data
min_norm = self._normalized_min_widget.value()
if min_norm is None:
- return # bad data
+ return # bad data
max_norm = self._normalized_max_widget.value()
if max_norm is None:
- return # bad data
- min_cmd = self._command_min_widget.value() # minimum range
- max_cmd = self._command_max_widget.value() # maximum range
+ return # bad data
+ min_cmd = self._command_min_widget.value() # minimum range
+ max_cmd = self._command_max_widget.value() # maximum range
min_source = self._data_min_widget.value()
if min_source is None:
- return # bad data
+ return # bad data
max_source = self._data_max_widget.value()
if max_source is None:
- return # bad data
-
+ return # bad data
+
min_range = self._min_range
max_range = self._max_range
min_value = gremlin.util.clamp(min_source, min_range, max_range)
max_value = gremlin.util.clamp(max_source, min_range, max_range)
- min_norm = gremlin.util.scale_to_range(min_value, source_min=min_cmd, source_max=max_cmd) # to -1, 1
- max_norm = gremlin.util.scale_to_range(max_value, source_min=min_cmd, source_max=max_cmd) # to -1, 1
- min_percent = gremlin.util.scale_to_range(min_norm, source_min=min_norm, source_max=max_norm, target_min = 0, target_max = 100)
- max_percent = gremlin.util.scale_to_range(max_norm, source_min=min_norm, source_max=max_norm, target_min = 0, target_max = 100)
+ min_norm = gremlin.util.scale_to_range(
+ min_value, source_min=min_cmd, source_max=max_cmd
+ ) # to -1, 1
+ max_norm = gremlin.util.scale_to_range(
+ max_value, source_min=min_cmd, source_max=max_cmd
+ ) # to -1, 1
+ min_percent = gremlin.util.scale_to_range(
+ min_norm,
+ source_min=min_norm,
+ source_max=max_norm,
+ target_min=0,
+ target_max=100,
+ )
+ max_percent = gremlin.util.scale_to_range(
+ max_norm,
+ source_min=min_norm,
+ source_max=max_norm,
+ target_min=0,
+ target_max=100,
+ )
self._last_cmd_min = min_cmd
self._last_cmd_max = max_cmd
-
+
self._last_min = min_value
self._last_max = max_value
@@ -6547,42 +6676,52 @@ def _update_from_output(self, value, emit = True):
with QtCore.QSignalBlocker(self._percent_max_widget):
self._percent_max_widget.setValue(max_percent)
- if self._verbose: syslog.info(f"JRANGE: update from output: output: {min_value:0.3f} {max_value:0.3f} normalized: {min_norm:0.3f} {max_norm:0.3f} percent: {min_percent:0.3f} {max_percent:0.3f} cmd: {min_cmd:0.3f} {max_cmd:0.3f} ")
+ if self._verbose:
+ syslog.info(
+ f"JRANGE: update from output: output: {min_value:0.3f} {max_value:0.3f} normalized: {min_norm:0.3f} {max_norm:0.3f} percent: {min_percent:0.3f} {max_percent:0.3f} cmd: {min_cmd:0.3f} {max_cmd:0.3f} "
+ )
if emit:
if self._is_range:
self.valueChanged.emit((min_norm, max_norm))
else:
self.valueChanged.emit(min_norm)
-
- def _update_from_command(self, value, emit = True):
-
-
- min_cmd = self._command_min_widget.value() # minimum range
+ def _update_from_command(self, value, emit=True):
+ min_cmd = self._command_min_widget.value() # minimum range
if min_cmd is None:
- return # bad data
-
- max_cmd = self._command_max_widget.value() # maximum range
+ return # bad data
+
+ max_cmd = self._command_max_widget.value() # maximum range
if max_cmd is None:
- return # bad data
+ return # bad data
min_norm = self._normalized_min_widget.value()
if min_norm is None:
- return # bad data
+ return # bad data
max_norm = self._normalized_max_widget.value()
if max_norm is None:
- return # bad data
-
- if self._last_cmd_min != min_cmd or \
- self._last_cmd_max != max_cmd:
-
- min_value = gremlin.util.scale_to_range(min_norm, source_min = min_norm, source_max = max_norm, target_min= min_cmd, target_max = max_cmd)
- max_value = gremlin.util.scale_to_range(max_norm, source_min = min_norm, source_max = max_norm, target_min= min_cmd, target_max = max_cmd)
+ return # bad data
+
+ if self._last_cmd_min != min_cmd or self._last_cmd_max != max_cmd:
+ min_value = gremlin.util.scale_to_range(
+ min_norm,
+ source_min=min_norm,
+ source_max=max_norm,
+ target_min=min_cmd,
+ target_max=max_cmd,
+ )
+ max_value = gremlin.util.scale_to_range(
+ max_norm,
+ source_min=min_norm,
+ source_max=max_norm,
+ target_min=min_cmd,
+ target_max=max_cmd,
+ )
self._last_cmd_min = min_cmd
self._last_cmd_max = max_cmd
-
+
self._last_min = min_value
self._last_max = max_value
@@ -6591,7 +6730,6 @@ def _update_from_command(self, value, emit = True):
with QtCore.QSignalBlocker(self._data_max_widget):
self._data_max_widget.setValue(max_value)
-
if self._verbose:
min_percent = self._percent_min_widget.value()
max_percent = self._percent_max_widget.value()
@@ -6601,7 +6739,11 @@ def _update_from_command(self, value, emit = True):
if max_percent is None:
max_percent = 0
- syslog.info(f"JRANGE: update from command: output: {min_value:0.3f} {max_value:0.3f} normalized: {min_norm:0.3f} {max_norm:0.3f} percent: {min_percent:0.3f} {max_percent:0.3f} cmd: {min_cmd:0.3f} {max_cmd:0.3f} ")
+ syslog.info(
+ f"JRANGE: update from command: output: {min_value:0.3f} {max_value:0.3f} "
+ f"normalized: {min_norm:0.3f} {max_norm:0.3f} "
+ f"percent: {min_percent:0.3f} {max_percent:0.3f} cmd: {min_cmd:0.3f} {max_cmd:0.3f} "
+ )
if emit:
if self._is_range:
@@ -6611,11 +6753,10 @@ def _update_from_command(self, value, emit = True):
self.rangeChanged.emit(min_cmd)
self.valueChanged.emit(min_norm)
-
-
@property
def min_range(self) -> float:
return self._data_min_widget.value()
+
@min_range.setter
def min_range(self, value: float):
if self._data_min_widget.value() != value:
@@ -6624,15 +6765,16 @@ def min_range(self, value: float):
@property
def max_range(self) -> float:
return self._data_max_widget.value()
+
@max_range.setter
def max_range(self, value: float):
if self._data_max_widget.value() != value:
self._data_max_widget.setValue(value)
-
@property
def min_command(self) -> float:
return self._command_min_widget.value()
+
@min_command.setter
def min_command(self, value: float):
if self._command_min_widget.value() != value:
@@ -6641,29 +6783,33 @@ def min_command(self, value: float):
@property
def max_command(self) -> float:
return self._command_max_widget.value()
+
@max_command.setter
def max_command(self, value: float):
if self._command_max_widget.value() != value:
- self._command_max_widget.setValue(value)
+ self._command_max_widget.setValue(value)
@property
def inverted(self) -> bool:
return self._inverted
+
@inverted.setter
- def inverted(self, value : bool):
+ def inverted(self, value: bool):
if self._inverted != value:
self._inverted = value
self.invertChanged.emit()
with QtCore.QSignalBlocker(self._invert_output_widget):
self._invert_output_widget.setChecked(value)
- def setLimits(self, value : float, max_value : float = None):
- ''' sets the overall max values for command range and output values'''
+ def setLimits(self, value: float, max_value: float = None):
+ """sets the overall max values for command range and output values"""
if value == max_value:
# syslog = logging.getLogger("system")
- syslog.error(f"RANGE WIDGET: cannot set range to the same value: {value:0.3f} - skipping")
+ syslog.error(
+ f"RANGE WIDGET: cannot set range to the same value: {value:0.3f} - skipping"
+ )
return
-
+
self._min_range = value
self._max_range = value
min_value = self._data_min_widget.value()
@@ -6684,59 +6830,62 @@ def setLimits(self, value : float, max_value : float = None):
self._command_max_widget.setValue(value)
self._update_from_normalized()
- def setRange(self, value : float, max_value : float):
- ''' updates the overall command range min and max values '''
-
+ def setRange(self, value: float, max_value: float):
+ """updates the overall command range min and max values"""
+
with QtCore.QSignalBlocker(self._command_min_widget):
self._command_min_widget.setRange(value, max_value)
self._command_min_widget.setValue(value)
-
+
with QtCore.QSignalBlocker(self._data_min_widget):
self._data_min_widget.setRange(value, max_value)
-
+
with QtCore.QSignalBlocker(self._command_max_widget):
self._command_max_widget.setRange(value, max_value)
self._command_max_widget.setValue(max_value)
-
+
with QtCore.QSignalBlocker(self._data_max_widget):
self._data_max_widget.setRange(value, max_value)
self._update_from_command(None, False)
- def setPercent(self, percent : float, max_percent : float = None):
- ''' updates based on percentage'''
- percent = gremlin.util.clamp(percent,0, 100)
+ def setPercent(self, percent: float, max_percent: float = None):
+ """updates based on percentage"""
+ percent = gremlin.util.clamp(percent, 0, 100)
with QtCore.QSignalBlocker(self._percent_min_widget):
self._percent_min_widget.setValue(percent)
- if self._is_range:
- assert max_percent is not None,"Missing max value must be provided in range mode"
- max_percent = gremlin.util.clamp(max_percent,0, 100)
+ if self._is_range:
+ assert max_percent is not None, (
+ "Missing max value must be provided in range mode"
+ )
+ max_percent = gremlin.util.clamp(max_percent, 0, 100)
with QtCore.QSignalBlocker(self._percent_max_widget):
self._percent_max_widget.setValue(max_percent)
self._update_from_percent(None, False)
- def setNormalized(self, norm : float, max_norm : float = None):
- ''' updates range from normalized values (-1 to +1)'''
- norm = gremlin.util.clamp(norm,-1,1)
+ def setNormalized(self, norm: float, max_norm: float = None):
+ """updates range from normalized values (-1 to +1)"""
+ norm = gremlin.util.clamp(norm, -1, 1)
with QtCore.QSignalBlocker(self._normalized_min_widget):
self._normalized_min_widget.setValue(norm)
if self._is_range:
- assert max_norm is not None,"Missing max value must be provided in range mode"
- max_norm = gremlin.util.clamp(max_norm,-1,1)
+ assert max_norm is not None, (
+ "Missing max value must be provided in range mode"
+ )
+ max_norm = gremlin.util.clamp(max_norm, -1, 1)
with QtCore.QSignalBlocker(self._normalized_max_widget):
self._normalized_max_widget.setValue(max_norm)
-
+
self._update_from_normalized(False)
- def setValue(self, value : float, max_value: float = None):
- ''' updates normalized value '''
+ def setValue(self, value: float, max_value: float = None):
+ """updates normalized value"""
self.setNormalized(value, max_value)
-
- def setOutput(self, min_value, max_value = None):
- ''' sets the output range value '''
+ def setOutput(self, min_value, max_value=None):
+ """sets the output range value"""
if self._is_range and max_value is None:
# if the widget is a range value, expecting two data points
@@ -6744,15 +6893,15 @@ def setOutput(self, min_value, max_value = None):
elif not self._is_range and max_value is None:
# not a range item, make max the same as min
max_value = min_value
-
+
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_detailed
min_cmd = self._command_min_widget.value()
if min_cmd is None:
- return # bad data
+ return # bad data
max_cmd = self._command_max_widget.value()
if max_cmd is None:
- return # bad data
+ return # bad data
min_value = gremlin.util.clamp(min_value, min_cmd, max_cmd)
max_value = gremlin.util.clamp(max_value, min_cmd, max_cmd)
@@ -6760,46 +6909,48 @@ def setOutput(self, min_value, max_value = None):
self._data_min_widget.setValue(min_value)
with QtCore.QSignalBlocker(self._data_max_widget):
self._data_max_widget.setValue(max_value)
-
- if verbose: syslog.info(f"Range widget set value: {min_value:0.3f} {max_value:0.3f} commmand: {min_cmd:0.3f} {max_cmd:0.3f}")
+
+ if verbose:
+ syslog.info(
+ f"Range widget set value: {min_value:0.3f} {max_value:0.3f} commmand: {min_cmd:0.3f} {max_cmd:0.3f}"
+ )
self._update_from_output(None, False)
def getNormalized(self) -> tuple | float:
- ''' gets the normalized value '''
+ """gets the normalized value"""
if self._is_range:
return (self._data_min_widget.value(), self._data_max_widget.value())
else:
return self._data_min_widget.value()
-
+
def getValue(self) -> tuple | float:
- ''' returns normalized values -1 to + 1 (min,max)'''
+ """returns normalized values -1 to + 1 (min,max)"""
return self.getNormalized()
-
- def showCommandRange(self, value : bool):
- ''' show/hide the command range '''
+ def showCommandRange(self, value: bool):
+ """show/hide the command range"""
self._showCommandRange = value
self.grid_command.setVisible(value)
header_visible = not value and not self._is_range
self.grid_header.setVisible(header_visible)
-
- def showPercentRange(self, value : bool):
- ''' show/hid the percentage range '''
+
+ def showPercentRange(self, value: bool):
+ """show/hid the percentage range"""
self._showPercentRange = value
self.grid_percent.setVisible(value)
-
- def showNormalizedRange(self, value : bool):
- ''' show/hide the normalized range '''
+
+ def showNormalizedRange(self, value: bool):
+ """show/hide the normalized range"""
self._showNormalizedRange = value
self.grid_normalized.setVisible(value)
- def showDataRange(self, value : bool):
- ''' show/hide the value range '''
+ def showDataRange(self, value: bool):
+ """show/hide the value range"""
self._showDataRange = value
self.grid_data.setVisible(value)
def showModeChange(self, value: bool):
- ''' show/hide mode change radio buttons '''
+ """show/hide mode change radio buttons"""
self._showModeChange = value
self._output_mode_widget.setVisible(value)
@@ -6809,10 +6960,11 @@ def showInverted(self, value: bool):
@property
def isRange(self) -> bool:
- ''' enables single value mode if false or min/max mode if true'''
+ """enables single value mode if false or min/max mode if true"""
return self._is_range
+
@isRange.setter
- def isRange(self, value : bool):
+ def isRange(self, value: bool):
if value != self._is_range:
self._is_range = value
visible = value
@@ -6825,25 +6977,24 @@ def isRange(self, value : bool):
self.modeChanged.emit()
-
-
class QVjoySelector(QtWidgets.QWidget):
- ''' widget to select a vjoy device '''
+ """widget to select a vjoy device"""
- selectionChanged = QtCore.Signal(object, int, InputType, int) # fires when selection changes (device_guid, vjoy_id, input_type, input_id)
+ selectionChanged = QtCore.Signal(
+ object, int, InputType, int
+ ) # fires when selection changes (device_guid, vjoy_id, input_type, input_id)
- def __init__(self, device_label = "Device:", input_label = "Input:", parent = None):
+ def __init__(self, device_label="Device:", input_label="Input:", parent=None):
super().__init__(parent)
-
- self._enable_hats = False # true if hat list enabled
- self._enable_buttons = False # true if button list enabled
+ self._enable_hats = False # true if hat list enabled
+ self._enable_buttons = False # true if button list enabled
self._enable_axis = True # true if axis list enabled
- self._current_device_guid = None # selected device
- self._current_vjoy_id = None # current vjoy #
- self._current_input_id = None # selected input id
- self._current_input_type = None # selected input type
+ self._current_device_guid = None # selected device
+ self._current_vjoy_id = None # current vjoy #
+ self._current_input_id = None # selected input id
+ self._current_input_type = None # selected input type
widget, layout = getVContainer()
self.container_stepped_widget = widget
@@ -6856,13 +7007,13 @@ def __init__(self, device_label = "Device:", input_label = "Input:", parent = No
device_widget = QtWidgets.QWidget()
device_layout = QtWidgets.QGridLayout(device_widget)
- device_layout.addWidget(QtWidgets.QLabel(device_label),0,0)
- device_layout.addWidget(self.selector_device_widget,0,1)
- device_layout.addWidget(listen_widget,0,3)
- device_layout.addWidget(QtWidgets.QLabel(" "),0,4)
- device_layout.addWidget(QtWidgets.QLabel(input_label),1,0)
- device_layout.addWidget(self.selector_input_widget,1,1)
- device_layout.setColumnStretch(4,2)
+ device_layout.addWidget(QtWidgets.QLabel(device_label), 0, 0)
+ device_layout.addWidget(self.selector_device_widget, 0, 1)
+ device_layout.addWidget(listen_widget, 0, 3)
+ device_layout.addWidget(QtWidgets.QLabel(" "), 0, 4)
+ device_layout.addWidget(QtWidgets.QLabel(input_label), 1, 0)
+ device_layout.addWidget(self.selector_input_widget, 1, 1)
+ device_layout.setColumnStretch(4, 2)
self.container_stepped_layout.addWidget(device_widget)
self.container_stepped_layout.addWidget(self.step_value_container_widget)
@@ -6872,26 +7023,24 @@ def __init__(self, device_label = "Device:", input_label = "Input:", parent = No
self.selector_device_widget.currentIndexChanged.connect(self._device_changed_cb)
self.selector_input_widget.currentIndexChanged.connect(self._input_changed_cb)
- self.stepped_device_map = {} # holds the device information keyed by device_id (str)
- self.input_map = {} # holds the list of buttons for the given device by device_id(str)
+ self.stepped_device_map = {} # holds the device information keyed by device_id (str)
+ self.input_map = {} # holds the list of buttons for the given device by device_id(str)
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.addWidget(self.container_stepped_widget)
self._update_devices()
-
-
def _update_devices(self):
- ''' reloads input choices in the selector '''
-
- devices = sorted(gremlin.joystick_handling.vjoy_devices(),key=lambda x: x.name)
-
- device_index = None
- current_index = 0
+ """reloads input choices in the selector"""
+
+ devices = sorted(gremlin.joystick_handling.vjoy_devices(), key=lambda x: x.name)
+
+ # device_index = None
+ # current_index = 0
dev: DeviceSummary
axis_list = {}
- button_list = {}
+ # button_list = {}
hat_list = {}
with QtCore.QSignalBlocker(self.selector_device_widget):
self.selector_device_widget.clear()
@@ -6899,7 +7048,7 @@ def _update_devices(self):
self.stepped_device_map[dev.device_id] = dev
button_list = {}
if self._enable_axis:
- for input_id in range(1, dev.axis_count+1):
+ for input_id in range(1, dev.axis_count + 1):
# if dev.device_guid == self.action_data.hardware_device_guid and \
# input_id == self.action_data.hardware_input_id:
# # skip self as a possible input
@@ -6907,34 +7056,35 @@ def _update_devices(self):
axis_list[input_id] = f"Axis {input_id}"
if self._enable_buttons:
- for input_id in range(1, dev.button_count+1):
+ for input_id in range(1, dev.button_count + 1):
# if dev.device_guid == self.action_data.hardware_device_guid and \
# input_id == self.action_data.hardware_input_id:
# # skip self as a possible input
# continue
button_list[input_id] = f"Button {input_id}"
-
+
if self._enable_hats:
- for input_id in range(1, dev.hat_count+1):
+ for input_id in range(1, dev.hat_count + 1):
# if dev.device_guid == self.action_data.hardware_device_guid and \
# input_id == self.action_data.hardware_input_id:
# # skip self as a possible input
# continue
hat_list[input_id] = f"Hat {input_id}"
-
-
self.input_map[dev.device_id] = {}
self.input_map[dev.device_id][InputType.JoystickAxis] = axis_list
- self.input_map[dev.device_id][InputType.JoystickButton] = button_list
+ self.input_map[dev.device_id][InputType.JoystickButton] = (
+ button_list
+ )
self.input_map[dev.device_id][InputType.JoystickHat] = hat_list
- self.selector_device_widget.addItem(dev.name, (dev.device_id, dev.joystick_id)) # data (device_guid, vjoy_id)
+ self.selector_device_widget.addItem(
+ dev.name, (dev.device_id, dev.joystick_id)
+ ) # data (device_guid, vjoy_id)
self._select_first_device()
-
def _update_inputs(self):
- ''' populates the device input list based on current filters - entry data is (input_type, input_id)'''
+ """populates the device input list based on current filters - entry data is (input_type, input_id)"""
device_guid, vjoy_id = self.selector_device_widget.currentData()
current_index = 0
selected_input_index = None
@@ -6947,67 +7097,76 @@ def _update_inputs(self):
self.selector_input_widget.clear()
first_input_id = None
if self._enable_axis:
- for input_id, input_name in self.input_map[device_guid][InputType.JoystickAxis].items():
- self.selector_input_widget.addItem(input_name, (InputType.JoystickAxis, input_id))
+ for input_id, input_name in self.input_map[device_guid][
+ InputType.JoystickAxis
+ ].items():
+ self.selector_input_widget.addItem(
+ input_name, (InputType.JoystickAxis, input_id)
+ )
if first_input_id is None:
first_input_id = input_id
if selected_input_index is None and input_id == active_input_id:
selected_input_index = current_index
- current_index +=1
+ current_index += 1
if self._enable_buttons:
- for input_id, input_name in self.input_map[device_guid][InputType.JoystickButton].items():
- self.selector_input_widget.addItem(input_name, (InputType.JoystickButton, input_id))
+ for input_id, input_name in self.input_map[device_guid][
+ InputType.JoystickButton
+ ].items():
+ self.selector_input_widget.addItem(
+ input_name, (InputType.JoystickButton, input_id)
+ )
if first_input_id is None:
first_input_id = input_id
if selected_input_index is None and input_id == active_input_id:
selected_input_index = current_index
- current_index +=1
+ current_index += 1
if self._enable_hats:
- for input_id, input_name in self.input_map[device_guid][InputType.JoystickHat].items():
- self.selector_input_widget.addItem(input_name, (InputType.JoystickHat, input_id))
+ for input_id, input_name in self.input_map[device_guid][
+ InputType.JoystickHat
+ ].items():
+ self.selector_input_widget.addItem(
+ input_name, (InputType.JoystickHat, input_id)
+ )
if first_input_id is None:
first_input_id = input_id
if selected_input_index is None and input_id == active_input_id:
selected_input_index = current_index
- current_index +=1
-
+ current_index += 1
self._select_input(active_input_type, active_input_id)
-
-
@property
def input_id(self) -> int:
return self._current_input_id
-
- def setAxisEnabled(self, value:bool):
+
+ def setAxisEnabled(self, value: bool):
if self._enable_axis != value:
self._enable_axis = value
self._update_inputs()
- def setButtonsEnabled(self, value:bool):
+ def setButtonsEnabled(self, value: bool):
if self._enable_buttons != value:
self._enable_buttons = value
self._update_inputs()
-
- def setHatsEnabled(self, value:bool):
+
+ def setHatsEnabled(self, value: bool):
if self._enable_hats != value:
self._enable_hats = value
self._update_inputs()
-
@input_id.setter
- def input_id(self, value : int):
-
+ def input_id(self, value: int):
if value < 1:
syslog.error(f"Invalid input id: {value}")
- return # invalid value
-
+ return # invalid value
+
if value == self._current_input_id:
# nothing to do
return
-
- info = gremlin.joystick_handling.device_info_from_guid(self._current_device_guid)
+
+ info = gremlin.joystick_handling.device_info_from_guid(
+ self._current_device_guid
+ )
if info:
match self._current_input_type:
case InputType.JoystickAxis:
@@ -7023,7 +7182,7 @@ def input_id(self, value : int):
self._select_input(self._current_input_type, self._current_input_id)
def _select_input(self, input_type, input_id):
- ''' selects the entry in the control for the given input type and input ID if it exists'''
+ """selects the entry in the control for the given input type and input ID if it exists"""
key = (input_type, input_id)
index = self.selector_input_widget.findData(key)
@@ -7031,49 +7190,51 @@ def _select_input(self, input_type, input_id):
self.selector_input_widget.setCurrentIndex(index)
return True
return False
-
def _select_first_device(self):
- ''' selects the first device if there is a device to select '''
+ """selects the first device if there is a device to select"""
if self.selector_device_widget.count():
self.selector_device_widget.setCurrentIndex(0)
-
+
def _select_first_input(self):
- ''' selects the first input if there is an input to select '''
+ """selects the first input if there is an input to select"""
if self.selector_input_widget.count():
self.selector_input_widget.setCurrentIndex(0)
-
- def _select_device(self, device_guid, input_type, input_id):
+ def _select_device(self, device_guid, input_type, input_id):
key = device_guid
index = self.selector_device_widget.findData(key)
if index != -1:
self.selector_device_widget.setCurrentIndex(index)
-
@QtCore.Slot()
def _device_changed_cb(self):
- ''' called when device selection changes '''
+ """called when device selection changes"""
device_guid = self.selector_device_widget.currentData()
self._current_device_guid = device_guid
self._select_first_input()
@QtCore.Slot()
def _input_changed_cb(self):
- ''' called when the input selection changed '''
+ """called when the input selection changed"""
input_type, input_id = self.selector_input_widget.currentData()
-
+
self._current_input_id = input_id
self._current_input_type = input_type
- self.selectionChanged.emit(self._current_device_guid, self._current_vjoy_id, input_type, input_id)
+ self.selectionChanged.emit(
+ self._current_device_guid, self._current_vjoy_id, input_type, input_id
+ )
class QPaginator(QtWidgets.QWidget):
- ''' table view that displays paginated data '''
- pageChanged = QtCore.Signal(int, int, int) # fires when the page is changed (page_number, start_index, end_index)
+ """table view that displays paginated data"""
+
+ pageChanged = QtCore.Signal(
+ int, int, int
+ ) # fires when the page is changed (page_number, start_index, end_index)
- def __init__(self, item_count = 0, page_size=10):
- ''' setups the data model and callback to get a model by index '''
+ def __init__(self, item_count=0, page_size=10):
+ """setups the data model and callback to get a model by index"""
super().__init__()
self._item_count = item_count
self._page_size = page_size
diff --git a/gremlin/ui/ui_gremlin.py b/gremlin/ui/ui_gremlin.py
index 76d21bd5..1b5d9425 100644
--- a/gremlin/ui/ui_gremlin.py
+++ b/gremlin/ui/ui_gremlin.py
@@ -8,52 +8,51 @@
from PySide6 import QtCore, QtGui, QtWidgets
+
class Ui_Gremlin(object):
def setupUi(self, main_window):
import gremlin.ui.ui_common
+
main_window.setObjectName("Gremlin")
main_window.resize(800, 600)
self.main = QtWidgets.QWidget(main_window)
self.main.setObjectName("main")
self.main_layout = QtWidgets.QVBoxLayout(self.main)
-
# content panel below the tab
self.tab_bar_widget = QtWidgets.QWidget(parent=self.main)
- self.tab_bar_widget.setContentsMargins(0,0,0,0)
- #self.tab_bar_widget.setStyleSheet("background: yellow")
+ self.tab_bar_widget.setContentsMargins(0, 0, 0, 0)
+ # self.tab_bar_widget.setStyleSheet("background: yellow")
self.tab_bar_layout = QtWidgets.QVBoxLayout(self.tab_bar_widget)
- self.tab_bar_layout.setContentsMargins(0,0,0,0)
+ self.tab_bar_layout.setContentsMargins(0, 0, 0, 0)
self.tab_bar_widget.setMaximumHeight(30)
-
- self.devices = gremlin.ui.ui_common.QTabHeader(parent = self.tab_bar_widget)
+
+ self.devices = gremlin.ui.ui_common.QTabHeader(parent=self.tab_bar_widget)
self.devices.setMovable(True)
self.devices.setUsesScrollButtons(True)
self.devices.setObjectName("devices")
-
-
+
self.tab_bar_layout.addWidget(self.devices)
self.tab_bar_layout.addStretch(2)
# content panel below the tab
self.tab_content_widget = QtWidgets.QWidget(self.main)
- self.tab_content_widget.setContentsMargins(0,0,0,0)
- #self.tab_content_widget.setStyleSheet("background: green")
+ self.tab_content_widget.setContentsMargins(0, 0, 0, 0)
+ # self.tab_content_widget.setStyleSheet("background: green")
self.tab_content_layout = QtWidgets.QVBoxLayout(self.tab_content_widget)
- self.tab_content_layout.setContentsMargins(0,0,0,0)
-
-
+ self.tab_content_layout.setContentsMargins(0, 0, 0, 0)
+
self.main_layout.addWidget(self.tab_bar_widget)
self.main_layout.addWidget(self.tab_content_widget)
self.statusbar_widget = QtWidgets.QWidget()
- self.statusbar_widget.setContentsMargins(0,0,0,0)
+ self.statusbar_widget.setContentsMargins(0, 0, 0, 0)
self.statusbar_layout = QtWidgets.QHBoxLayout(self.statusbar_widget)
- self.statusbar_layout.setContentsMargins(0,0,0,0)
+ self.statusbar_layout.setContentsMargins(0, 0, 0, 0)
self.statusbar_widget.setMaximumHeight(32)
self.main_layout.addWidget(self.statusbar_widget)
-
+
main_window.setCentralWidget(self.main)
self.menubar = QtWidgets.QMenuBar(main_window)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
@@ -156,7 +155,7 @@ def setupUi(self, main_window):
self.menuFile.addAction(self.actionSaveProfileAs)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionImportProfile)
-
+
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionModifyProfile)
self.menuFile.addSeparator()
@@ -164,7 +163,7 @@ def setupUi(self, main_window):
self.menuTools.addAction(self.actionManageModes)
self.menuTools.addAction(self.actionInputRepeater)
self.menuTools.addAction(self.actionDeviceInformation)
- #self.menuTools.addAction(self.actionCalibration)
+ # self.menuTools.addAction(self.actionCalibration)
self.menuTools.addAction(self.actionInputViewer)
main_window.add_custom_tools_menu(self.menuTools)
self.menuTools.addSeparator()
@@ -189,7 +188,6 @@ def setupUi(self, main_window):
self.menubar.addAction(self.menuTools.menuAction())
self.menubar.addAction(self.menu_Help.menuAction())
-
# main UI toolbar setup
self.toolBar.addAction(self.actionSave)
self.toolBar.addAction(self.actionOpen)
@@ -198,18 +196,17 @@ def setupUi(self, main_window):
# separator widget
widget = QtWidgets.QWidget()
widget.setMinimumWidth(32)
- #self.toolBar.addWidget(QtWidgets.QLabel(" "*5))
+ # self.toolBar.addWidget(QtWidgets.QLabel(" "*5))
self.toolBar.addWidget(widget)
self.toolBar.addAction(self.actionInputViewer)
self.toolBar.addAction(self.actionOptions)
-
- self.actionSimconnectOptions = QtGui.QAction(main_window, text = "Simconnect...")
+ self.actionSimconnectOptions = QtGui.QAction(main_window, text="Simconnect...")
self.actionSimconnectOptions.setObjectName("actionSimconnectOptions")
self.menuTools.addSeparator()
self.menuTools.addAction(self.actionSimconnectOptions)
-
+
self.retranslateUi(main_window)
self.devices.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(main_window)
@@ -217,7 +214,7 @@ def setupUi(self, main_window):
def retranslateUi(self, Gremlin):
_translate = QtCore.QCoreApplication.translate
Gremlin.setWindowTitle(_translate("GremlinEx", "Joystick Gremlin Ex"))
-
+
self.menuFile.setTitle(_translate("GremlinEx", "&File"))
self.menuRecent.setTitle(_translate("GremlinEx", "Recent"))
self.menuTools.setTitle(_translate("GremlinEx", "&Tools"))
@@ -232,23 +229,37 @@ def retranslateUi(self, Gremlin):
self.actionNewProfile.setText(_translate("GremlinEx", "&New Profile"))
self.actionSaveProfile.setText(_translate("GremlinEx", "&Save Profile"))
self.actionSaveProfileAs.setText(_translate("GremlinEx", "&Save Profile As"))
- self.actionRevealProfile.setText(_translate("GremlinEx", "&Reveal Profile in Explorer..."))
- self.actionOpenLogFile.setText(_translate("GremlinEx", "Open &Log file in editor..."))
- self.actionOpenXmlProfile.setText(_translate("GremlinEx","&Open profile XML in editor..."))
- self.actionOpenGremlinExFolder.setText(_translate("GremlinEx","&Open GremlinEx folder..."))
+ self.actionRevealProfile.setText(
+ _translate("GremlinEx", "&Reveal Profile in Explorer...")
+ )
+ self.actionOpenLogFile.setText(
+ _translate("GremlinEx", "Open &Log file in editor...")
+ )
+ self.actionOpenXmlProfile.setText(
+ _translate("GremlinEx", "&Open profile XML in editor...")
+ )
+ self.actionOpenGremlinExFolder.setText(
+ _translate("GremlinEx", "&Open GremlinEx folder...")
+ )
self.actionGenerate.setText(_translate("GremlinEx", "Generate"))
- self.actionDeviceInformation.setText(_translate("GremlinEx", "Device Information"))
+ self.actionDeviceInformation.setText(
+ _translate("GremlinEx", "Device Information")
+ )
self.actionAbout.setText(_translate("GremlinEx", "&About"))
- self.actionManageCustomModules.setText(_translate("GremlinEx", "&Manage Custom Modules"))
+ self.actionManageCustomModules.setText(
+ _translate("GremlinEx", "&Manage Custom Modules")
+ )
self.actionInputRepeater.setText(_translate("GremlinEx", "Input Repeater"))
- #self.actionCalibration.setText(_translate("GremlinEx", "&Calibration"))
+ # self.actionCalibration.setText(_translate("GremlinEx", "&Calibration"))
self.actionManageModes.setText(_translate("GremlinEx", "Manage Modes"))
self.actionHTMLCheatsheet.setText(_translate("GremlinEx", "HTML Cheatsheet"))
self.actionPDFCheatsheet.setText(_translate("GremlinEx", "PDF Cheatsheet"))
- self.actionViewInput.setText(_translate("GremlinEx","View Input Map"))
+ self.actionViewInput.setText(_translate("GremlinEx", "View Input Map"))
self.actionExit.setText(_translate("GremlinEx", "E&xit"))
self.actionOptions.setText(_translate("GremlinEx", "&Options"))
- self.actionCreate1to1Mapping.setText(_translate("GremlinEx", "Create 1:1 mapping"))
+ self.actionCreate1to1Mapping.setText(
+ _translate("GremlinEx", "Create 1:1 mapping")
+ )
self.actionLogDisplay.setText(_translate("GremlinEx", "&Log display"))
self.actionLogEdit.setText(_translate("GremlinEx", "&Log display in editor"))
self.actionMergeAxis.setText(_translate("GremlinEx", "&Merge Axis"))
@@ -257,4 +268,3 @@ def retranslateUi(self, Gremlin):
self.actionEmpty.setText(_translate("GremlinEx", "Empty"))
self.actionSwapDevices.setText(_translate("GremlinEx", "Swap Devices"))
self.actionInputViewer.setText(_translate("GremlinEx", "Input Viewer"))
-
diff --git a/gremlin/ui/ui_util.py b/gremlin/ui/ui_util.py
index 33af4685..15d1834c 100644
--- a/gremlin/ui/ui_util.py
+++ b/gremlin/ui/ui_util.py
@@ -17,17 +17,20 @@
from __future__ import annotations
-from ast import In
-from collections.abc import Callable
-from multiprocessing import Event
import threading
from typing import List, Optional
from PySide6 import QtCore, QtQml
-from PySide6.QtCore import Property, Signal, Slot
+from PySide6.QtCore import Property, Signal
-from gremlin import event_handler, input_devices, keyboard, shared_state, windows_event_hook
+from gremlin import (
+ event_handler,
+ input_devices,
+ keyboard,
+ shared_state,
+ windows_event_hook,
+)
from gremlin.input_types import InputType
import gremlin.keyboard
@@ -39,7 +42,6 @@
@QtQml.QmlElement
class InputListenerModel(QtCore.QObject):
-
"""Allows recording user inputs with an on-screen prompt."""
# Signal emitted when the listening for inputs is done to let the UI
@@ -52,7 +54,7 @@ class InputListenerModel(QtCore.QObject):
# Signal emitted when multiple inputs are accepted or ignored
multipleInputsChanged = Signal(bool)
- def __init__(self, parent: Optional[QtCore.QObject]=None):
+ def __init__(self, parent: Optional[QtCore.QObject] = None):
super().__init__(parent)
# List of InputTypes that will be listened to
@@ -72,7 +74,7 @@ def _get_event_types(self) -> List[str]:
def _set_event_types(self, event_types: List[str]) -> None:
types = sorted(
[InputType.to_enum(v) for v in event_types],
- key=lambda v: InputType.to_string(v)
+ key=lambda v: InputType.to_string(v),
)
if types != self._event_types:
self._event_types = types
@@ -107,9 +109,11 @@ def _connect_listeners(self) -> None:
# Start listening to user inputs
event_listener = event_handler.EventListener()
event_listener.keyboard_event.connect(self._kb_event_cb)
- if InputType.JoystickAxis in self._event_types or \
- InputType.JoystickButton in self._event_types or \
- InputType.JoystickHat in self._event_types:
+ if (
+ InputType.JoystickAxis in self._event_types
+ or InputType.JoystickButton in self._event_types
+ or InputType.JoystickHat in self._event_types
+ ):
event_listener.joystick_event.connect(self._joy_event_cb)
elif InputType.Mouse in self._event_types:
event_listener = gremlin.event_handler.EventListener()
@@ -123,15 +127,15 @@ def _disconnect_listeners(self) -> None:
event_listener = event_handler.EventListener()
try:
event_listener.keyboard_event.disconnect(self._kb_event_cb)
- except RuntimeError as e:
+ except RuntimeError:
pass
try:
event_listener.joystick_event.disconnect(self._joy_event_cb)
- except RuntimeError as e:
+ except RuntimeError:
pass
try:
event_listener.mouse_event.disconnect(self._mouse_event_cb)
- except RuntimeError as e:
+ except RuntimeError:
pass
# Stop mouse hook in case it is running
@@ -148,12 +152,11 @@ def _maybe_terminate_listening(self, event: event_handler.Event) -> None:
"""Terminates listening to user input if adequate."""
# ESC key always triggers the abort timer
if event.is_pressed and event.event_type == InputType.Keyboard:
- key = keyboard.key_from_code(
- event.identifier[0],
- event.identifier[1]
- )
- if key == keyboard.key_from_name("esc") and \
- not self._abort_timer.is_alive():
+ key = keyboard.key_from_code(event.identifier[0], event.identifier[1])
+ if (
+ key == keyboard.key_from_name("esc")
+ and not self._abort_timer.is_alive()
+ ):
self._abort_timer = threading.Timer(1.0, self._stop_listening)
self._abort_timer.start()
@@ -189,8 +192,7 @@ def _joy_event_cb(self, event: event_handler.Event) -> None:
return
# Ensure the event corresponds to a significant enough change in input
- process_event = \
- input_devices.JoystickInputSignificant().should_process(event)
+ process_event = input_devices.JoystickInputSignificant().should_process(event)
if process_event:
input_devices.JoystickInputSignificant().reset()
@@ -237,29 +239,19 @@ def _mouse_event_cb(self, event: event_handler.Event) -> None:
self._inputs.append(event)
self._maybe_terminate_listening(event)
- currentInput = Property(
- str,
- fget=_get_current_inputs,
- notify=listeningTerminated
- )
+ currentInput = Property(str, fget=_get_current_inputs, notify=listeningTerminated)
enabled = Property(
- bool,
- fget=_get_is_enabled,
- fset=_set_is_enabled,
- notify=enabledChanged
+ bool, fget=_get_is_enabled, fset=_set_is_enabled, notify=enabledChanged
)
multipleInputs = Property(
bool,
fget=_get_multiple_inputs,
fset=_set_multiple_inputs,
- notify=multipleInputsChanged
+ notify=multipleInputsChanged,
)
eventTypes = Property(
- list,
- fget=_get_event_types,
- fset=_set_event_types,
- notify=eventTypesChanged
- )
\ No newline at end of file
+ list, fget=_get_event_types, fset=_set_event_types, notify=eventTypesChanged
+ )
diff --git a/gremlin/ui/user_plugin_management.py b/gremlin/ui/user_plugin_management.py
index c5a1a0ab..254a71a1 100644
--- a/gremlin/ui/user_plugin_management.py
+++ b/gremlin/ui/user_plugin_management.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
import logging
-from PySide6 import QtCore, QtGui, QtWidgets
+from PySide6 import QtCore, QtWidgets
from gremlin.common import PluginVariableType
@@ -35,8 +35,8 @@
syslog = logging.getLogger("system")
-class ModuleManagementController(QtCore.QObject):
+class ModuleManagementController(QtCore.QObject):
def __init__(self, profile_data, parent=None):
super().__init__(parent)
@@ -62,10 +62,6 @@ def new_module(self, fname):
# Update the model
module = gremlin.base_profile.Plugin(self.profile_data)
module.file_name = fname
-
- # Create new data instance
- instance = self._create_module_instance("Default", module)
-
self.profile_data.plugins.append(module)
# Update the view
@@ -98,9 +94,7 @@ def refresh_module_list(self):
# Empty module list and then add one module at a time
self.view.module_list.clear()
for plugin in self.profile_data.plugins:
- self.view.module_list.add_module(
- self._create_module_widget(plugin)
- )
+ self.view.module_list.add_module(self._create_module_widget(plugin))
def remove_instance(self, instance, widget):
# Remove model
@@ -113,12 +107,13 @@ def rename_instance(self, instance, widget, name):
widget.label_name.setText(name)
def copy_instance(self, instance, widget):
- ''' copy to a new instance '''
+ """copy to a new instance"""
import re
+
gremlin.util.pushCursor()
module_data = instance.parent
- new_instance = gremlin.base_profile.PluginInstance(module_data)
-
+ new_instance = gremlin.base_profile.PluginInstance(module_data)
+
not_unique = True
if instance.name.endswith("copy"):
@@ -126,7 +121,7 @@ def copy_instance(self, instance, widget):
index = 1
copy_name = name_stub + f" {index}"
else:
- m = re.search(r'copy \d+$', instance.name)
+ m = re.search(r"copy \d+$", instance.name)
if m is None:
# does not end in numerical sequence
index = 0
@@ -137,14 +132,13 @@ def copy_instance(self, instance, widget):
stub = m.group()
seq = stub.split()[-1]
index = int(seq) + 1
- name_stub = instance.name[:-len(seq)].strip()
+ name_stub = instance.name[: -len(seq)].strip()
copy_name = name_stub + f" {index}"
-
-
+
while not_unique:
for item in instance.parent.instances:
if item.name == copy_name:
- index+=1
+ index += 1
copy_name = name_stub + f" {index}"
break
not_unique = False
@@ -155,15 +149,13 @@ def copy_instance(self, instance, widget):
module_data.instances.append(new_instance)
module_widget = widget.module_widget
- new_instance_widget = InstanceWidget(new_instance.name)
+ new_instance_widget = InstanceWidget(new_instance.name)
new_instance_widget.module_widget = module_widget
-
module_widget.add_instance(new_instance_widget)
self._connect_instance_signals(new_instance, new_instance_widget)
gremlin.util.popCursor()
-
def configure_instance(self, instance, widget):
# Get data from the custom module itself
variables = gremlin.user_plugin.get_variable_definitions(
@@ -173,13 +165,11 @@ def configure_instance(self, instance, widget):
layout = self.view.right_panel.layout()
gremlin.ui.ui_common.clear_layout(layout)
-
# add the name of the instance being configured
header_container_widget = QtWidgets.QWidget()
header_container_layout = QtWidgets.QHBoxLayout(header_container_widget)
- header_container_widget.setContentsMargins(0,0,0,0)
- header_container_layout.setContentsMargins(0,0,0,0)
-
+ header_container_widget.setContentsMargins(0, 0, 0, 0)
+ header_container_layout.setContentsMargins(0, 0, 0, 0)
instance_name_widget = gremlin.ui.ui_common.QDataLineEdit(text=instance.name)
instance_name_widget.setStyleSheet("border-style: solid;border-width: 1px;")
@@ -187,13 +177,12 @@ def configure_instance(self, instance, widget):
instance_name_widget.textChanged.connect(self._update_instance_name_cb)
header_container_layout.addWidget(QtWidgets.QLabel("Instance:"))
header_container_layout.addWidget(instance_name_widget)
- #header_container_layout.addStretch()
+ # header_container_layout.addStretch()
layout.addWidget(header_container_widget)
layout.addWidget(gremlin.ui.ui_common.QHLine())
verbose = gremlin.config.Configuration().verbose
-
if verbose:
log = syslog
log.info(f"Configure instance: {instance.name}")
@@ -218,14 +207,11 @@ def configure_instance(self, instance, widget):
if verbose:
log.info(f"\t{str(profile_var)}")
-
ui_element = var.create_ui_element(profile_var.value)
var.value_changed.connect(
self._create_value_changed_cb(
- profile_var,
- ui_element,
- self._update_value_variable
+ profile_var, ui_element, self._update_value_variable
)
)
layout.addLayout(ui_element)
@@ -243,7 +229,7 @@ def _update_instance_name_cb(self):
instance = widget.data
name = widget.text()
instance_widget = self.instance_widget_map[instance]
- self.rename_instance(instance, instance_widget, name )
+ self.rename_instance(instance, instance_widget, name)
def _update_value_variable(self, data, widget, variable):
if variable.type in [
@@ -267,7 +253,7 @@ def _update_value_variable(self, data, widget, variable):
)
button.setText(
f"{data["device_name"]} {InputType.to_string(data["input_type"]).capitalize()} {input_id}"
- )
+ )
variable.is_valid = True
@@ -296,18 +282,14 @@ def _create_module_widget(self, module_data):
return module_widget
def _connect_instance_signals(self, instance, widget):
- widget.renamed.connect(
- lambda x: self.rename_instance(instance, widget, x)
- )
+ widget.renamed.connect(lambda x: self.rename_instance(instance, widget, x))
widget.btn_delete.clicked.connect(
lambda x: self.remove_instance(instance, widget)
)
widget.btn_configure.clicked.connect(
lambda x: self.configure_instance(instance, widget)
)
- widget.btn_copy.clicked.connect(
- lambda x: self.copy_instance(instance, widget)
- )
+ widget.btn_copy.clicked.connect(lambda x: self.copy_instance(instance, widget))
def _create_module_instance(self, name, module_data):
# Create the model data side of things
@@ -332,14 +314,12 @@ def _create_module_instance(self, name, module_data):
class ModuleManagementView(QtWidgets.QSplitter):
-
add_module = QtCore.Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self.controller = None
-
# Create the left panel showing the modules and their instances
self.left_panel = QtWidgets.QWidget()
@@ -348,11 +328,12 @@ def __init__(self, parent=None):
# Displays the various modules and instances associated with them
self.module_list = ModuleListWidget()
-
# Button to add a new module
prefix = "dark_" if gremlin.shared_state.is_dark_theme else ""
- self.btn_add_module = QtWidgets.QPushButton(load_icon(f"gfx/{prefix}list_add.svg"), "Add Plugin")
-
+ self.btn_add_module = QtWidgets.QPushButton(
+ load_icon(f"gfx/{prefix}list_add.svg"), "Add Plugin"
+ )
+
self.btn_add_module.clicked.connect(self._prompt_user_for_module)
self.left_panel.layout().addWidget(self.module_list)
@@ -372,27 +353,22 @@ def refresh_ui(self):
def _prompt_user_for_module(self):
"""Asks the user to select the path to the module to add."""
import gremlin.config
+
config = gremlin.config.Configuration()
dir = config.last_plugin_folder
if dir is None or not os.path.isdir(dir):
dir = userprofile_path()
fname, _ = QtWidgets.QFileDialog.getOpenFileName(
- None,
- "Path to Python plugin",
- dir,
- "Python (*.py)"
+ None, "Path to Python plugin", dir, "Python (*.py)"
)
if os.path.isfile(fname):
- dirname,_ = os.path.split(fname)
+ dirname, _ = os.path.split(fname)
config.last_plugin_folder = dirname
-
-
self.add_module.emit(fname)
class ModuleListWidget(QtWidgets.QScrollArea):
-
"""Displays a list of loaded modules."""
def __init__(self, parent=None):
@@ -412,10 +388,7 @@ def add_module(self, module_widget):
# Insert provided widget as the last one in the list above the
# stretcher item
self.widget_list.append(module_widget)
- self.content_layout.insertWidget(
- self.content_layout.count() - 1,
- module_widget
- )
+ self.content_layout.insertWidget(self.content_layout.count() - 1, module_widget)
def remove_module(self, module_widget):
module_widget.hide()
@@ -431,20 +404,16 @@ def clear(self):
class ModuleWidget(QBoxFrame):
-
def __init__(self, module_name, parent=None):
super().__init__(parent)
- variables = gremlin.user_plugin.get_variable_definitions(
- module_name
- )
+ variables = gremlin.user_plugin.get_variable_definitions(module_name)
self.has_variables = len(variables) > 0
layout = QtWidgets.QVBoxLayout(self)
background_color = gremlin.ui.ui_common.Color.actionBackgroundColor()
self.setStyleSheet(f"QFrame {{ background-color : {background_color}; }}")
-
header_layout = QtWidgets.QHBoxLayout()
header_layout.addWidget(QtWidgets.QLabel(module_name))
@@ -454,12 +423,13 @@ def __init__(self, module_name, parent=None):
if self.has_variables:
self.btn_add_instance = QtWidgets.QPushButton(
- load_icon(f"gfx/{prefix}button_add.png"),""
+ load_icon(f"gfx/{prefix}button_add.png"), ""
)
header_layout.addWidget(self.btn_add_instance)
self.btn_delete = QtWidgets.QPushButton(
- load_icon(f"gfx/{prefix}button_delete.png"),"")
+ load_icon(f"gfx/{prefix}button_delete.png"), ""
+ )
header_layout.addWidget(self.btn_delete)
self.instance_layout = QtWidgets.QVBoxLayout()
@@ -481,12 +451,10 @@ def remove_instance(self, widget):
class InstanceWidget(QtWidgets.QWidget):
-
"""Shows the controls for a particular module instance."""
renamed = QtCore.Signal(str)
-
def __init__(self, name, parent=None):
super().__init__(parent)
@@ -496,8 +464,6 @@ def __init__(self, name, parent=None):
self._create_ui()
def _create_ui(self):
-
-
prefix = "dark_" if gremlin.shared_state.is_dark_theme else ""
icon_color = gremlin.ui.ui_common.Color.normalColor()
@@ -514,10 +480,12 @@ def _create_ui(self):
)
self.btn_configure.setToolTip("Configure this instance")
self.btn_delete = QtWidgets.QPushButton(
- load_icon(f"gfx/{prefix}button_delete.png"), ""
+ load_icon(f"gfx/{prefix}button_delete.png"), ""
)
self.btn_delete.setToolTip("Delete this instance")
- self.btn_copy = QtWidgets.QPushButton(load_icon(f"gfx/{prefix}button_copy.svg"),"")
+ self.btn_copy = QtWidgets.QPushButton(
+ load_icon(f"gfx/{prefix}button_copy.svg"), ""
+ )
self.btn_copy.setToolTip("Copy this instance")
self.main_layout.addWidget(self.label_name)
@@ -529,11 +497,11 @@ def _create_ui(self):
def rename_instance(self):
name, user_input = QtWidgets.QInputDialog.getText(
- self,
- "Instance name",
- "New name for this instance",
- QtWidgets.QLineEdit.Normal,
- self.name
+ self,
+ "Instance name",
+ "New name for this instance",
+ QtWidgets.QLineEdit.Normal,
+ self.name,
)
if user_input:
@@ -542,6 +510,7 @@ def rename_instance(self):
@property
def module_widget(self):
return self._module_widget
+
@module_widget.setter
def module_widget(self, value):
- self._module_widget = value
\ No newline at end of file
+ self._module_widget = value
diff --git a/gremlin/ui/virtual_button.py b/gremlin/ui/virtual_button.py
index 50542c8f..f10fdad8 100644
--- a/gremlin/ui/virtual_button.py
+++ b/gremlin/ui/virtual_button.py
@@ -16,7 +16,7 @@
# along with this program. If not, see .
-from PySide6 import QtWidgets, QtCore, QtGui
+from PySide6 import QtWidgets, QtCore
import gremlin
import gremlin.shared_state
@@ -185,11 +185,11 @@ def _update_range_state(self, value):
case gremlin.types.AxisButtonDirection.Below:
if value < self.last_value:
- self.range_status_widget.setText(f"(below)")
+ self.range_status_widget.setText("(below)")
visible = True
case gremlin.types.AxisButtonDirection.Above:
if value > self.last_value:
- self.range_status_widget.setText(f"(below)")
+ self.range_status_widget.setText("(below)")
visible = True
diff --git a/gremlin/ui/virtual_keyboard.py b/gremlin/ui/virtual_keyboard.py
index a4257d11..7d1fb248 100644
--- a/gremlin/ui/virtual_keyboard.py
+++ b/gremlin/ui/virtual_keyboard.py
@@ -1,5 +1,3 @@
-import os
-from lxml import etree as ElementTree
from PySide6 import QtWidgets, QtCore, QtGui
@@ -12,27 +10,27 @@
import gremlin.shared_state
import gremlin.ui.ui_common
import gremlin.ui.input_item
-import enum
from gremlin.keyboard import Key
from gremlin.util import load_icon
import logging
import datetime
import time
-
+
syslog = logging.getLogger("system")
-class QKeyWidget(QtWidgets.QPushButton):
+class QKeyWidget(QtWidgets.QPushButton):
# indicates when the widget is hovered (true = on)
hover = QtCore.Signal(object, bool)
# fires when selection changes
selected_changed = QtCore.Signal(object)
- ''' custom key label '''
- def __init__(self, text = None, parent = None) -> None:
- super().__init__(text= text, parent = parent)
+ """ custom key label """
+
+ def __init__(self, text=None, parent=None) -> None:
+ super().__init__(text=text, parent=parent)
self._key = None
self._selected = False
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_Hover, True)
@@ -52,33 +50,32 @@ def __init__(self, text = None, parent = None) -> None:
self._key_size = 1
self.setStyleSheet(self._default_style)
-
- self.normal_key = None # what to display normally
- self.shifted_key = None # what to display when shifted
+
+ self.normal_key = None # what to display normally
+ self.shifted_key = None # what to display when shifted
self.installEventFilter(self)
-
@property
def key(self) -> Key:
- ''' returns the associated key '''
+ """returns the associated key"""
return self._key
-
+
@property
def keySize(self) -> int:
return self._key_size
+
@keySize.setter
- def keySize(self, value : int):
+ def keySize(self, value: int):
self._key_size = value
match value:
case 1:
self.setStyleSheet(self._default_style)
case 2:
self.setStyleSheet(self._x2_default_style)
-
-
+
@key.setter
- def key(self, value : Key):
- ''' sets the associated key '''
+ def key(self, value: Key):
+ """sets the associated key"""
self._key = value
@property
@@ -88,7 +85,7 @@ def is_click_shifted(self):
@property
def is_keypad(self):
return self._is_keypad
-
+
@is_keypad.setter
def is_keypad(self, value):
self._is_keypad = value
@@ -96,16 +93,15 @@ def is_keypad(self, value):
@property
def selected(self):
return self._selected
-
+
@selected.setter
- def selected(self,value):
+ def selected(self, value):
if self._selected != value:
self._selected = value
self._update_state()
-
def _update_state(self):
- ''' updates the color of the button based on the selection state '''
+ """updates the color of the button based on the selection state"""
match self._key_size:
case 2:
plain = self._x2_default_style
@@ -113,13 +109,12 @@ def _update_state(self):
case _:
plain = self._default_style
selected = self._selected_style
-
+
if self._selected:
self.setStyleSheet(plain)
else:
self.setStyleSheet(selected)
-
def eventFilter(self, widget, event):
t = event.type()
if t == QtCore.QEvent.Type.HoverEnter:
@@ -127,30 +122,31 @@ def eventFilter(self, widget, event):
elif t == QtCore.QEvent.Type.HoverLeave:
self.hover.emit(self, False)
- return False # super().eventFilter(widget, event)
-
+ return False # super().eventFilter(widget, event)
+
@property
def display_name(self):
- ''' friendly key name'''
+ """friendly key name"""
if self._key:
return self._key.name + " " + self._key.latched_code
return ""
-
-class QKeyboardWidget(QtWidgets.QWidget):
- ''' virtual keyboard widget '''
- keyEvent = QtCore.Signal() # called when the data has changed
+class QKeyboardWidget(QtWidgets.QWidget):
+ """virtual keyboard widget"""
- def __init__(self, parent = None):
+ keyEvent = QtCore.Signal() # called when the data has changed
- ''' creates a full keyboard widget for manual data entry '''
+ def __init__(self, parent=None):
+ """creates a full keyboard widget for manual data entry"""
super().__init__(parent)
- self.installEventFilter(self) # capture keys so the window doesn't go nuts when we hit special keys
+ self.installEventFilter(
+ self
+ ) # capture keys so the window doesn't go nuts when we hit special keys
main_layout = QtWidgets.QVBoxLayout(self)
- main_layout.setContentsMargins(0,0,0,0)
+ main_layout.setContentsMargins(0, 0, 0, 0)
grid_layout = QtWidgets.QGridLayout()
grid_layout.setSpacing(2)
main_layout.addLayout(grid_layout)
@@ -159,17 +155,16 @@ def __init__(self, parent = None):
widget, layout = gremlin.ui.ui_common.getVContainer()
self.repeater_container_widget = widget
- self.repeater_container_layout= layout
-
+ self.repeater_container_layout = layout
+
main_layout.addWidget(self.repeater_container_widget)
self._show_repeater = self.config.keyboard_repeater_show
- self._repeater_lines = 30 # number of lines displayed in the repeater
+ self._repeater_lines = 30 # number of lines displayed in the repeater
self._repeater_list = []
self._repeater_timestamp = {}
self._invert_display = self.config.keyboard_repeater_invert_display
self._capture_mouse = self.config.keyboard_repeater_capture_mouse
-
clear_widget = QtWidgets.QPushButton("Clear")
clear_widget.setToolTip("Clears selection")
clear_widget.clicked.connect(self._clear_repeater)
@@ -184,8 +179,9 @@ def __init__(self, parent = None):
invert_display_widget.setChecked(self._invert_display)
invert_display_widget.clicked.connect(self._invert_display_changed)
-
- self.options_widget, _= gremlin.ui.ui_common.getHContainer([clear_widget, capture_mouse_widget, invert_display_widget])
+ self.options_widget, _ = gremlin.ui.ui_common.getHContainer(
+ [clear_widget, capture_mouse_widget, invert_display_widget]
+ )
self.repeater_container_layout.addWidget(self.options_widget)
self.repeater_widget = QtWidgets.QPlainTextEdit()
@@ -193,36 +189,210 @@ def __init__(self, parent = None):
self.repeater_container_layout.addWidget(self.repeater_widget)
self.keyEvent.connect(self._update_repeater)
-
-
+
# list of scancodes https://handmade.network/forums/articles/t/2823-keyboard_inputs_-_scancodes%252C_raw_input%252C_text_input%252C_key_names
# first row = QUERTY object
- row_0 = ["","","F13","F14","F15","F16","F17","F18","F19","F20","F21","F22","F23","F24","","mouse_1","mouse_2","mouse_3","","mouse_4","mouse_5","wheel_up","wheel_down"]
- row_1 = ["Esc","","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","",["PrtSc","printscreen"],["Scrlck","scrolllock"],["Pause","pause"],"","","","wheel_left","wheel_right"]
- row_2 = ["`","1","2","3","4","5","6","7","8","9","0","-","=",["Back","backspace"],"",["Ins","insert"],["Home","home"],["PgUp","pageup"],"",["NmLck","numlock"],["/","npdivide"],["*","npmultiply"],["-","npminus"]]
- row_3 = [["Tab","tab"],"Q","W","E","R","T","Y","U","I","O","P","[","]","\\","",["Del","delete"],"End",["PgDn","pagedown"],"",["7","np7"],["8","np8"],["9","np9"],["+","npplus",1,2]]
- row_4 = [["CpsLck","capslock"],"A","S","D","F","G","H","J","K","L",";","'",["Enter",2],"","","","","",["4","np4"],["5","np5"],["6","np6"]]
- row_5 = [["LShift","leftshift"],"Z","X","C","V","B","N","M",",",".","/",["RShift","rightshift"],"","","","","up","","",["1","np1"],["2","np2"],["3","np3"],["Enter","npenter",1,2]]
- row_6 = [["LCtrl","leftcontrol"],["LWin","leftwin"],["LAlt","leftalt"],["Spacebar","space",6],["RAlt","rightalt2"],["RWin","rightwin"],["RCtrl","rightcontrol"],"","","","left","down","right","",["0/Ins","np0",2],["./Del","npdelete"]]
+ row_0 = [
+ "",
+ "",
+ "F13",
+ "F14",
+ "F15",
+ "F16",
+ "F17",
+ "F18",
+ "F19",
+ "F20",
+ "F21",
+ "F22",
+ "F23",
+ "F24",
+ "",
+ "mouse_1",
+ "mouse_2",
+ "mouse_3",
+ "",
+ "mouse_4",
+ "mouse_5",
+ "wheel_up",
+ "wheel_down",
+ ]
+ row_1 = [
+ "Esc",
+ "",
+ "F1",
+ "F2",
+ "F3",
+ "F4",
+ "F5",
+ "F6",
+ "F7",
+ "F8",
+ "F9",
+ "F10",
+ "F11",
+ "F12",
+ "",
+ ["PrtSc", "printscreen"],
+ ["Scrlck", "scrolllock"],
+ ["Pause", "pause"],
+ "",
+ "",
+ "",
+ "wheel_left",
+ "wheel_right",
+ ]
+ row_2 = [
+ "`",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "0",
+ "-",
+ "=",
+ ["Back", "backspace"],
+ "",
+ ["Ins", "insert"],
+ ["Home", "home"],
+ ["PgUp", "pageup"],
+ "",
+ ["NmLck", "numlock"],
+ ["/", "npdivide"],
+ ["*", "npmultiply"],
+ ["-", "npminus"],
+ ]
+ row_3 = [
+ ["Tab", "tab"],
+ "Q",
+ "W",
+ "E",
+ "R",
+ "T",
+ "Y",
+ "U",
+ "I",
+ "O",
+ "P",
+ "[",
+ "]",
+ "\\",
+ "",
+ ["Del", "delete"],
+ "End",
+ ["PgDn", "pagedown"],
+ "",
+ ["7", "np7"],
+ ["8", "np8"],
+ ["9", "np9"],
+ ["+", "npplus", 1, 2],
+ ]
+ row_4 = [
+ ["CpsLck", "capslock"],
+ "A",
+ "S",
+ "D",
+ "F",
+ "G",
+ "H",
+ "J",
+ "K",
+ "L",
+ ";",
+ "'",
+ ["Enter", 2],
+ "",
+ "",
+ "",
+ "",
+ "",
+ ["4", "np4"],
+ ["5", "np5"],
+ ["6", "np6"],
+ ]
+ row_5 = [
+ ["LShift", "leftshift"],
+ "Z",
+ "X",
+ "C",
+ "V",
+ "B",
+ "N",
+ "M",
+ ",",
+ ".",
+ "/",
+ ["RShift", "rightshift"],
+ "",
+ "",
+ "",
+ "",
+ "up",
+ "",
+ "",
+ ["1", "np1"],
+ ["2", "np2"],
+ ["3", "np3"],
+ ["Enter", "npenter", 1, 2],
+ ]
+ row_6 = [
+ ["LCtrl", "leftcontrol"],
+ ["LWin", "leftwin"],
+ ["LAlt", "leftalt"],
+ ["Spacebar", "space", 6],
+ ["RAlt", "rightalt2"],
+ ["RWin", "rightwin"],
+ ["RCtrl", "rightcontrol"],
+ "",
+ "",
+ "",
+ "left",
+ "down",
+ "right",
+ "",
+ ["0/Ins", "np0", 2],
+ ["./Del", "npdelete"],
+ ]
shifted_list = [
- ("`","~"),("1","!"),("2","@"),("3","#"),("4","$"),("5","%"),("6","^"),
- ("7","&&"),("8","*"),("9","("),("0",")"),("-","_"),("=","+"),
- ("[","{"),("]","}"),("\\","|"),(";",":"),("'","\""),(",","<"),(".",">"),("/","?")
- ]
-
+ ("`", "~"),
+ ("1", "!"),
+ ("2", "@"),
+ ("3", "#"),
+ ("4", "$"),
+ ("5", "%"),
+ ("6", "^"),
+ ("7", "&&"),
+ ("8", "*"),
+ ("9", "("),
+ ("0", ")"),
+ ("-", "_"),
+ ("=", "+"),
+ ("[", "{"),
+ ("]", "}"),
+ ("\\", "|"),
+ (";", ":"),
+ ("'", '"'),
+ (",", "<"),
+ (".", ">"),
+ ("/", "?"),
+ ]
+
shifted_map = {}
for normal, shifted in shifted_list:
shifted_map[normal] = shifted
-
-
- rows = [row_0,row_1,row_2,row_3,row_4,row_5,row_6]
+ rows = [row_0, row_1, row_2, row_3, row_4, row_5, row_6]
current_row = 0
- self._key_map = {} # map of key (scancode, extended) to key name
- self._key_widget_map = {} # map of key (scancode, extended) to widget
+ self._key_map = {} # map of key (scancode, extended) to key name
+ self._key_widget_map = {} # map of key (scancode, extended) to widget
self._hooked = False
self._read_only = False
@@ -241,7 +411,7 @@ def __init__(self, parent = None):
key = None
key_name = None
column_span = 1
- row_span= 1
+ row_span = 1
for item in data:
if not found_key:
key = item
@@ -266,7 +436,7 @@ def __init__(self, parent = None):
key_name = key.lower()
key_complex = False
column_span = 1
- row_span= 1
+ row_span = 1
if key:
if key in shifted_map.keys():
@@ -276,97 +446,93 @@ def __init__(self, parent = None):
icon = None
# handle special key names
- tooltip = ""
if key == "mouse_1":
key = "M1"
icon = "mdi.mouse"
- toolltip = "Left Mouse Button"
elif key == "mouse_2":
key = "M2"
icon = "mdi.mouse"
- toolltip = "Middle Mouse Button"
elif key == "mouse_3":
key = "M3"
icon = "mdi.mouse"
- toolltip = "Right Mouse Button"
elif key == "mouse_4":
key = "M4"
icon = "mdi.mouse"
- toolltip = "Forward Mouse Button"
elif key == "mouse_5":
key = "M5"
icon = "mdi.mouse"
- toolltip = "Back Mouse Button"
elif key == "wheel_up":
key = "MWU"
icon = "mdi.mouse"
- toolltip = "Wheel Up"
elif key == "wheel_down":
key = "MWD"
icon = "mdi.mouse"
- toolltip = "Wheel Down"
elif key == "wheel_left":
key = "MWL"
- icon = "mdi.mouse"
- toolltip = "Tilt Left"
+ icon = "mdi.mouse"
elif key == "wheel_right":
key = "MWR"
icon = "mdi.mouse"
- toolltip = "Tilt Right"
-
+
widget = QKeyWidget(key)
if icon:
widget.setIcon(load_icon(icon))
- widget.setIconSize(QtCore.QSize(14,14))
+ widget.setIconSize(QtCore.QSize(14, 14))
action_key = gremlin.keyboard.key_from_name(key_name)
- widget.key = action_key # this name must be defined in keybpoard.py
+ widget.key = action_key # this name must be defined in keybpoard.py
widget.normal_key = key
widget.shifted_key = shifted if shifted else widget.normal_key
-
+
widget.clicked.connect(self._widget_clicked_cb)
widget.hover.connect(self._key_hover_cb)
- #syslog.info(f"{key_name}: {key} {shifted}")
- self._key_map[(action_key.scan_code, action_key.is_extended)] = key_name
- self._key_widget_map[(action_key.scan_code, action_key.is_extended)] = widget
+ # syslog.info(f"{key_name}: {key} {shifted}")
+ self._key_map[(action_key.scan_code, action_key.is_extended)] = (
+ key_name
+ )
+ self._key_widget_map[
+ (action_key.scan_code, action_key.is_extended)
+ ] = widget
+
+ assert (
+ key_name not in self._key_widget_map.keys()
+ ), f"duplicate key in keyboard map found: {key_name}"
- assert key_name not in self._key_widget_map.keys(),f"duplicate key in keyboard map found: {key_name}"
-
-
self._key_widget_map[key_name] = widget
key_widgets.append(widget)
else:
widget = QtWidgets.QLabel(" ")
- grid_layout.addWidget(widget, current_row, current_column, row_span, column_span)
-
-
+ grid_layout.addWidget(
+ widget, current_row, current_column, row_span, column_span
+ )
+
# bump column
current_column += column_span
# bump next row
current_column = 0
- current_row +=1
-
+ current_row += 1
self.key_description = QtWidgets.QLabel()
main_layout.addWidget(self.key_description)
# ensure widgets have a minimum size
- widget : QtWidgets.QWidget
+ widget: QtWidgets.QWidget
- min_w = max(widget.minimumSizeHint().width() for widget in key_widgets if len(widget.text()) <= 5)
+ min_w = max(
+ widget.minimumSizeHint().width()
+ for widget in key_widgets
+ if len(widget.text()) <= 5
+ )
for widget in key_widgets:
widget.setMinimumWidth(min_w)
-
def eventFilter(self, widget, event):
t = event.type()
- if t in (QtCore.QEvent.Type.KeyPress, QtCore.QEvent.Type.KeyRelease):
+ if t in (QtCore.QEvent.Type.KeyPress, QtCore.QEvent.Type.KeyRelease):
return True
return super().eventFilter(widget, event)
-
-
@QtCore.Slot()
def _clear_repeater(self):
self._repeater_list.clear()
@@ -378,20 +544,21 @@ def _capture_mouse_changed(self, checked):
self.config.keyboard_repeater_capture_mouse = checked
if not checked:
# clear mouse widgets
- mouse_names = ["mouse_1",
- "mouse_2",
- "mouse_3",
- "mouse_4",
- "mouse_5",
- "wheel_right",
- "wheel_left",
- "wheel_up",
- "wheel_down"]
+ mouse_names = [
+ "mouse_1",
+ "mouse_2",
+ "mouse_3",
+ "mouse_4",
+ "mouse_5",
+ "wheel_right",
+ "wheel_left",
+ "wheel_up",
+ "wheel_down",
+ ]
for map_key in mouse_names:
widget = self._key_widget_map[map_key]
widget.selected = False
-
@QtCore.Slot(bool)
def _invert_display_changed(self, checked):
self._invert_display = checked
@@ -400,7 +567,7 @@ def _invert_display_changed(self, checked):
self._update_repeater()
def setReadonly(self, value: bool):
- ''' set readonly flag - when readonly - acts as a repeater only (cannot be interacted with)'''
+ """set readonly flag - when readonly - acts as a repeater only (cannot be interacted with)"""
self._read_only = value
def isReadonly(self) -> bool:
@@ -412,20 +579,19 @@ def _key_hover_cb(self, widget, hover):
else:
self.key_description.setText("")
- def setRepeaterVisible(self, value : bool):
- ''' turns repeater on/off'''
+ def setRepeaterVisible(self, value: bool):
+ """turns repeater on/off"""
self._show_repeater = value
self.config.keyboard_repeater_show = value
self.repeater_container_widget.setVisible(value)
- def setRepeaterLineCount(self, value : int):
+ def setRepeaterLineCount(self, value: int):
if value >= 1:
self._repeater_lines = value
self._update_repeater()
-
def _widget_clicked_cb(self):
- ''' occurs when the widget is selected'''
+ """occurs when the widget is selected"""
if self._read_only:
# no interaction
return
@@ -437,19 +603,29 @@ def _widget_clicked_cb(self):
if self._select_single:
# single select mode
source_modifier = False
- if self._allow_modifiers and current_widget.key.lookup_name in self._modifier_keys:
+ if (
+ self._allow_modifiers
+ and current_widget.key.lookup_name in self._modifier_keys
+ ):
source_modifier = True
if not source_modifier:
- selected_widgets = [widget for widget in self._key_widget_map.values() if widget.selected]
+ selected_widgets = [
+ widget
+ for widget in self._key_widget_map.values()
+ if widget.selected
+ ]
for widget in selected_widgets:
- if self._allow_modifiers and widget.key.lookup_name in self._modifier_keys:
+ if (
+ self._allow_modifiers
+ and widget.key.lookup_name in self._modifier_keys
+ ):
continue
- widget.selected = False # deselect
-
+ widget.selected = False # deselect
def hook(self):
- ''' hooks to keyboard / mouse events '''
+ """hooks to keyboard / mouse events"""
import gremlin.windows_event_hook
+
if not self._hooked:
el = gremlin.event_handler.EventListener()
el.keyboard_event.connect(self._keyboard_handler)
@@ -457,9 +633,8 @@ def hook(self):
self.mouse_hook.register(self._mouse_handler)
self._hooked = True
-
def unhook(self):
- ''' unhooks keyboard / mouse events '''
+ """unhooks keyboard / mouse events"""
if self._hooked:
el = gremlin.event_handler.EventListener()
el.keyboard_event.disconnect(self._keyboard_handler)
@@ -467,16 +642,14 @@ def unhook(self):
self.mouse_hook.unregister(self._mouse_handler)
self._hooked = False
-
-
@QtCore.Slot(object)
def _keyboard_handler(self, event):
- ''' handles an inbound API key '''
+ """handles an inbound API key"""
key = gremlin.keyboard.KeyMap.from_event(event)
if key is not None:
map_key = key.key_id
if map_key is not None:
- if not map_key in self._key_widget_map:
+ if map_key not in self._key_widget_map:
# see if a translation is needed
map_key, vk = gremlin.keyboard.KeyMap.translate(map_key)
if map_key in self._key_widget_map:
@@ -486,7 +659,7 @@ def _keyboard_handler(self, event):
if self._show_repeater:
self._add_repeater(key, is_pressed)
return
-
+
# output error message
now = datetime.datetime.now()
timestamp = now.strftime("%H:%M:%S")
@@ -497,12 +670,9 @@ def _keyboard_handler(self, event):
except Exception as ex:
line = f"{timestamp}: [ERROR] {ex}"
self._add_line(line)
-
-
-
def _mouse_handler(self, event):
- ''' mouse handler '''
+ """mouse handler"""
if not self._capture_mouse:
return
# mouse special case
@@ -511,11 +681,11 @@ def _mouse_handler(self, event):
map_key = "mouse_1"
case gremlin.types.MouseButton.Right:
map_key = "mouse_2"
- case gremlin.types.MouseButton.Middle:
+ case gremlin.types.MouseButton.Middle:
map_key = "mouse_3"
- case gremlin.types.MouseButton.Back:
+ case gremlin.types.MouseButton.Back:
map_key = "mouse_5"
- case gremlin.types.MouseButton.Forward:
+ case gremlin.types.MouseButton.Forward:
map_key = "mouse_4"
case gremlin.types.MouseButton.WheelRight:
map_key = "wheel_right"
@@ -527,7 +697,7 @@ def _mouse_handler(self, event):
map_key = "wheel_down"
case _:
return
-
+
if map_key is not None and map_key in self._key_widget_map:
widget = self._key_widget_map[map_key]
is_pressed = event.is_pressed
@@ -536,9 +706,9 @@ def _mouse_handler(self, event):
key = gremlin.keyboard.key_from_name(map_key)
self._add_repeater(key, is_pressed)
- def _add_repeater(self, key, is_pressed : bool ):
- ''' adds a key to the repeater '''
-
+ def _add_repeater(self, key, is_pressed: bool):
+ """adds a key to the repeater"""
+
now = datetime.datetime.now()
timestamp = now.strftime("%H:%M:%S")
interval = None
@@ -550,21 +720,19 @@ def _add_repeater(self, key, is_pressed : bool ):
if key_id in self._repeater_timestamp:
interval = time.time() - self._repeater_timestamp[key_id]
-
line = f"{timestamp}: [{key.name}] 0x{key.scan_code:X} ({key.scan_code}) {'[EX]' if key.is_extended else ''} {'pressed' if is_pressed else 'released'}"
if interval is not None:
line += f" ({int(interval*1000)} ms)"
-
+
self._add_line(line)
-
- def _add_line(self, line : str):
- ''' adds a line to the display '''
+ def _add_line(self, line: str):
+ """adds a line to the display"""
count = len(self._repeater_list)
if count > self._repeater_lines:
if self._invert_display:
- self._repeater_list.pop(-1)
+ self._repeater_list.pop(-1)
else:
self._repeater_list.pop(0)
@@ -575,50 +743,62 @@ def _add_line(self, line : str):
self.keyEvent.emit()
def _update_repeater(self):
- text = ''.join(line + "\n" for line in self._repeater_list)
+ text = "".join(line + "\n" for line in self._repeater_list)
self.repeater_widget.setPlainText(text)
cursor = self.repeater_widget.textCursor()
-
+
if self._invert_display:
# move cursor to top
- cursor.movePosition(QtGui.QTextCursor.MoveOperation.Start, QtGui.QTextCursor.MoveMode.MoveAnchor)
+ cursor.movePosition(
+ QtGui.QTextCursor.MoveOperation.Start,
+ QtGui.QTextCursor.MoveMode.MoveAnchor,
+ )
else:
- cursor.movePosition(QtGui.QTextCursor.MoveOperation.End, QtGui.QTextCursor.MoveMode.MoveAnchor)
+ cursor.movePosition(
+ QtGui.QTextCursor.MoveOperation.End,
+ QtGui.QTextCursor.MoveMode.MoveAnchor,
+ )
self.repeater_widget.setTextCursor(cursor)
-
-
class InputKeyboardDialog(gremlin.ui.ui_common.QRememberDialog):
- ''' dialog showing a virtual keyboard in which to select key combinations with the keyboard or mouse '''
-
- closed = QtCore.Signal() # sent when the dialog closes
-
- def __init__(self, sequence = None, parent = None, select_single = False, allow_modifiers = True, index = None):
- '''
+ """dialog showing a virtual keyboard in which to select key combinations with the keyboard or mouse"""
+
+ closed = QtCore.Signal() # sent when the dialog closes
+
+ def __init__(
+ self,
+ sequence=None,
+ parent=None,
+ select_single=False,
+ allow_modifiers=True,
+ index=None,
+ ):
+ """
:param sequence - input keys to use
:param select_single - if set, only can select a single key
:param allow_modifiers - if set - modifier keys along with regular keys are allowed
- '''
- super().__init__(self.__class__.__name__,parent = parent)
+ """
+ super().__init__(self.__class__.__name__, parent=parent)
# self._sequence = InputKeyboardModel(sequence=sequence)
main_layout = QtWidgets.QVBoxLayout()
self.setWindowTitle("Keyboard & Mouse Input Mapper")
self._select_single = select_single
self._allow_modifiers = allow_modifiers
self.index = index
- self._latched_key = None # contains a single primary key latched to all the others
+ self._latched_key = (
+ None # contains a single primary key latched to all the others
+ )
self._display_shifted = False
self._solo_select = False
-
self._modifier_keys = gremlin.keyboard.KeyMap._keyboard_modifiers
- self._key_map = {} # map of (scancode, extended) to keys (scancode, extended) -> key
- self._key_widget_map = {} # map of keys to widgets key -> widget
- self.keyboard_widget = self._create_keyboard_widget() # populate the two maps
- self._keys = None # return data
- self._display_shifted = False # true if displayed shifted
+ self._key_map = {} # map of (scancode, extended) to keys (scancode, extended) -> key
+ self._key_widget_map = {} # map of keys to widgets key -> widget
+ self.keyboard_widget = self._create_keyboard_widget() # populate the two maps
+ self._keys = None # return data
+ self._display_shifted = False # true if displayed shifted
self.button_widget = QtWidgets.QWidget()
self.button_layout = QtWidgets.QHBoxLayout()
@@ -631,28 +811,29 @@ def __init__(self, sequence = None, parent = None, select_single = False, allow_
self.listen_widget = QtWidgets.QPushButton("Listen")
self.listen_widget.clicked.connect(self._listen_cb)
- widgets = [gremlin.ui.ui_common.QDataRadioButton("Small",1),
- gremlin.ui.ui_common.QDataRadioButton("Large",2)
+ widgets = [
+ gremlin.ui.ui_common.QDataRadioButton("Small", 1),
+ gremlin.ui.ui_common.QDataRadioButton("Large", 2),
]
- current = gremlin.config.Configuration().keySize
+ current = gremlin.config.Configuration().keySize
if current > len(widgets):
current = 1
gremlin.config.Configuration().keySize = 1
- widgets[current-1].setChecked(True)
+ widgets[current - 1].setChecked(True)
for widget in self._key_widget_map.values():
widget.keySize = current
for w in widgets:
w.clicked.connect(self._size_changed)
-
self.size_container_widget, _ = gremlin.ui.ui_common.getHContainer(widgets)
-
self.numlock_widget = QtWidgets.QCheckBox("Force numlock Off")
- self.numlock_widget.setChecked(gremlin.shared_state.current_profile.get_force_numlock())
+ self.numlock_widget.setChecked(
+ gremlin.shared_state.current_profile.get_force_numlock()
+ )
self.numlock_widget.clicked.connect(self._force_numlock_cb)
self.key_description = QtWidgets.QLabel()
@@ -672,15 +853,13 @@ def __init__(self, sequence = None, parent = None, select_single = False, allow_
self.button_layout.addWidget(self.ok_widget)
self.button_layout.addWidget(self.cancel_widget)
-
main_layout.addWidget(self.keyboard_widget)
main_layout.addWidget(gremlin.ui.ui_common.QHLine())
main_layout.addWidget(self.button_widget)
-
self.setLayout(main_layout)
- self._set_sequence(sequence)
+ self._set_sequence(sequence)
def closeEvent(self, event):
self.closed.emit()
@@ -688,18 +867,17 @@ def closeEvent(self, event):
@property
def latched_key(self):
- ''' contains a single key which represents the latched selection in the dialog '''
+ """contains a single key which represents the latched selection in the dialog"""
return self._latched_key
-
+
@property
def keys(self):
- ''' list of raw selected keys '''
+ """list of raw selected keys"""
return self._keys
-
def _set_sequence(self, sequence):
- ''' loads a given key sequence into the virtual keyboard '''
-
+ """loads a given key sequence into the virtual keyboard"""
+
if sequence:
# the action keeps a list of keys in the format (scancode, extended_flag)
# convert that to a key from it and selected it if the key is mapped
@@ -718,7 +896,7 @@ def _set_sequence(self, sequence):
# lookup = gremlin.keyboard.KeyMap.translate_lookup(lookup)
# if lookup in self._key_map:
key_name = self._key_map[lookup]
- #key_name = self._key_map[lookup]
+ # key_name = self._key_map[lookup]
widget = self._key_widget_map[key_name]
widget.selected = True
# else:
@@ -726,14 +904,16 @@ def _set_sequence(self, sequence):
elif isinstance(item, tuple):
# key id
lookup = item
- if not lookup in self._key_map:
+ if lookup not in self._key_map:
lookup = gremlin.keyboard.KeyMap.translate_lookup(lookup)
if lookup in self._key_map:
key_name = self._key_map[lookup]
widget = self._key_widget_map[key_name]
widget.selected = True
else:
- syslog.error(f"VIRTUAL KEYBOARD: key {lookup} not found in keyboard map ")
+ syslog.error(
+ f"VIRTUAL KEYBOARD: key {lookup} not found in keyboard map "
+ )
elif isinstance(item, int):
# virtual code
key = gremlin.keyboard.KeyMap.find_virtual(item)
@@ -746,7 +926,9 @@ def _set_sequence(self, sequence):
widget.selected = True
else:
# log the fact we didn't find the key in the keyboard dialog
- syslog.warning(f"Keyboard: unable to find {item} in dialog keyboard")
+ syslog.warning(
+ f"Keyboard: unable to find {item} in dialog keyboard"
+ )
def _force_numlock_cb(self, checked):
gremlin.shared_state.current_profile.set_force_numlock(checked)
@@ -758,13 +940,16 @@ def _listen_cb(self):
Asks the user to press the key they wish to add bindings for.
"""
from gremlin.ui.ui_common import InputListenerWidget
+
self.button_press_dialog = InputListenerWidget(
[InputType.Keyboard],
return_kb_event=False,
- multi_keys=True # allow key combinations
+ multi_keys=True, # allow key combinations
)
- self.button_press_dialog.item_selected.connect(self._add_keyboard_listener_key_cb)
+ self.button_press_dialog.item_selected.connect(
+ self._add_keyboard_listener_key_cb
+ )
# Display the dialog centered in the middle of the UI
root = self
@@ -776,7 +961,7 @@ def _listen_cb(self):
int(geom.x() + geom.width() / 2 - 150),
int(geom.y() + geom.height() / 2 - 75),
300,
- 150
+ 150,
)
self.button_press_dialog.show()
@@ -790,9 +975,7 @@ def _size_changed(self):
w.keySize = data
# resize window to fit the new size
QtCore.QTimer.singleShot(0, self.adjustSize)
-
-
@QtCore.Slot()
def _add_keyboard_listener_key_cb(self, keys):
"""Adds the provided key to the list of keys.
@@ -803,14 +986,13 @@ def _add_keyboard_listener_key_cb(self, keys):
# the new entry will be a new index
self._set_sequence(keys)
-
def keyPressEvent(self, event):
key = event.key()
if key == QtCore.Qt.Key.Key_Shift:
self.display_shifted = True
elif key == QtCore.Qt.Key.Key_Control:
self.solo_select = True
-
+
super().keyPressEvent(event)
def keyReleaseEvent(self, event):
@@ -819,12 +1001,12 @@ def keyReleaseEvent(self, event):
self.display_shifted = False
elif key == QtCore.Qt.Key.Key_Control:
self.solo_select = False
- super().keyReleaseEvent(event)
+ super().keyReleaseEvent(event)
@property
def solo_select(self):
return self._solo_select
-
+
@solo_select.setter
def solo_select(self, value):
self._solo_select = value
@@ -835,23 +1017,24 @@ def display_shifted(self):
@display_shifted.setter
def display_shifted(self, value):
- ''' changes the display mode of the keyboard to shifted/unshifted'''
+ """changes the display mode of the keyboard to shifted/unshifted"""
if value != self._display_shifted:
for widget in self._key_widget_map.values():
if widget.normal_key == "9":
- pass
+ pass
if widget.shifted_key != widget.normal_key:
# only updates those that are different in shifted form
widget.setText(widget.shifted_key if value else widget.normal_key)
- #widget.update()
+ # widget.update()
self._display_shifted = value
-
def _ok_button_cb(self):
- ''' ok button pressed '''
-
+ """ok button pressed"""
+
# keys = [Key(scan_code = widget.key.scan_code, is_extended = widget.key.is_extended, is_mouse=widget.key.is_mouse, virtual_code=widget.key.virtual_code) for widget in self._key_widget_map.values() if widget.selected]
- selected_widgets = [widget for widget in self._key_widget_map.values() if widget.selected]
+ selected_widgets = [
+ widget for widget in self._key_widget_map.values() if widget.selected
+ ]
keys = []
for widget in selected_widgets:
key = widget.key.duplicate()
@@ -866,56 +1049,229 @@ def _ok_button_cb(self):
self._keys = keys
return_key = gremlin.keyboard.KeyMap.get_latched_key(keys)
-
+
# print (f"Return key: {return_key}")
self._latched_key = return_key
-
self.accept()
self.close()
-
def _cancel_button_cb(self):
- ''' cancel button pressed '''
+ """cancel button pressed"""
self.reject()
self.close()
def _clear_button_cb(self):
- ''' clear button pressed - clear all entries '''
+ """clear button pressed - clear all entries"""
for widget in self._key_widget_map.values():
widget.selected = False
- def _create_keyboard_widget(self, parent = None):
- ''' creates a full keyboard widget for manual data entry '''
-
+ def _create_keyboard_widget(self, parent=None):
+ """creates a full keyboard widget for manual data entry"""
+
grid_layout = QtWidgets.QGridLayout()
grid_layout.setSpacing(2)
# grid_layout.setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetFixedSize)
-
+
# list of scancodes https://handmade.network/forums/articles/t/2823-keyboard_inputs_-_scancodes%252C_raw_input%252C_text_input%252C_key_names
# first row = QUERTY object
- row_0 = ["","","F13","F14","F15","F16","F17","F18","F19","F20","F21","F22","F23","F24","","mouse_1","mouse_2","mouse_3","","mouse_4","mouse_5","wheel_up","wheel_down"]
- row_1 = ["Esc","","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","",["PrtSc","printscreen"],["Scrlck","scrolllock"],["Pause","pause"],"","","","wheel_left","wheel_right"]
- row_2 = ["`","1","2","3","4","5","6","7","8","9","0","-","=",["Back","backspace"],"",["Ins","insert"],["Home","home"],["PgUp","pageup"],"",["NLck","numlock"],["/","npdivide"],["*","npmultiply"],["-","npminus"]]
- row_3 = [["Tab","tab"],"Q","W","E","R","T","Y","U","I","O","P","[","]","\\","",["Del","delete"],"End",["PgDn","pagedown"],"",["7","np7"],["8","np8"],["9","np9"],["+","npplus",1,2]]
- row_4 = [["CpLck","capslock"],"A","S","D","F","G","H","J","K","L",";","'",["Enter",2],"","","","","",["4","np4"],["5","np5"],["6","np6"]]
- row_5 = [["LShift","leftshift"],"Z","X","C","V","B","N","M",",",".","/",["RShift","rightshift"],"","","","","up","","",["1","np1"],["2","np2"],["3","np3"],["Enter","npenter",1,2]]
- row_6 = [["LCtrl","leftcontrol"],["LWin","leftwin"],["LAlt","leftalt"],["Spacebar","space",6],["RAlt","rightalt2"],["RWin","rightwin"],["RCtrl","rightcontrol"],"","","","left","down","right","",["0/Ins","np0",2],["./Del","npdelete"]]
+ row_0 = [
+ "",
+ "",
+ "F13",
+ "F14",
+ "F15",
+ "F16",
+ "F17",
+ "F18",
+ "F19",
+ "F20",
+ "F21",
+ "F22",
+ "F23",
+ "F24",
+ "",
+ "mouse_1",
+ "mouse_2",
+ "mouse_3",
+ "",
+ "mouse_4",
+ "mouse_5",
+ "wheel_up",
+ "wheel_down",
+ ]
+ row_1 = [
+ "Esc",
+ "",
+ "F1",
+ "F2",
+ "F3",
+ "F4",
+ "F5",
+ "F6",
+ "F7",
+ "F8",
+ "F9",
+ "F10",
+ "F11",
+ "F12",
+ "",
+ ["PrtSc", "printscreen"],
+ ["Scrlck", "scrolllock"],
+ ["Pause", "pause"],
+ "",
+ "",
+ "",
+ "wheel_left",
+ "wheel_right",
+ ]
+ row_2 = [
+ "`",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "0",
+ "-",
+ "=",
+ ["Back", "backspace"],
+ "",
+ ["Ins", "insert"],
+ ["Home", "home"],
+ ["PgUp", "pageup"],
+ "",
+ ["NLck", "numlock"],
+ ["/", "npdivide"],
+ ["*", "npmultiply"],
+ ["-", "npminus"],
+ ]
+ row_3 = [
+ ["Tab", "tab"],
+ "Q",
+ "W",
+ "E",
+ "R",
+ "T",
+ "Y",
+ "U",
+ "I",
+ "O",
+ "P",
+ "[",
+ "]",
+ "\\",
+ "",
+ ["Del", "delete"],
+ "End",
+ ["PgDn", "pagedown"],
+ "",
+ ["7", "np7"],
+ ["8", "np8"],
+ ["9", "np9"],
+ ["+", "npplus", 1, 2],
+ ]
+ row_4 = [
+ ["CpLck", "capslock"],
+ "A",
+ "S",
+ "D",
+ "F",
+ "G",
+ "H",
+ "J",
+ "K",
+ "L",
+ ";",
+ "'",
+ ["Enter", 2],
+ "",
+ "",
+ "",
+ "",
+ "",
+ ["4", "np4"],
+ ["5", "np5"],
+ ["6", "np6"],
+ ]
+ row_5 = [
+ ["LShift", "leftshift"],
+ "Z",
+ "X",
+ "C",
+ "V",
+ "B",
+ "N",
+ "M",
+ ",",
+ ".",
+ "/",
+ ["RShift", "rightshift"],
+ "",
+ "",
+ "",
+ "",
+ "up",
+ "",
+ "",
+ ["1", "np1"],
+ ["2", "np2"],
+ ["3", "np3"],
+ ["Enter", "npenter", 1, 2],
+ ]
+ row_6 = [
+ ["LCtrl", "leftcontrol"],
+ ["LWin", "leftwin"],
+ ["LAlt", "leftalt"],
+ ["Spacebar", "space", 6],
+ ["RAlt", "rightalt2"],
+ ["RWin", "rightwin"],
+ ["RCtrl", "rightcontrol"],
+ "",
+ "",
+ "",
+ "left",
+ "down",
+ "right",
+ "",
+ ["0/Ins", "np0", 2],
+ ["./Del", "npdelete"],
+ ]
shifted_list = [
- ("`","~"),("1","!"),("2","@"),("3","#"),("4","$"),("5","%"),("6","^"),
- ("7","&&"),("8","*"),("9","("),("0",")"),("-","_"),("=","+"),
- ("[","{"),("]","}"),("\\","|"),(";",":"),("'","\""),(",","<"),(".",">"),("/","?")
- ]
-
+ ("`", "~"),
+ ("1", "!"),
+ ("2", "@"),
+ ("3", "#"),
+ ("4", "$"),
+ ("5", "%"),
+ ("6", "^"),
+ ("7", "&&"),
+ ("8", "*"),
+ ("9", "("),
+ ("0", ")"),
+ ("-", "_"),
+ ("=", "+"),
+ ("[", "{"),
+ ("]", "}"),
+ ("\\", "|"),
+ (";", ":"),
+ ("'", '"'),
+ (",", "<"),
+ (".", ">"),
+ ("/", "?"),
+ ]
+
shifted_map = {}
for normal, shifted in shifted_list:
shifted_map[normal] = shifted
-
-
- rows = [row_0,row_1,row_2,row_3,row_4,row_5,row_6]
+ rows = [row_0, row_1, row_2, row_3, row_4, row_5, row_6]
current_row = 0
self._key_map = {}
@@ -933,7 +1289,7 @@ def _create_keyboard_widget(self, parent = None):
key = None
key_name = None
column_span = 1
- row_span= 1
+ row_span = 1
for item in data:
if not found_key:
key = item
@@ -958,7 +1314,7 @@ def _create_keyboard_widget(self, parent = None):
key_name = key.lower()
key_complex = False
column_span = 1
- row_span= 1
+ row_span = 1
if key:
if key in shifted_map.keys():
@@ -972,86 +1328,92 @@ def _create_keyboard_widget(self, parent = None):
if key == "mouse_1":
key = "M1"
icon = "mdi.mouse"
- toolltip = "Left Mouse Button"
+ tooltip = "Left Mouse Button"
elif key == "mouse_2":
key = "M2"
icon = "mdi.mouse"
- toolltip = "Middle Mouse Button"
+ tooltip = "Middle Mouse Button"
elif key == "mouse_3":
key = "M3"
icon = "mdi.mouse"
- toolltip = "Right Mouse Button"
+ tooltip = "Right Mouse Button"
elif key == "mouse_4":
key = "M4"
icon = "mdi.mouse"
- toolltip = "Forward Mouse Button"
+ tooltip = "Forward Mouse Button"
elif key == "mouse_5":
key = "M5"
icon = "mdi.mouse"
- toolltip = "Back Mouse Button"
+ tooltip = "Back Mouse Button"
elif key == "wheel_up":
key = "MWU"
icon = "mdi.mouse"
- toolltip = "Wheel Up"
+ tooltip = "Wheel Up"
elif key == "wheel_down":
key = "MWD"
icon = "mdi.mouse"
- toolltip = "Wheel Down"
+ tooltip = "Wheel Down"
elif key == "wheel_left":
key = "MWL"
- icon = "mdi.mouse"
- toolltip = "Tilt Left"
+ icon = "mdi.mouse"
+ tooltip = "Tilt Left"
elif key == "wheel_right":
key = "MWR"
icon = "mdi.mouse"
- toolltip = "Tilt Right"
-
+ tooltip = "Tilt Right"
+
widget = QKeyWidget(key)
if icon:
widget.setIcon(load_icon(icon))
- widget.setIconSize(QtCore.QSize(14,14))
-
+ widget.setIconSize(QtCore.QSize(14, 14))
+ widget.setToolTip(tooltip)
action_key = gremlin.keyboard.key_from_name(key_name)
- widget.key = action_key # this name must be defined in keybpoard.py
+ widget.key = action_key # this name must be defined in keybpoard.py
widget.normal_key = key
widget.shifted_key = shifted if shifted else widget.normal_key
-
+
widget.clicked.connect(self._widget_clicked_cb)
widget.hover.connect(self._key_hover_cb)
- #syslog.info(f"{key_name}: {key} {shifted}")
- self._key_map[(action_key.scan_code, action_key.is_extended)] = key_name
- assert key_name not in self._key_widget_map.keys(),f"duplicate key in keyboard map found: {key_name}"
+ # syslog.info(f"{key_name}: {key} {shifted}")
+ self._key_map[(action_key.scan_code, action_key.is_extended)] = (
+ key_name
+ )
+ assert (
+ key_name not in self._key_widget_map.keys()
+ ), f"duplicate key in keyboard map found: {key_name}"
-
self._key_widget_map[key_name] = widget
key_widgets.append(widget)
else:
widget = QtWidgets.QLabel(" ")
- grid_layout.addWidget(widget, current_row, current_column, row_span, column_span)
-
-
+ grid_layout.addWidget(
+ widget, current_row, current_column, row_span, column_span
+ )
+
# bump column
current_column += column_span
# bump next row
current_column = 0
- current_row +=1
-
+ current_row += 1
grid_widget = QtWidgets.QWidget(parent)
grid_widget.setLayout(grid_layout)
# ensure widgets have a minimum size
- widget : QtWidgets.QWidget
+ widget: QtWidgets.QWidget
- min_w = max(widget.minimumSizeHint().width() for widget in key_widgets if len(widget.text()) <= 5)
+ min_w = max(
+ widget.minimumSizeHint().width()
+ for widget in key_widgets
+ if len(widget.text()) <= 5
+ )
for widget in key_widgets:
widget.setMinimumWidth(min_w)
-
return grid_widget
-
+
def _key_hover_cb(self, widget, hover):
if hover:
self.key_description.setText(widget.display_name)
@@ -1059,14 +1421,15 @@ def _key_hover_cb(self, widget, hover):
self.key_description.setText("")
def deselect(self):
- ''' deselects all keys '''
- selected_widgets = [widget for widget in self._key_widget_map.values() if widget.selected]
+ """deselects all keys"""
+ selected_widgets = [
+ widget for widget in self._key_widget_map.values() if widget.selected
+ ]
for widget in selected_widgets:
widget.selected = False
-
def _widget_clicked_cb(self):
- ''' occurs when the widget is selected'''
+ """occurs when the widget is selected"""
current_widget = self.sender()
if self.solo_select:
# deselect all
@@ -1075,15 +1438,23 @@ def _widget_clicked_cb(self):
if self._select_single:
# single select mode
source_modifier = False
- if self._allow_modifiers and current_widget.key.lookup_name in self._modifier_keys:
+ if (
+ self._allow_modifiers
+ and current_widget.key.lookup_name in self._modifier_keys
+ ):
source_modifier = True
if not source_modifier:
- selected_widgets = [widget for widget in self._key_widget_map.values() if widget.selected]
+ selected_widgets = [
+ widget
+ for widget in self._key_widget_map.values()
+ if widget.selected
+ ]
for widget in selected_widgets:
- if self._allow_modifiers and widget.key.lookup_name in self._modifier_keys:
+ if (
+ self._allow_modifiers
+ and widget.key.lookup_name in self._modifier_keys
+ ):
continue
- widget.selected = False # deselect
+ widget.selected = False # deselect
-
- current_widget.selected = not current_widget.selected # toggle
-
\ No newline at end of file
+ current_widget.selected = not current_widget.selected # toggle
diff --git a/gremlin/user_plugin.py b/gremlin/user_plugin.py
index 50460e4d..43fd7bfa 100644
--- a/gremlin/user_plugin.py
+++ b/gremlin/user_plugin.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,26 +16,30 @@
# along with this program. If not, see .
-import enum
import importlib
import inspect
import logging
import os
import random
import string
-import uuid
import sys
-from PySide6 import QtCore, QtGui, QtWidgets
+from PySide6 import QtCore, QtWidgets
import dinput
-from gremlin import common, error, input_devices, joystick_handling, profile, shared_state
+from gremlin import (
+ error,
+ joystick_handling,
+ profile,
+ shared_state,
+)
import gremlin.ui.ui_common
from gremlin.input_types import InputType
import gremlin.types
syslog = logging.getLogger("system")
+
def load_module(module_name, file_path):
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
@@ -43,6 +47,7 @@ def load_module(module_name, file_path):
spec.loader.exec_module(module)
return module
+
def get_variable_definitions(fname):
"""Returns all variable definitions contained in the provided module.
@@ -59,24 +64,23 @@ def get_variable_definitions(fname):
"""
if not os.path.isfile(fname):
return {}
-
+
user_package = "user_plugins"
- spec = importlib.util.spec_from_file_location(user_package + "." +
- "".join(random.choices(string.ascii_lowercase, k=16)),
- fname
+ spec = importlib.util.spec_from_file_location(
+ user_package + "." + "".join(random.choices(string.ascii_lowercase, k=16)),
+ fname,
)
# see if there is a package to load
- fname_init = os.path.join(os.path.dirname(fname),"__init__.py")
+ fname_init = os.path.join(os.path.dirname(fname), "__init__.py")
if not os.path.isfile(fname_init):
# create the file so we have a package
- open (fname_init,'a').close
-
+ open(fname_init, "a").close
+
# load the package for the plugins
load_module("user_plugins", fname_init)
-
-
+
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
@@ -114,7 +118,6 @@ def clamp_value(value, min_val, max_val):
class VariableRegistry:
-
"""Stores variables of plugin instances."""
def __init__(self):
@@ -218,7 +221,6 @@ def _init_numerical(var, default_value, min_value, max_value):
class AbstractVariable(QtCore.QObject):
-
"""Represents the base class of all variables used in plugins."""
# Signal emitted when the value of the variable changes
@@ -278,11 +280,7 @@ def _load_from_registry(self, identifier):
content from the variable registry
"""
if identifier is not None:
- val = variable_registry.get(
- identifier[0],
- identifier[1],
- self.label
- )
+ val = variable_registry.get(identifier[0], identifier[1], self.label)
if val is not None:
self.value = self._process_registry_value(val)
self.variable_set = True
@@ -315,25 +313,26 @@ def _get_identifier(self):
Tuple of (file name, instance name) or None
"""
for frame in inspect.stack():
- identifier = frame.frame.f_locals.get("_CodeRunner__gremlin_identifier", None)
+ identifier = frame.frame.f_locals.get(
+ "_CodeRunner__gremlin_identifier", None
+ )
if identifier is not None:
return identifier
return None
class NumericalVariable(AbstractVariable):
-
"""Base class for numerical variable types."""
def __init__(
- self,
- label,
- description,
- variable_type,
- initial_value=None,
- min_value=None,
- max_value=None,
- is_optional=False
+ self,
+ label,
+ description,
+ variable_type,
+ initial_value=None,
+ min_value=None,
+ max_value=None,
+ is_optional=False,
):
super().__init__(label, description, variable_type, is_optional)
@@ -353,11 +352,9 @@ def create_ui_element(self, value):
if self.variable_type == gremlin.types.PluginVariableType.Int:
value_widget = QtWidgets.QSpinBox()
value_widget.setRange(self.min_value, self.max_value)
- value_widget.setValue(clamp_value(
- int(value),
- self.min_value,
- self.max_value
- ))
+ value_widget.setValue(
+ clamp_value(int(value), self.min_value, self.max_value)
+ )
value_widget.valueChanged.connect(
lambda x: self.value_changed.emit({"value": x})
)
@@ -380,27 +377,24 @@ def create_ui_element(self, value):
def _process_registry_value(self, value):
return clamp_value(
- _cast_variable[self.variable_type](value),
- self.min_value,
- self.max_value
+ _cast_variable[self.variable_type](value), self.min_value, self.max_value
)
-
+
def __str__(self):
return f"NumericalVariable: {self.description} min: {self.min_value} max: {self.max_value} value: {self.value}"
class IntegerVariable(NumericalVariable):
-
"""Variable representing an integer value."""
def __init__(
- self,
- label,
- description,
- initial_value=None,
- min_value=None,
- max_value=None,
- is_optional=False
+ self,
+ label,
+ description,
+ initial_value=None,
+ min_value=None,
+ max_value=None,
+ is_optional=False,
):
super().__init__(
label,
@@ -409,26 +403,24 @@ def __init__(
initial_value,
min_value,
max_value,
- is_optional
+ is_optional,
)
_init_numerical(self, 0, 0, 10)
self._load_from_registry(self._get_identifier())
-
class FloatVariable(NumericalVariable):
-
"""Variable representing an float value."""
def __init__(
- self,
- label,
- description,
- initial_value=None,
- min_value=None,
- max_value=None,
- is_optional=False
+ self,
+ label,
+ description,
+ initial_value=None,
+ min_value=None,
+ max_value=None,
+ is_optional=False,
):
super().__init__(
label,
@@ -437,7 +429,7 @@ def __init__(
initial_value,
min_value,
max_value,
- is_optional
+ is_optional,
)
_init_numerical(self, 0.0, -1.0, 1.0)
@@ -445,21 +437,11 @@ def __init__(
class BoolVariable(AbstractVariable):
-
"""Variable representing a boolean value."""
- def __init__(
- self,
- label,
- description,
- initial_value=False,
- is_optional=False
- ):
+ def __init__(self, label, description, initial_value=False, is_optional=False):
super().__init__(
- label,
- description,
- gremlin.types.PluginVariableType.Bool,
- is_optional
+ label, description, gremlin.types.PluginVariableType.Bool, is_optional
)
self.value = initial_value
@@ -493,27 +475,17 @@ def create_ui_element(self, value):
def _process_registry_value(self, value):
return value
-
+
def __str__(self):
return f"BoolVariable: {self.description} value: {self.value}"
class StringVariable(AbstractVariable):
-
"""Variable representing a string value."""
- def __init__(
- self,
- label,
- description,
- initial_value=None,
- is_optional=False
- ):
+ def __init__(self, label, description, initial_value=None, is_optional=False):
super().__init__(
- label,
- description,
- gremlin.types.PluginVariableType.String,
- is_optional
+ label, description, gremlin.types.PluginVariableType.String, is_optional
)
self.value = initial_value
@@ -547,20 +519,11 @@ def _process_registry_value(self, value):
class ModeVariable(AbstractVariable):
-
"""Variable representing a mode present in a profile."""
- def __init__(
- self,
- label,
- description,
- is_optional=False
- ):
+ def __init__(self, label, description, is_optional=False):
super().__init__(
- label,
- description,
- gremlin.types.PluginVariableType.Mode,
- is_optional
+ label, description, gremlin.types.PluginVariableType.Mode, is_optional
)
self.value = profile.mode_list(shared_state.current_profile)[0]
@@ -594,7 +557,6 @@ def _process_registry_value(self, value):
class VirtualInputVariable(AbstractVariable):
-
"""Variable representing a vJoy input."""
def __init__(self, label, description, valid_types=None, is_optional=False):
@@ -602,7 +564,7 @@ def __init__(self, label, description, valid_types=None, is_optional=False):
label,
description,
gremlin.types.PluginVariableType.VirtualInput,
- is_optional
+ is_optional,
)
joystick_handling.vjoy_devices()
@@ -612,11 +574,9 @@ def __init__(self, label, description, valid_types=None, is_optional=False):
self.valid_types = [
InputType.JoystickAxis,
InputType.JoystickButton,
- InputType.JoystickHat
+ InputType.JoystickHat,
]
- self.value = joystick_handling.select_first_valid_vjoy_input(
- self.valid_types
- )
+ self.value = joystick_handling.select_first_valid_vjoy_input(self.valid_types)
self._load_from_registry(self._get_identifier())
@@ -656,14 +616,11 @@ def create_ui_element(self, value):
layout.addWidget(label, 0, 0)
value_widget = gremlin.ui.ui_common.VJoySelector(
- lambda data: self.value_changed.emit(data),
- self.valid_types
+ lambda data: self.value_changed.emit(data), self.valid_types
)
if value is not None:
value_widget.set_selection(
- value["input_type"],
- value["device_id"],
- value["input_id"]
+ value["input_type"], value["device_id"], value["input_id"]
)
layout.addWidget(value_widget, 0, 1)
@@ -679,8 +636,8 @@ def _process_registry_value(self, value):
def __str__(self):
return f"VirtualInputVariable: {self.value} vjoy_id: {self.vjoy_id} input_id: {self.input_id} description: {self.description}"
-class PhysicalInputVariable(AbstractVariable):
+class PhysicalInputVariable(AbstractVariable):
"""Variable representing a physical device input."""
def __init__(self, label, description, valid_types=None, is_optional=False):
@@ -688,7 +645,7 @@ def __init__(self, label, description, valid_types=None, is_optional=False):
label,
description,
gremlin.types.PluginVariableType.PhysicalInput,
- is_optional
+ is_optional,
)
self.value = None
@@ -697,7 +654,7 @@ def __init__(self, label, description, valid_types=None, is_optional=False):
self.valid_types = [
InputType.JoystickAxis,
InputType.JoystickButton,
- InputType.JoystickHat
+ InputType.JoystickHat,
]
self._load_from_registry(self._get_identifier())
@@ -730,9 +687,7 @@ def create_decorator(self, mode_name):
)
else:
return gremlin.input_devices.JoystickDecorator(
- self.value["device_name"],
- str(self.value["device_id"]),
- mode_name
+ self.value["device_name"], str(self.value["device_id"]), mode_name
)
def create_ui_element(self, value):
@@ -750,7 +705,7 @@ def create_ui_element(self, value):
)
value_widget.setText(
f"{value["device_name"]} {InputType.to_string(value["input_type"]).capitalize()} {input_id}"
- )
+ )
value_widget.clicked.connect(self._record_user_input)
layout.addWidget(value_widget, 0, 1)
@@ -761,9 +716,7 @@ def create_ui_element(self, value):
return layout
def _record_user_input(self):
- widget = gremlin.ui.ui_common.InputListenerWidget(
- self.valid_types
- )
+ widget = gremlin.ui.ui_common.InputListenerWidget(self.valid_types)
widget.item_selected.connect(self._user_input)
@@ -773,18 +726,20 @@ def _record_user_input(self):
int(geom.x() + geom.width() / 2 - 150),
int(geom.y() + geom.height() / 2 - 75),
300,
- 150
+ 150,
)
widget.show()
def _user_input(self, event):
- self.value_changed.emit({
- "device_id": event.device_guid,
- "device_name": dinput.DILL.get_device_name(event.device_guid),
- "input_id": event.identifier,
- "input_type": event.event_type,
- })
+ self.value_changed.emit(
+ {
+ "device_id": event.device_guid,
+ "device_name": dinput.DILL.get_device_name(event.device_guid),
+ "input_id": event.identifier,
+ "input_type": event.event_type,
+ }
+ )
def _process_registry_value(self, value):
return value
@@ -792,27 +747,19 @@ def _process_registry_value(self, value):
def __str__(self):
return f"PhysicalInputVariable: device_name: {self.device_name} device_id: {str(self.device_guid)} input_id: {self.input_id}"
-class SelectionVariable(AbstractVariable):
+class SelectionVariable(AbstractVariable):
"""Permits selecting a value out of a list of possibilities."""
def __init__(
- self,
- label,
- description,
- option_list,
- default_index=0,
- is_optional=False
+ self, label, description, option_list, default_index=0, is_optional=False
):
super().__init__(
- label,
- description,
- gremlin.types.PluginVariableType.Selection,
- is_optional
+ label, description, gremlin.types.PluginVariableType.Selection, is_optional
)
- assert(isinstance(option_list, list))
- assert(len(option_list) > 0)
+ assert isinstance(option_list, list)
+ assert len(option_list) > 0
self.options = list(sorted(set(option_list)))
self.value = option_list[default_index]
@@ -846,6 +793,8 @@ def create_ui_element(self, value):
layout.setColumnMinimumWidth(0, 150)
return layout
-
+
def __str__(self):
- return f"SelectionVariable: description: {self.description} values: {self.options}"
\ No newline at end of file
+ return (
+ f"SelectionVariable: description: {self.description} values: {self.options}"
+ )
diff --git a/gremlin/util.py b/gremlin/util.py
index cfe08a1f..fc9ef2a6 100644
--- a/gremlin/util.py
+++ b/gremlin/util.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -30,13 +30,11 @@
import qtawesome as qta
from lxml import etree as ElementTree
from typing import Callable
-from types import FunctionType, MethodType
from PySide6 import QtCore, QtWidgets, QtGui
from win32api import GetFileVersionInfo, LOWORD, HIWORD
from PySide6.QtGui import QColor
-import gremlin.ui
from . import error
@@ -49,7 +47,6 @@
class FileWatcher(QtCore.QObject):
-
"""Watches files in the filesystem for changes."""
# Signal emitted when the watched file is modified
@@ -112,12 +109,9 @@ def axis_calibration(value, minimum, center, maximum):
if center is None:
# if no center provided, use the slider function with no center
return slider_calibration(value, minimum, maximum)
-
-
-
if value < center:
- div = float(center - minimum)
+ div = float(center - minimum)
if div == 0.0:
return 0.0
return (value - center) / div + 0.0
@@ -141,7 +135,6 @@ def slider_calibration(value, minimum, maximum):
return value
-
def create_calibration_function(minimum, center, maximum):
"""Returns a calibration function appropriate for the provided data.
@@ -151,7 +144,7 @@ def create_calibration_function(minimum, center, maximum):
:return function which returns a value in [-1, 1] corresponding
to the provided raw input value
"""
- if center is None or minimum == center or maximum == center:
+ if center is None or minimum == center or maximum == center:
return lambda x: slider_calibration(x, minimum, maximum)
else:
return lambda x: axis_calibration(x, minimum, center, maximum)
@@ -183,35 +176,46 @@ def script_path():
def userprofile_path():
"""Returns the path to the user's profile folder, %userprofile%."""
- path = os.path.abspath(os.path.join(os.getenv("userprofile"),"Joystick Gremlin Ex"))
+ path = os.path.abspath(
+ os.path.join(os.getenv("userprofile"), "Joystick Gremlin Ex")
+ )
if not os.path.isdir(path):
# profile folder does not exist - see if we can create it from the original profile
- source_path = os.path.abspath(os.path.join(os.getenv("userprofile"),"Joystick Gremlin"))
+ source_path = os.path.abspath(
+ os.path.join(os.getenv("userprofile"), "Joystick Gremlin")
+ )
if os.path.isdir(source_path):
try:
# copy from original profile
shutil.copytree(source_path, path)
- syslog.info(f"First run - copied Joystick Gremlin profiles to to Joystick Gremlin Ex")
+ syslog.info(
+ "First run - copied Joystick Gremlin profiles to to Joystick Gremlin Ex"
+ )
except Exception as error:
- syslog.error(f"Unable to copy profile from Joystick Gremlin to Joystick Gremlin Ex:\n{error}")
+ syslog.error(
+ f"Unable to copy profile from Joystick Gremlin to Joystick Gremlin Ex:\n{error}"
+ )
if not os.path.isdir(path):
try:
# just create it
os.mkdir(path)
except Exception as error:
- syslog.error(f"Unable to create profile folder for Joystick Gremlin Ex:\n{error}")
-
+ syslog.error(
+ f"Unable to create profile folder for Joystick Gremlin Ex:\n{error}"
+ )
+
if not os.path.isdir(path):
- from gremlin.error import GremlinError
- raise GremlinError(f"Critical error: Unable to create profile folder: {path}")
-
+ from gremlin.error import GremlinError
- return os.path.normcase(path)
+ raise GremlinError(
+ f"Critical error: Unable to create profile folder: {path}"
+ )
+ return os.path.normcase(path)
def resource_path(relative_path):
- """ Get absolute path to resource, handling development and pyinstaller
+ """Get absolute path to resource, handling development and pyinstaller
based usage.
:param relative_path the relative path to the file of interest
@@ -219,7 +223,7 @@ def resource_path(relative_path):
"""
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
- if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
+ if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
base_path = sys._MEIPASS
else:
base_path = script_path()
@@ -229,12 +233,6 @@ def resource_path(relative_path):
return os.path.normcase(os.path.join(base_path, relative_path))
-
-
-
-
-
-
def display_error(msg):
"""Displays the provided error message to the user.
@@ -252,7 +250,7 @@ def display_error(msg):
QtWidgets.QMessageBox.Critical,
"Joystick Gremlin Ex Error",
msg,
- QtWidgets.QMessageBox.Ok
+ QtWidgets.QMessageBox.Ok,
)
box.exec()
@@ -267,30 +265,36 @@ def log(msg):
"""
logging.getLogger("user").debug(str(msg))
+
def log_sys(msg):
- ''' logs to the system log '''
+ """logs to the system log"""
syslog.debug(str(msg))
+
def log_info(msg):
- ''' logs to the system log '''
+ """logs to the system log"""
syslog.info(str(msg))
+
def log_sys_warn(msg):
- ''' logs to the system log '''
+ """logs to the system log"""
syslog.warning(str(msg))
+
def log_sys_error(msg):
- ''' logs to the system error log'''
+ """logs to the system error log"""
syslog.error(str(msg))
+
def format_name(name):
"""Returns the name formatted as valid python variable name.
:param name the name to format
:return name formatted to be suitable as a python variable name
"""
- return re.sub("[^A-Za-z]", "", name.lower()[0]) + \
- re.sub("[^A-Za-z0-9]", "", name.lower()[1:])
+ return re.sub("[^A-Za-z]", "", name.lower()[0]) + re.sub(
+ "[^A-Za-z0-9]", "", name.lower()[1:]
+ )
def valid_python_identifier(name):
@@ -326,18 +330,19 @@ def hat_tuple_to_direction(value):
:return textual equivalent of the event tuple
"""
lookup = {
- ( 0, 0): "center",
- ( 0, 1): "north",
- ( 1, 1): "north-east",
- ( 1, 0): "east",
- ( 1, -1): "south-east",
- ( 0, -1): "south",
+ (0, 0): "center",
+ (0, 1): "north",
+ (1, 1): "north-east",
+ (1, 0): "east",
+ (1, -1): "south-east",
+ (0, -1): "south",
(-1, -1): "south-west",
- (-1, 0): "west",
- (-1, 1): "north-west",
+ (-1, 0): "west",
+ (-1, 1): "north-west",
}
return lookup[value]
+
def hat_index_to_tuple(index):
lookup = {
"center": (0, 0),
@@ -348,39 +353,40 @@ def hat_index_to_tuple(index):
"south": (0, -1),
"south-west": (-1, -1),
"west": (-1, 0),
- "north-west": (-1, 1)
+ "north-west": (-1, 1),
}
keys = list(lookup.keys())
if index < len(keys):
return lookup[keys[index]]
return None
+
def hat_tuples():
lookup = {
- ( 0, 0): "center",
- ( 0, 1): "north",
- ( 1, 1): "north-east",
- ( 1, 0): "east",
- ( 1, -1): "south-east",
- ( 0, -1): "south",
+ (0, 0): "center",
+ (0, 1): "north",
+ (1, 1): "north-east",
+ (1, 0): "east",
+ (1, -1): "south-east",
+ (0, -1): "south",
(-1, -1): "south-west",
- (-1, 0): "west",
- (-1, 1): "north-west",
+ (-1, 0): "west",
+ (-1, 1): "north-west",
}
return list(lookup.keys())
def hat_direction_to_name(value):
lookup = {
- ( 0, 0): "center",
- ( 0, 1): "north",
- ( 1, 1): "north-east",
- ( 1, 0): "east",
- ( 1, -1): "south-east",
- ( 0, -1): "south",
+ (0, 0): "center",
+ (0, 1): "north",
+ (1, 1): "north-east",
+ (1, 0): "east",
+ (1, -1): "south-east",
+ (0, -1): "south",
(-1, -1): "south-west",
- (-1, 0): "west",
- (-1, 1): "north-west",
+ (-1, 0): "west",
+ (-1, 1): "north-west",
}
if value in lookup:
return lookup[value]
@@ -402,7 +408,7 @@ def hat_direction_to_tuple(value):
"south": (0, -1),
"south-west": (-1, -1),
"west": (-1, 0),
- "north-west": (-1, 1)
+ "north-west": (-1, 1),
}
value = value.casefold()
if value in lookup:
@@ -417,13 +423,9 @@ def setup_userprofile():
try:
os.mkdir(folder)
except Exception as e:
- raise error.GremlinError(
- f"Unable to create data folder: {str(e)}"
- )
+ raise error.GremlinError(f"Unable to create data folder: {str(e)}")
elif not os.path.isdir(folder):
- raise error.GremlinError(
- "Data folder exists but is not a folder"
- )
+ raise error.GremlinError("Data folder exists but is not a folder")
def clear_layout(layout):
@@ -437,14 +439,15 @@ def clear_layout(layout):
if child.layout():
clear_layout(child.layout())
elif child.widget():
- if hasattr(child,"_cleanup_ui"):
+ if hasattr(child, "_cleanup_ui"):
child._cleanup_ui()
child.widget().hide()
child.widget().deleteLater()
layout.removeItem(child)
-def get_layout_widgets(layout : QtWidgets.QLayout) -> list:
- ''' returns a list of layout widgets '''
+
+def get_layout_widgets(layout: QtWidgets.QLayout) -> list:
+ """returns a list of layout widgets"""
widgets = []
index = layout.count()
while index >= 0:
@@ -458,8 +461,9 @@ def get_layout_widgets(layout : QtWidgets.QLayout) -> list:
return widgets
+
def layout_contains(layout, widget):
- ''' true if widget is contained in the given layout '''
+ """true if widget is contained in the given layout"""
while layout.count() > 0:
child = layout.takeAt(0)
if child.layout():
@@ -470,8 +474,9 @@ def layout_contains(layout, widget):
return True
return False
+
def layout_remove(layout, widget):
- ''' removes widget from the layout if the layout includes that widget '''
+ """removes widget from the layout if the layout includes that widget"""
while layout.count() > 0:
child = layout.takeAt(0)
if child.layout():
@@ -493,7 +498,7 @@ def layout_remove(layout, widget):
18000: (0, -1),
22500: (-1, -1),
27000: (-1, 0),
- 31500: (-1, 1)
+ 31500: (-1, 1),
}
@@ -529,34 +534,35 @@ def rad2deg(angle):
return angle * (180.0 / math.pi)
+def get_dll_version(path, as_string=True):
+ """gets the dll file version number
-def get_dll_version(path, as_string = True):
- ''' gets the dll file version number
-
:param path - the full path to the file
:returns file major, file minor, product version major, product version minor as integers
- '''
+ """
if not os.path.isfile(path):
if as_string:
return None
- return (0,0,0,0)
-
+ return (0, 0, 0, 0)
+
try:
- info = GetFileVersionInfo (path, "\\")
- ms = info['FileVersionMS']
- ls = info['FileVersionLS']
-
- f_major = HIWORD (ms)
- f_minor = LOWORD (ms)
- p_major = HIWORD (ls)
- p_minor = LOWORD (ls)
-
+ info = GetFileVersionInfo(path, "\\")
+ ms = info["FileVersionMS"]
+ ls = info["FileVersionLS"]
+
+ f_major = HIWORD(ms)
+ f_minor = LOWORD(ms)
+ p_major = HIWORD(ls)
+ p_minor = LOWORD(ls)
+
if as_string:
return f"{f_major}.{f_minor}.{p_major}.{p_minor}"
return (f_major, f_minor, p_major, p_minor)
- except:
+ except Exception as e:
syslog = logging.getLogger("system")
- syslog.warning(f"Unable to get file version information due to an OS error for: {path} ")
+ syslog.warning(
+ f"Unable to get file version information due to an OS error for: {path}\n{e}"
+ )
return None
@@ -582,18 +588,21 @@ def get_dll_version(path, as_string = True):
# return version.replace("\r","").replace("\"","")
# return None
+
def version_valid(v, v_req):
- ''' compares two versions
-
+ """compares two versions
+
:param v - version as string in x.x.x.x format
:param r - version required as string in x.x.x.x format
-
- '''
+
+ """
+
def compare_version(version1, version2):
def parse_version(version):
- version_parts = version.split('.')
+ version_parts = version.split(".")
version_ints = [int(part) for part in version_parts]
return version_ints
+
v1_parts = parse_version(version1)
v2_parts = parse_version(version2)
for i in range(max(len(v1_parts), len(v2_parts))):
@@ -603,32 +612,36 @@ def parse_version(version):
if v1_num < v2_num:
return -1 # version1 is smaller
elif v1_num > v2_num:
- return 1 # version2 is smaller
- return 0 # equal
+ return 1 # version2 is smaller
+ return 0 # equal
return compare_version(v, v_req) >= 0
def grouped(iterable, n):
- ''' returns n items for a given iterable item '''
- return zip(*[iter(iterable)]*n)
+ """returns n items for a given iterable item"""
+ return zip(*[iter(iterable)] * n)
+
def get_dinput_guid() -> dinput.GUID:
- ''' gets a DirectInput compatible GUID'''
- return parse_guid(get_guid(strip=False,no_brackets=True))
+ """gets a DirectInput compatible GUID"""
+ return parse_guid(get_guid(strip=False, no_brackets=True))
-def get_guid(strip=True,no_brackets = False) -> str:
- ''' generates a reasonably lowercase unique guid string '''
+
+def get_guid(strip=True, no_brackets=False) -> str:
+ """generates a reasonably lowercase unique guid string"""
import uuid
+
guid = f"{uuid.uuid4()}"
if strip:
- guid = guid.replace("-",'')
+ guid = guid.replace("-", "")
if no_brackets:
- guid = guid.replace("{",'').replace("}",'')
+ guid = guid.replace("{", "").replace("}", "")
return guid
+
def compare_guid(first, other):
- ''' compares GUIDs DINPUT or str - True if equal'''
+ """compares GUIDs DINPUT or str - True if equal"""
if first is None and other is None:
return True
if first is None:
@@ -638,99 +651,100 @@ def compare_guid(first, other):
first = str(first).casefold()
other = str(other).casefold()
return first == other
-
-def find_files(root_folder, source_pattern = "*") -> list:
- ''' runs native file search to find files without blowing up on borked sym links in windows unlike rglob - returns full paths to the found file pattern '''
+
+
+def find_files(root_folder, source_pattern="*") -> list:
+ """runs native file search to find files without blowing up on borked sym links in windows unlike rglob - returns full paths to the found file pattern"""
import subprocess
+
if not os.path.isdir(root_folder):
return []
-
+
wd = os.getcwd()
os.chdir(root_folder)
- process = subprocess.Popen(["cmd", "/c", "dir", source_pattern, "/b","/s"],stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ process = subprocess.Popen(
+ ["cmd", "/c", "dir", source_pattern, "/b", "/s"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
out, err = process.communicate()
os.chdir(wd)
- encoding = 'utf-8'
+ encoding = "utf-8"
# the link will be in square brackets
if out:
- lines = str(out,encoding)
+ lines = str(out, encoding)
if lines:
- lines = lines.replace('\r','')
- lines = lines.split('\n')
- lines = [l for l in lines if l]
+ lines = lines.replace("\r", "")
+ lines = lines.split("\n")
+ lines = [line for line in lines if line]
return lines
return []
+def find_folders(root_folder, source_pattern="*") -> list:
+ """looks for a subfolder off the root folder"""
-
-def find_folders(root_folder, source_pattern = "*") -> list:
- ''' looks for a subfolder off the root folder '''
- import subprocess
if not os.path.isdir(root_folder):
return []
-
+
folders = os.listdir(root_folder)
return [os.path.join(root_folder, folder) for folder in folders]
-
@SingletonDecorator
-class SearchCache():
- ''' file search cache service '''
+class SearchCache:
+ """file search cache service"""
+
def __init__(self):
self.cache = {}
- def find_file(self, file_path, root_folder = None):
- if not file_path in self.cache:
+ def find_file(self, file_path, root_folder=None):
+ if file_path not in self.cache:
item = _find_file(file_path, root_folder)
if item is not None:
self.cache[file_path] = item
return item
return self.cache[file_path]
-
-def find_file(file_path, root_folder = None):
+
+
+def find_file(file_path, root_folder=None):
cache = SearchCache()
return cache.find_file(file_path, root_folder)
def find_package_file(file_path):
- ''' find a package file '''
+ """find a package file"""
syslog = logging.getLogger("system")
# get the execution folder
root_folder = None
- if getattr(sys, 'frozen', False):
+ if getattr(sys, "frozen", False):
app = QtWidgets.QApplication.instance()
root_folder = app.applicationDirPath()
elif __file__:
root_folder = os.path.dirname(__file__)
-
+
syslog.info(f"Application Execution folder: {root_folder}")
return find_file(file_path, root_folder)
+def _find_file(file_path, root_folder=None):
+ """finds a file"""
-def _find_file(file_path, root_folder = None):
- ''' finds a file '''
-
-
-
-
- from pathlib import Path
from gremlin.config import Configuration
import gremlin.shared_state
+
verbose = Configuration().verbose_mode_details
- file_path = file_path.lower().replace("/",os.sep)
+ file_path = file_path.lower().replace("/", os.sep)
sub_folders = None
folders = []
-
+ circuit_breaker = 0
if not root_folder:
root_folder = gremlin.shared_state.root_path
if not os.path.isdir(root_folder):
return None
-
+
if os.sep in file_path:
# we have folders
splits = file_path.split(os.sep)
@@ -745,14 +759,14 @@ def _find_file(file_path, root_folder = None):
if ext:
extensions = [ext]
else:
- extensions = [".svg",".png"]
+ extensions = [".svg", ".png"]
circuit_breaker = 1000
for dirpath, _, filenames in os.walk(root_folder):
last = os.path.basename(dirpath)
if last.startswith("."):
# ignore hidden folders
continue
- circuit_breaker-=1
+ circuit_breaker -= 1
if circuit_breaker == 0:
break
if sub_folders and not dirpath.endswith(sub_folders):
@@ -761,81 +775,82 @@ def _find_file(file_path, root_folder = None):
for ext in extensions:
if filename.endswith(ext) and filename.startswith(file_root):
files.append(os.path.join(dirpath, filename))
-
+
if files:
- files.sort(key = lambda x: len(x)) # shortest to largest
- found_path = files.pop(0) # grab the first one
+ files.sort(key=lambda x: len(x)) # shortest to largest
+ found_path = files.pop(0) # grab the first one
if verbose:
syslog.info(f"Find_files() - found : {found_path} for {file_path}")
return found_path
-
+
if circuit_breaker == 0:
- syslog.error(f"Find_files() - search exceeded maximum when searching for: {file_path}")
-
+ syslog.error(
+ f"Find_files() - search exceeded maximum when searching for: {file_path}"
+ )
+
if verbose or circuit_breaker == 0:
syslog.error(f"Find_files() failed for: {file_path}")
return None
+def get_icon_path(*paths):
+ """
+ gets an icon path
+
+ """
+ from gremlin.config import Configuration
-def get_icon_path(*paths):
- '''
- gets an icon path
-
- '''
-
- from gremlin.config import Configuration
- verbose = Configuration().verbose_mode_details
-
- import gremlin.shared_state
-
- # be aware of runtime environment
- root_path = gremlin.shared_state.root_path
- try:
- the_path = os.path.join(*paths).lower()
- except:
- # no path provided
- return None
-
- if the_path in gremlin.shared_state._icon_path_cache.keys():
- return gremlin.shared_state._icon_path_cache[the_path]
-
-
- # syslog.info(f"icon path: {the_path} root: {root_path}")
- icon_file = os.path.join(root_path, the_path)
- icon_file = icon_file.replace("/",os.sep).lower()
- if icon_file:
- if os.path.isfile(icon_file):
- if verbose:
- syslog.info(f"Icon file (straight) found: {icon_file}")
- gremlin.shared_state._icon_path_cache[the_path] = icon_file
- return icon_file
- if not icon_file.endswith(".png"):
- icon_file_png = icon_file + ".png"
- if os.path.isfile(icon_file_png):
- if verbose:
- syslog.info(f"Icon file (png) found: {icon_file_png}")
- gremlin.shared_state._icon_path_cache[the_path] = icon_file_png
- return icon_file_png
- if not icon_file.endswith(".svg"):
- icon_file_svg = icon_file + ".svg"
- if os.path.isfile(icon_file_svg):
- if verbose:
- syslog.info(f"Icon file (svg) found: {icon_file_svg}")
- gremlin.shared_state._icon_path_cache[the_path] = icon_file_svg
- return icon_file_svg
- brute_force = find_file(the_path)
- if brute_force and os.path.isfile(brute_force):
- gremlin.shared_state._icon_path_cache[the_path] = brute_force
- return brute_force
-
- syslog.error(f"Icon file not found: {icon_file}")
-
+ verbose = Configuration().verbose_mode_details
+
+ import gremlin.shared_state
+
+ # be aware of runtime environment
+ root_path = gremlin.shared_state.root_path
+ try:
+ the_path = os.path.join(*paths).lower()
+ except Exception:
+ # no path provided
return None
+ if the_path in gremlin.shared_state._icon_path_cache.keys():
+ return gremlin.shared_state._icon_path_cache[the_path]
+
+ # syslog.info(f"icon path: {the_path} root: {root_path}")
+ icon_file = os.path.join(root_path, the_path)
+ icon_file = icon_file.replace("/", os.sep).lower()
+ if icon_file:
+ if os.path.isfile(icon_file):
+ if verbose:
+ syslog.info(f"Icon file (straight) found: {icon_file}")
+ gremlin.shared_state._icon_path_cache[the_path] = icon_file
+ return icon_file
+ if not icon_file.endswith(".png"):
+ icon_file_png = icon_file + ".png"
+ if os.path.isfile(icon_file_png):
+ if verbose:
+ syslog.info(f"Icon file (png) found: {icon_file_png}")
+ gremlin.shared_state._icon_path_cache[the_path] = icon_file_png
+ return icon_file_png
+ if not icon_file.endswith(".svg"):
+ icon_file_svg = icon_file + ".svg"
+ if os.path.isfile(icon_file_svg):
+ if verbose:
+ syslog.info(f"Icon file (svg) found: {icon_file_svg}")
+ gremlin.shared_state._icon_path_cache[the_path] = icon_file_svg
+ return icon_file_svg
+ brute_force = find_file(the_path)
+ if brute_force and os.path.isfile(brute_force):
+ gremlin.shared_state._icon_path_cache[the_path] = brute_force
+ return brute_force
+
+ syslog.error(f"Icon file not found: {icon_file}")
+
+ return None
+
+
def load_pixmap(*paths):
- ''' gets a pixmap from the path '''
+ """gets a pixmap from the path"""
the_path = get_icon_path(*paths)
if the_path:
pixmap = QtGui.QPixmap(the_path)
@@ -843,24 +858,26 @@ def load_pixmap(*paths):
syslog.warning(f"load_pixmap(): pixmap failed: {the_path}")
return None
return pixmap
-
- syslog.error(f"load_pixmap(): invalid path")
- return None
-
+ syslog.error("load_pixmap(): invalid path")
+ return None
-def load_icon(*paths, use_qta = False, qta_color = None):
- ''' gets an icon (returns a QIcon) - uses the qtawesome library or does a raw file search '''
+def load_icon(*paths, use_qta=False, qta_color=None):
+ """gets an icon (returns a QIcon) - uses the qtawesome library or does a raw file search"""
from gremlin.config import Configuration
import gremlin.shared_state
import gremlin.ui.ui_common
+
verbose = Configuration().verbose_mode_details
is_dark = gremlin.shared_state.is_dark_theme
(the_path,) = paths
- _ , ext = os.path.splitext(the_path.casefold())
+ if the_path is None:
+ syslog.error("load_icon(): the_path is None")
+ return None
+ _, ext = os.path.splitext(the_path.casefold())
if ext == ".svg":
if not os.path.isfile(the_path):
@@ -873,24 +890,26 @@ def load_icon(*paths, use_qta = False, qta_color = None):
create_dark_svg(the_path, dark_path)
if os.path.isfile(dark_path):
# load the dark equivalent
- the_path = dark_path
-
+ the_path = dark_path
+
icon = None
- if ext == "" or not (ext in (".png",".ico",".svg")) or use_qta:
+ if ext == "" or ext not in (".png", ".ico", ".svg") or use_qta:
# assume a QTA icon if no extension
try:
if not qta_color:
qta_color = gremlin.ui.ui_common.Color.normalColor()
if isinstance(qta_color, str):
assert qta_color.startswith("#") and len(qta_color) == 7
- icon = QtGui.QIcon(qta.icon(the_path, color = qta_color))
- except:
+ icon = QtGui.QIcon(qta.icon(the_path, color=qta_color))
+ except Exception:
pass
if not icon:
pixmap = load_pixmap(*paths)
if not pixmap or pixmap.isNull():
if verbose:
- syslog.info(f"LoadIcon() using generic icon - failed to locate: {paths}")
+ syslog.info(
+ f"LoadIcon() using generic icon - failed to locate: {paths}"
+ )
return get_generic_icon()
icon = QtGui.QIcon()
@@ -901,7 +920,7 @@ def load_icon(*paths, use_qta = False, qta_color = None):
def dark_file(image_path):
- ''' gets the dark file name for an icon, if it exists '''
+ """gets the dark file name for an icon, if it exists"""
the_path = image_path.casefold()
dirname, basefile = os.path.split(the_path)
basename, ext = os.path.splitext(basefile)
@@ -910,53 +929,49 @@ def dark_file(image_path):
return image_path
-
-def create_dark_svg(source_path, dark_path, hexcolor = ""):
+def create_dark_svg(source_path, dark_path, hexcolor=""):
if os.path.isfile(source_path):
if not os.path.isfile(dark_path):
new_color = "#CCCCCC"
new_gray_color = "#AAAAAA"
new_stroke = "#666666"
- with open(source_path,"r") as fin:
- with open(dark_path,"w") as fout:
+ with open(source_path, "r") as fin:
+ with open(dark_path, "w") as fout:
for line in fin.readlines():
-
- line = line.replace("#ffffff",new_stroke)
- line = line.replace("#666666",new_gray_color)
- line = line.replace("#000000",new_color)
- line = line.replace("#000005;",new_color)
-
+ line = line.replace("#ffffff", new_stroke)
+ line = line.replace("#666666", new_gray_color)
+ line = line.replace("#000000", new_color)
+ line = line.replace("#000005;", new_color)
+
fout.write(line)
fout.flush()
fout.close()
fin.close()
-
-
-def recolor_icon_pixmap(image_path, color = "red"):
- ''' recolors non-transparent pixels in an icon image
+def recolor_icon_pixmap(image_path, color="red"):
+ """recolors non-transparent pixels in an icon image
:Returns: pixmap of the recolored item
- '''
+ """
the_path = get_icon_path(image_path)
if the_path:
tmp = QtGui.QImage(the_path)
tmp = tmp.convertToFormat(QtGui.QImage.Format.Format_ARGB32)
c = QtGui.QColor(color)
for y in range(tmp.height()):
- for x in range (tmp.width()):
- c.setAlpha(tmp.pixelColor(x,y).alpha())
- tmp.setPixelColor(x,y,color)
+ for x in range(tmp.width()):
+ c.setAlpha(tmp.pixelColor(x, y).alpha())
+ tmp.setPixelColor(x, y, color)
- pixmap = QtGui.QPixmap.fromImage(tmp)
+ pixmap = QtGui.QPixmap.fromImage(tmp)
return pixmap
return None
-
def load_image(*paths):
- ''' loads an image '''
+ """loads an image"""
from gremlin.config import Configuration
+
verbose = Configuration().verbose_mode_details
the_path = get_icon_path(*paths)
if the_path:
@@ -964,15 +979,14 @@ def load_image(*paths):
syslog.info(f"LoadImage() found image: {paths}")
return QtGui.QImage(the_path)
if verbose:
- syslog.info(f"LoadImage() failed to locate: {paths}")
+ syslog.info(f"LoadImage() failed to locate: {paths}")
return None
-
-
-
+
def get_generic_icon():
- ''' gets a generic icon'''
+ """gets a generic icon"""
import gremlin.shared_state
+
root_path = gremlin.shared_state.root_path
generic_icon = os.path.join(root_path, "gfx/generic.png")
if generic_icon and os.path.isfile(generic_icon):
@@ -987,7 +1001,6 @@ def get_generic_icon():
return None
-
def write_guid(guid):
"""Returns the string representation of a GUID object.
@@ -1013,7 +1026,7 @@ def safe_read(node, key, type_cast=None, default_value=None):
# in case reading fails
syslog = logging.getLogger("system")
value = default_value
- if not key in node.keys():
+ if key not in node.keys():
if default_value is None:
match type_cast:
case str():
@@ -1032,13 +1045,12 @@ def safe_read(node, key, type_cast=None, default_value=None):
raise error.ProfileError(msg)
else:
value = node.get(key)
-
if type_cast is not None:
try:
- if type_cast == bool and isinstance(value,str):
- value = value.strip().casefold()
- value = value == "true"
+ if type_cast is bool and isinstance(value, str):
+ value = value.strip().casefold()
+ value = value == "true"
else:
if value == "none":
value = None
@@ -1047,8 +1059,10 @@ def safe_read(node, key, type_cast=None, default_value=None):
else:
try:
value = type_cast(value)
- except:
- syslog.error(f"XML: safe read - unable to convert type: {type_cast} value: [{value}] - using default: {default_value}")
+ except Exception as e:
+ syslog.error(
+ f"XML: safe read - unable to convert type: {type_cast} value: [{value}] - using default: {default_value}\n error: {e}"
+ )
value = default_value
except ValueError:
@@ -1084,22 +1098,22 @@ def safe_format(value, data_type, formatter=str):
return formatter(value)
else:
raise error.ProfileError(
- f"Value \"{value}\" has type {type(value)} when {data_type} is expected"
+ f'Value "{value}" has type {type(value)} when {data_type} is expected'
)
-def get_xml_child(node, tag : str, multiple = False):
- ''' gets a specific xml child node by tag - None if not found
-
+def get_xml_child(node, tag: str, multiple=False):
+ """gets a specific xml child node by tag - None if not found
+
:param: multiple - if set, returns all matching subnodes as a list, blank list if nothing found - if not set, returns None or the first node found
-
- '''
+
+ """
value = tag.casefold()
if multiple:
nodes = []
for child in list(node):
- if not child.tag is ElementTree.Comment:
+ if child.tag is not ElementTree.Comment:
if child.tag.casefold() == value:
nodes.append(child)
return nodes
@@ -1108,13 +1122,12 @@ def get_xml_child(node, tag : str, multiple = False):
if child.tag is ElementTree.Comment:
continue
if child.tag.casefold() == value:
-
return child
return None
-def get_xml_parent(node, tag : str):
- ''' gets the first parent node of that tag '''
- value = tag.casefold()
+
+def get_xml_parent(node, tag: str):
+ """gets the first parent node of that tag"""
parent = node.getparent()
while parent is not None:
if parent.tag.casefold() == tag:
@@ -1122,8 +1135,9 @@ def get_xml_parent(node, tag : str):
parent = parent.getparent()
return None
+
def get_xml_mode(node):
- ''' gets the mode from a parent xml node '''
+ """gets the mode from a parent xml node"""
# grab the mode
mode_node = node
while mode_node is not None and mode_node.tag != "mode":
@@ -1132,16 +1146,17 @@ def get_xml_mode(node):
if mode_node is not None:
mode = mode_node.get("name")
return mode
-
+
return None
+
def get_xml_input_data(node):
- ''' for a given XML node, find in the parent hierarchy of a profile the device_guid, mode, input_type and input_id
-
+ """for a given XML node, find in the parent hierarchy of a profile the device_guid, mode, input_type and input_id
+
:param node: the child node
:returns: (device_guid, input_type, input_id, mode)
-
- '''
+
+ """
from gremlin.input_types import InputType
device_guid = None
@@ -1166,23 +1181,22 @@ def get_xml_input_data(node):
# grab the input type and input id this applies to
input_node = node
- tags = ["axis","button","hat","osc","midi"]
+ tags = ["axis", "button", "hat", "osc", "midi"]
- while input_node is not None and not input_node.tag in tags:
+ while input_node is not None and input_node.tag not in tags:
input_node = input_node.getparent()
-
if input_node is not None:
match input_node.tag:
case "axis":
input_type = InputType.JoystickAxis
- input_id = safe_read(input_node,"id",int, 0)
+ input_id = safe_read(input_node, "id", int, 0)
case "button":
input_type = InputType.JoystickButton
- input_id = safe_read(input_node,"id",int, 0)
+ input_id = safe_read(input_node, "id", int, 0)
case "hat":
input_type = InputType.JoystickHat
- input_id = safe_read(input_node,"id",int, 0)
+ input_id = safe_read(input_node, "id", int, 0)
case "osc":
child = get_xml_child(input_node, "input")
input_type = InputType.OpenSoundControl
@@ -1191,12 +1205,6 @@ def get_xml_input_data(node):
child = get_xml_child(input_node, "input")
input_type = InputType.Midi
input_id = str(parse_guid(child.get("guid")))
-
-
-
-
-
-
return (device_guid, input_type, input_id, mode)
@@ -1224,10 +1232,8 @@ def parse_guid(value):
raw_guid.Data4[i] = tmp.bytes[8 + i]
return dinput.GUID(raw_guid)
- except (ValueError, AttributeError) as e:
- raise error.ProfileError(
- f"Failed parsing GUID from value {value}"
- )
+ except (ValueError, AttributeError):
+ raise error.ProfileError(f"Failed parsing GUID from value {value}")
def parse_bool(value, default_value=False):
@@ -1249,40 +1255,33 @@ def parse_bool(value, default_value=False):
if int_value in [0, 1]:
return int_value == 1
else:
- raise error.ProfileError(
- f"Invalid bool value used: {value}"
- )
+ raise error.ProfileError(f"Invalid bool value used: {value}")
else:
value = value.lower()
if value in ["true", "false"]:
return value == "true"
else:
- raise error.ProfileError(
- f"Invalid bool value used: {value}"
- )
+ raise error.ProfileError(f"Invalid bool value used: {value}")
except ValueError:
value = value.lower()
if value in ["true", "false"]:
return value == "true"
else:
- raise error.ProfileError(
- f"Invalid bool value used: {value}"
- )
+ raise error.ProfileError(f"Invalid bool value used: {value}")
except TypeError:
- raise error.ProfileError(
- f"Invalid type provided: {type(value)}"
- )
+ raise error.ProfileError(f"Invalid type provided: {type(value)}")
+
-def read_guid(node, key, default_value = None):
- ''' reads a GUID '''
+def read_guid(node, key, default_value=None):
+ """reads a GUID"""
if key in node.attrib:
try:
s_guid = node.get(key)
return uuid.UUID(s_guid)
- except:
+ except Exception:
pass
return default_value
-
+
def read_bool(node, key, default_value=False):
"""Attempts to read a boolean value.
@@ -1298,22 +1297,26 @@ def read_bool(node, key, default_value=False):
return parse_bool(node.get(key), default_value)
return default_value
-def byte_string_to_list(value : str) -> list:
- ''' converts a text string of sequential bytes separated by a space'''
+
+def byte_string_to_list(value: str) -> list:
+ """converts a text string of sequential bytes separated by a space"""
tokens = value.split()
data = []
for token in tokens:
try:
- value = int(token, 16) # expecting a hexadecimal number
+ value = int(token, 16) # expecting a hexadecimal number
data.append(value)
- except:
- raise ValueError(f"Unable to convert byte string to list, offending value: {token}")
-
+ except Exception as e:
+ raise ValueError(
+ f"Unable to convert byte string to list, offending value: {token}\n{e}"
+ )
+
return data
-def byte_list_to_string(data, as_hex = True):
- ''' converts a byte list to a string '''
- result = ''
+
+def byte_list_to_string(data, as_hex=True):
+ """converts a byte list to a string"""
+ result = ""
for value in data:
if as_hex:
result += f"{value:02x} "
@@ -1324,168 +1327,199 @@ def byte_list_to_string(data, as_hex = True):
result = result[:-1]
return result
-def scale_to_range(value, source_min = -1.0, source_max = 1.0, target_min = -1.0, target_max = 1.0, invert = False):
- ''' scales a value on one range to the new range
-
+
+def scale_to_range(
+ value,
+ source_min=-1.0,
+ source_max=1.0,
+ target_min=-1.0,
+ target_max=1.0,
+ invert=False,
+):
+ """scales a value on one range to the new range
+
value: the value to scale
r_min: the source value's min range
r_max: the source value's max range
new_min: the new range's min
new_max: the new range's max
invert: true if the value should be reversed
- '''
+ """
if value is None:
return None
-
+
if source_min == source_max:
syslog.warning("SCALE: scaling failed: source range is identical")
return value
-
+
# bracket value to input range if outside that range
- if value < source_min:
+ if value < source_min:
value = source_min
elif value > source_max:
value = source_max
-
+
if invert:
- result = (((source_max - value) * (target_max - target_min)) / (source_max - source_min)) + target_min
+ result = (
+ ((source_max - value) * (target_max - target_min))
+ / (source_max - source_min)
+ ) + target_min
else:
- result = (((value - source_min) * (target_max - target_min)) / (source_max - source_min)) + target_min
+ result = (
+ ((value - source_min) * (target_max - target_min))
+ / (source_max - source_min)
+ ) + target_min
return result + 0
+
def list_to_csv(data) -> str:
- ''' converts an input list to a CSV stream - returns a single row '''
+ """converts an input list to a CSV stream - returns a single row"""
if not data:
return ""
assert isinstance(data, tuple) or isinstance(data, list)
import csv
import io
+
output = io.StringIO()
writer = csv.writer(output, quoting=csv.QUOTE_MINIMAL)
writer.writerow(data)
- return output.getvalue().strip() # remove new lines
+ return output.getvalue().strip() # remove new lines
+
-def floatlist_to_csv(data, decimals = 3) -> str:
- ''' converts an input list to a CSV stream - returns a single row '''
+def floatlist_to_csv(data, decimals=3) -> str:
+ """converts an input list to a CSV stream - returns a single row"""
if not data:
return ""
assert isinstance(data, tuple) or isinstance(data, list)
import csv
import io
+
output = io.StringIO()
writer = csv.writer(output, quoting=csv.QUOTE_MINIMAL)
- writer.writerow([f'{x:.{decimals}f}' if isinstance(x, float) else x for x in data])
- return output.getvalue().strip() # remove new lines
+ writer.writerow([f"{x:.{decimals}f}" if isinstance(x, float) else x for x in data])
+ return output.getvalue().strip() # remove new lines
+
def csv_to_list(value) -> list:
- ''' converts a single row csv input to a list '''
+ """converts a single row csv input to a list"""
if value:
import csv
import io
+
input = io.StringIO(value)
try:
- reader = csv.reader(input, delimiter=',')
+ reader = csv.reader(input, delimiter=",")
for row in reader:
return row
- except:
- syslog.error(f"Unable to convert data stream {value} to a list")
+ except Exception as e:
+ syslog.error(f"Unable to convert data stream {value} to a list\n{e}")
return []
+
def csv_to_floatlist(value) -> list:
- ''' converts a single row csv input to a list of floating point values '''
+ """converts a single row csv input to a list of floating point values"""
if value:
import csv
import io
+
input = io.StringIO(value)
try:
- reader = csv.reader(input, delimiter=',')
+ reader = csv.reader(input, delimiter=",")
for row in reader:
values = [float(v) for v in row]
return values
- except:
- syslog.error(f"Unable to convert data stream {value} to a list")
+ except Exception as e:
+ syslog.error(f"Unable to convert data stream {value} to a list\n{e}")
return []
def waitCursor():
- ''' sets the app to a wait cursor '''
+ """sets the app to a wait cursor"""
pushCursor()
+
_cursor_push = 0
+
def pushCursor():
global _cursor_push
if _cursor_push == 0:
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.CursorShape.WaitCursor)
- #QtWidgets.QApplication.processEvents()
- _cursor_push+=1
+ # QtWidgets.QApplication.processEvents()
+ _cursor_push += 1
+
-def popCursor(reset = False):
- ''' restores form wait cusor '''
+def popCursor(reset=False):
+ """restores form wait cusor"""
global _cursor_push
if _cursor_push > 0:
_cursor_push -= 1
if _cursor_push == 0 or reset:
QtWidgets.QApplication.restoreOverrideCursor()
- #QtWidgets.QApplication.processEvents()
+ # QtWidgets.QApplication.processEvents()
+
def isCursorActive():
- ''' true if the cursor stack is not empty '''
+ """true if the cursor stack is not empty"""
global _cursor_push
return _cursor_push > 0
-
+
def compare_path(a, b):
- ''' compare two paths '''
+ """compare two paths"""
if a is None and b is None:
return True
if a is None:
return False
if b is None:
return False
- af = a.replace("\\","/").casefold().strip()
- bf = b.replace("\\","/").casefold().strip()
+ af = a.replace("\\", "/").casefold().strip()
+ bf = b.replace("\\", "/").casefold().strip()
return af == bf
+
def fix_path(a):
if a:
- return a.replace("\\","/").casefold().strip()
+ return a.replace("\\", "/").casefold().strip()
return a
-def compare_nocase(a : str, b : str):
- ''' compares two strings - not case sensitive '''
+
+def compare_nocase(a: str, b: str):
+ """compares two strings - not case sensitive"""
if a is None and b is None:
return True
if a == "" and b == "":
return True
return a.casefold() == b.casefold()
-def getSignal (oObject : QtCore.QObject, signal_name : str):
- ''' gets a reference to an object signal '''
+
+def getSignal(oObject: QtCore.QObject, signal_name: str):
+ """gets a reference to an object signal"""
oMetaObj = oObject.metaObject()
- for i in range (oMetaObj.methodCount()):
+ for i in range(oMetaObj.methodCount()):
oMetaMethod = oMetaObj.method(i)
if not oMetaMethod.isValid():
continue
- if oMetaMethod.methodType () == QtCore.QMetaMethod.Signal and \
- oMetaMethod.name() == signal_name:
+ if (
+ oMetaMethod.methodType() == QtCore.QMetaMethod.Signal
+ and oMetaMethod.name() == signal_name
+ ):
return oMetaMethod
return None
-def isSignalConnected(oObject : QtCore.QObject, signal_name : str):
- ''' true if a signal is connected '''
+
+def isSignalConnected(oObject: QtCore.QObject, signal_name: str):
+ """true if a signal is connected"""
mm = getSignal(oObject, signal_name)
return mm is not None and oObject.isSignalConnected(mm)
-
-
-def centerDialog(dialog, width = 300, height = 150):
- ''' centers the dialog on top of the UI '''
+def centerDialog(dialog, width=300, height=150):
+ """centers the dialog on top of the UI"""
# Display the dialog centered in the middle of the UI
import gremlin.shared_state
+
if gremlin.shared_state.ui is None:
- return # no UI yet
+ return # no UI yet
root = dialog
if not root.parent():
geom = gremlin.shared_state.ui.geometry()
@@ -1494,22 +1528,21 @@ def centerDialog(dialog, width = 300, height = 150):
root = root.parent()
geom = root.geometry()
-
- #dialog.move(geom.x() - geom.width()/2, geom.y() - geom.height()/2)
+ # dialog.move(geom.x() - geom.width()/2, geom.y() - geom.height()/2)
dialog.setGeometry(
- int(geom.x() + geom.width() / 2 - width/2),
- int(geom.y() + geom.height() / 2 - height/2),
+ int(geom.x() + geom.width() / 2 - width / 2),
+ int(geom.y() + geom.height() / 2 - height / 2),
width,
- height
+ height,
)
-def swapext(path, ext = None, prefix= '', suffix = ''):
- ''' replaces a file extension with a different one with an additional prefix / suffix'''
+def swapext(path, ext=None, prefix="", suffix=""):
+ """replaces a file extension with a different one with an additional prefix / suffix"""
dirname, filename = os.path.split(path)
base, old_ext = os.path.splitext(filename)
if ext:
- if ext != '' and not ext.startswith('.'):
+ if ext != "" and not ext.startswith("."):
ext = "." + ext
else:
ext = old_ext
@@ -1517,6 +1550,7 @@ def swapext(path, ext = None, prefix= '', suffix = ''):
return os.path.join(dirname, prefix + base + suffix + ext).lower()
return (prefix + base + suffix + ext).lower()
+
def get_ext(path):
# gets an extension
_, filename = os.path.split(path)
@@ -1525,27 +1559,29 @@ def get_ext(path):
return ext.lower()
return None
+
def strip_ext(path):
if path:
tokens = path.split(".")
return tokens[0]
- return ''
+ return ""
-def swap_ext(path, ext = None, prefix= '', suffix = ''):
+
+def swap_ext(path, ext=None, prefix="", suffix=""):
return swapext(path, ext, prefix, suffix)
def display_file(path):
- ''' opens a file in the current editor associated with the extension '''
- import subprocess
+ """opens a file in the current editor associated with the extension"""
import webbrowser
+
if os.path.isfile(path):
webbrowser.open(path)
else:
syslog.error(f"DISPLAYFILE: warning: file not found: {path}")
-def debug_pickle(instance, exception=None, string='', first_only=True):
+def debug_pickle(instance, exception=None, string="", first_only=True):
"""
Recursively go through all attributes of instance and return a list of whatever
can't be pickled.
@@ -1555,12 +1591,13 @@ def debug_pickle(instance, exception=None, string='', first_only=True):
"""
problems = []
import dill
+
if isinstance(instance, tuple) or isinstance(instance, list):
for k, v in enumerate(instance):
try:
dill.dumps(v)
except BaseException as e:
- problems.extend(debug_pickle(v, e, string + f'[{k}]'))
+ problems.extend(debug_pickle(v, e, string + f"[{k}]"))
if first_only:
break
elif isinstance(instance, dict):
@@ -1568,18 +1605,18 @@ def debug_pickle(instance, exception=None, string='', first_only=True):
try:
dill.dumps(k)
except BaseException as e:
- problems.extend(debug_pickle(
- k, e, string + f'[key type={type(k).__name__}]'
- ))
+ problems.extend(
+ debug_pickle(k, e, string + f"[key type={type(k).__name__}]")
+ )
if first_only:
break
for v in instance.values():
try:
dill.dumps(v)
except BaseException as e:
- problems.extend(debug_pickle(
- v, e, string + f'[val type={type(v).__name__}]'
- ))
+ problems.extend(
+ debug_pickle(v, e, string + f"[val type={type(v).__name__}]")
+ )
if first_only:
break
else:
@@ -1588,17 +1625,16 @@ def debug_pickle(instance, exception=None, string='', first_only=True):
try:
dill.dumps(v)
except BaseException as e:
- print (k)
- problems.extend(debug_pickle(v, e, string + '.' + k))
- except:
+ print(k)
+ problems.extend(debug_pickle(v, e, string + "." + k))
+ except Exception:
# ignore types that have no attributes
pass
-
# if we get here, it means pickling instance caused an exception (string is not
# empty), yet no member was a problem (problems is empty), thus instance itself
# is the problem.
- if string != '' and not problems:
+ if string != "" and not problems:
problems.append(
string + f" (Type '{type(instance).__name__}' caused: {exception})"
)
@@ -1606,21 +1642,23 @@ def debug_pickle(instance, exception=None, string='', first_only=True):
return problems
-def is_close(a, b, tolerance = 0.0001):
- ''' compares two floating point numbers with approximate precision '''
+def is_close(a, b, tolerance=0.0001):
+ """compares two floating point numbers with approximate precision"""
return math.isclose(a, b, abs_tol=tolerance)
+
class InvokeUiMethod(QtCore.QObject):
- ''' invokes a call on the UI thread as QT is not thread safe '''
+ """invokes a call on the UI thread as QT is not thread safe"""
+
def __init__(self, method: Callable):
- ''' Invokes a method on the main ui thread.
-
+ """Invokes a method on the main ui thread.
+
:params: method: lambda expression
-
- '''
+
+ """
super().__init__()
current_thread = QtCore.QThread.currentThread()
- ui_thread = QtWidgets.QApplication.instance().thread() # QT thread
+ ui_thread = QtWidgets.QApplication.instance().thread() # QT thread
if current_thread != ui_thread:
self.moveToThread(ui_thread)
self.setParent(QtWidgets.QApplication.instance())
@@ -1641,69 +1679,75 @@ def execute(self):
def assert_ui_thread():
current_thread = QtCore.QThread.currentThread()
- ui_thread = QtWidgets.QApplication.instance().thread() # UI thread
+ ui_thread = QtWidgets.QApplication.instance().thread() # UI thread
if current_thread != ui_thread:
- assert False,"call not on UI thread"
+ assert False, "call not on UI thread"
-def highlight_qcolor(color : QColor, factor : float = 1.1) -> QColor:
- '''
+def highlight_qcolor(color: QColor, factor: float = 1.1) -> QColor:
+ """
computes a highlight color from a QT color object
:param color: a QT color
:param factor: optional, factor
:returns: the new QColor object
-
- '''
- h,s,v,a = color.getHsv()
+
+ """
+ h, s, v, a = color.getHsv()
v = clamp(v * factor, 0, 255)
new_color = color.fromHsv(h, s, v, a)
return new_color
-
-def highlight_color(hex_color:str, factor : float = 1.1):
- ''''
+def highlight_color(hex_color: str, factor: float = 1.1):
+ """'
computes a highlight color from a hex color
:param hex_color: a hex color in the format "#aabbcc
:param factor: optional, factor
- :returns: the new hex color as a string
- '''
+ :returns: the new hex color as a string
+ """
import colorsys
- hex_color = hex_color.lstrip('#')
- r, g, b = tuple(int(h[i:i+2], 16) for i in (0, 2, 4))
- #luminance = 0.299 * r + 0.587 * g + 0.114 * b
+
+ hex_color = hex_color.lstrip("#")
+ r, g, b = tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4))
+ # luminance = 0.299 * r + 0.587 * g + 0.114 * b
h, s, v = colorsys.rgb_to_hsv(r, g, b)
v = v * factor
r, g, b = colorsys.hsv_to_rgb(h, s, v)
return f"#{r:02x}{g:02x}{b:02x}"
+
a_90 = math.radians(90)
a_45 = math.radians(45)
-def snap_to_grid(x : float, y: float, grid_size : int = 50,
- ref_x : float= None, ref_y : float = None,
- ) -> tuple[float, float]:
- ''' snaps a coordinate 0 to 1 to a grid '''
- spacing = 1/grid_size
+
+def snap_to_grid(
+ x: float,
+ y: float,
+ grid_size: int = 50,
+ ref_x: float = None,
+ ref_y: float = None,
+) -> tuple[float, float]:
+ """snaps a coordinate 0 to 1 to a grid"""
+ spacing = 1 / grid_size
gx = spacing * round(x / spacing)
gy = spacing * round(y / spacing)
sx = gx
sy = gy
- # get the rotational snaps
+ # get the rotational snaps
if ref_x is not None and ref_y is not None:
# reference point provided
dx = x - ref_x
dy = y - ref_y
- d = math.dist([ref_x, ref_y],[x,y])
+ d = math.dist([ref_x, ref_y], [x, y])
signed_a = math.atan2(dy, dx)
a = abs(signed_a)
factor = 1 if signed_a > 0 else -1
a_t = math.radians(3)
-
+
if a <= a_t:
# snap horizontal
sy = ref_y
@@ -1714,48 +1758,45 @@ def snap_to_grid(x : float, y: float, grid_size : int = 50,
sy = y
pass
elif a >= a_45 - a_t and a <= a_45 + a_t:
- # snap 45 degrees
+ # snap 45 degrees
sy = ref_y + d * math.sin(a_45) * factor
sy = ref_x + d * math.cos(a_45) * factor
+ return (sx, sy)
- return (sx,sy)
-
-
-
-def float_to_xml(value : float, decimals = 5) -> str:
- ''' converts a float to a string for xml saving'''
+def float_to_xml(value: float, decimals=5) -> str:
+ """converts a float to a string for xml saving"""
return f"{value:0.{decimals}f}"
def is_binary_string(data):
- ''' true if the string is a binary string '''
- if data is None:
- return False
- return isinstance(data, bytes)
+ """true if the string is a binary string"""
+ if data is None:
+ return False
+ return isinstance(data, bytes)
def getHostIp():
- ''' gets the current machine's IP address '''
+ """gets the current machine's IP address"""
import socket
# get the local, non VPN, non loopback address
-
+
try:
# this can blow up on some systems
hostname = socket.getfqdn()
return socket.gethostbyname_ex(hostname)[2][1]
- except:
+ except Exception:
pass
# use the old method
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(0)
try:
- s.connect(('10.254.254.254', 1)) # dummy address
+ s.connect(("10.254.254.254", 1)) # dummy address
host_ip = s.getsockname()[0]
except Exception:
- host_ip= '127.0.0.1'
+ host_ip = "127.0.0.1"
finally:
s.close()
@@ -1763,35 +1804,36 @@ def getHostIp():
def to_byte_string(source) -> tuple:
- ''' converts a byte string or regular string to (string, bytestring) '''
+ """converts a byte string or regular string to (string, bytestring)"""
if source is None:
return (None, None)
if isinstance(source, bytes):
return (source.decode(), source)
- return (source, source.encode('utf-8'))
-
+ return (source, source.encode("utf-8"))
+
def singleShot(callback):
- ''' fires callback in a thread - returns immediately to caller '''
- thread = threading.Thread(target = callback)
+ """fires callback in a thread - returns immediately to caller"""
+ thread = threading.Thread(target=callback)
thread.name = "SingleShot"
thread.start()
+
def cubic_progression(num_points, start, end):
- ''' computes a cubic progression between two numbers'''
+ """computes a cubic progression between two numbers"""
progression = []
for i in range(num_points):
t = i / (num_points - 1) # Normalized parameter from 0 to 1
value = start + (end - start) * (3 * t**2 - 2 * t**3)
progression.append(value)
- return progression
+ return progression
class ResetTimer(threading.Thread):
- ''' a reusable/resettable timer '''
+ """a reusable/resettable timer"""
- def __init__(self, interval, target, args = None, kwargs = None):
+ def __init__(self, interval, target, args=None, kwargs=None):
super().__init__()
self.interval = interval
self.function = target
@@ -1800,16 +1842,15 @@ def __init__(self, interval, target, args = None, kwargs = None):
self.finished = threading.Event()
self._is_reset = True
self._started = False
-
+
def cancel(self):
- ''' stops the timer '''
+ """stops the timer"""
self.finished.set()
@property
def started(self) -> bool:
return self._started
-
def run(self):
self._started = True
while self._is_reset:
@@ -1822,7 +1863,7 @@ def run(self):
self._started = False
def reset(self, interval=None):
- """ Reset the timer """
+ """Reset the timer"""
if interval is not None:
self.interval = interval
@@ -1832,18 +1873,18 @@ def reset(self, interval=None):
self.finished.clear()
-
def getPythonVersion() -> str:
- ''' gets the python environment version as a string '''
+ """gets the python environment version as a string"""
return f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
def decode(data) -> str:
- ''' decodes data and handles invalid characters '''
+ """decodes data and handles invalid characters"""
if data:
- text = data.decode('ascii',errors='replace')
- return text.replace('\ufffd','') # remove junk characters
- return ''
+ text = data.decode("ascii", errors="replace")
+ return text.replace("\ufffd", "") # remove junk characters
+ return ""
+
def valueInRange(value, r1, r2):
if value is None or r1 is None or r2 is None:
diff --git a/gremlin/windows_event_hook.py b/gremlin/windows_event_hook.py
index f301ee85..5bb4d4b4 100644
--- a/gremlin/windows_event_hook.py
+++ b/gremlin/windows_event_hook.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -25,19 +25,20 @@
import gremlin.event_handler
import gremlin.shared_state
from gremlin.singleton_decorator import SingletonDecorator
-
+import win32api
+import logging
user32 = ctypes.WinDLL("user32")
g_keyboard_callbacks = []
g_mouse_callbacks = []
-import win32api
-import logging
+
+
syslog = logging.getLogger("system")
-class KeyEvent:
+class KeyEvent:
"""Structure containing details about a key event."""
def __init__(self, virtual_code, scan_code, is_extended, is_pressed, is_injected):
@@ -76,14 +77,13 @@ def is_pressed(self):
@property
def is_injected(self):
return self._is_injected
-
+
@property
def virtual_code(self):
return self._virtual_code
class MouseEvent:
-
"""Structure containing information about a mouse event."""
def __init__(self, button_id, is_pressed, is_injected):
@@ -102,9 +102,10 @@ def is_pressed(self):
@property
def is_injected(self):
return self._is_injected
-
+
+
def get_last_error():
- ''' last error implementatoin'''
+ """last error implementatoin"""
return win32api.GetLastError()
@@ -123,36 +124,33 @@ def get_last_error():
# Signature of a hook callback function which can be used as a decorator
HOOKPROC = ctypes.WINFUNCTYPE(
- wintypes.LPARAM,
- ctypes.c_int,
- wintypes.WPARAM,
- wintypes.LPARAM
+ wintypes.LPARAM, ctypes.c_int, wintypes.WPARAM, wintypes.LPARAM
)
# Function to hook into an event stream
user32.SetWindowsHookExW.restype = wintypes.HHOOK
user32.SetWindowsHookExW.argtypes = (
- ctypes.c_int, # _In_ idHook
- HOOKPROC, # _In_ lpfn
- wintypes.HINSTANCE, # _In_ hMod
- wintypes.DWORD # _In_ dwThreadId
+ ctypes.c_int, # _In_ idHook
+ HOOKPROC, # _In_ lpfn
+ wintypes.HINSTANCE, # _In_ hMod
+ wintypes.DWORD, # _In_ dwThreadId
)
# Function to call next hook in the chain
user32.CallNextHookEx.restype = wintypes.LPARAM
user32.CallNextHookEx.argtypes = (
- wintypes.HHOOK, # _In_opt_ hhk
- ctypes.c_int, # _In_ nCode
- wintypes.WPARAM, # _In_ wParam
- wintypes.LPARAM # _In_ lParam
+ wintypes.HHOOK, # _In_opt_ hhk
+ ctypes.c_int, # _In_ nCode
+ wintypes.WPARAM, # _In_ wParam
+ wintypes.LPARAM, # _In_ lParam
)
# Retrieve a single message from a stream
user32.GetMessageW.argtypes = (
- wintypes.LPMSG, # _Out_ lpMsg
- wintypes.HWND, # _In_opt_ hWnd
- wintypes.UINT, # _In_ wMsgFilterMin
- wintypes.UINT # _In_ wMsgFilterMax
+ wintypes.LPMSG, # _Out_ lpMsg
+ wintypes.HWND, # _In_opt_ hWnd
+ wintypes.UINT, # _In_ wMsgFilterMin
+ wintypes.UINT, # _In_ wMsgFilterMax
)
# Convert message content
@@ -162,51 +160,51 @@ def get_last_error():
user32.DispatchMessageW.argtypes = (wintypes.LPMSG,)
# Action definitions
-HC_ACTION = 0
-WH_KEYBOARD_LL = 13
-WH_MOUSE_LL = 14
-
-WM_QUIT = 0x0012
-WM_MOUSEMOVE = 0x0200
-WM_LBUTTONDOWN = 0x0201
-WM_LBUTTONUP = 0x0202
-WM_RBUTTONDOWN = 0x0204
-WM_RBUTTONUP = 0x0205
-WM_MBUTTONDOWN = 0x0207
-WM_MBUTTONUP = 0x0208
-WM_MOUSEWHEEL = 0x020A
-WM_XBUTTONDOWN = 0x020B
-WM_XBUTTONUP = 0x020C
-WM_MOUSEHWHEEL = 0x020E
-
-
+HC_ACTION = 0
+WH_KEYBOARD_LL = 13
+WH_MOUSE_LL = 14
+
+WM_QUIT = 0x0012
+WM_MOUSEMOVE = 0x0200
+WM_LBUTTONDOWN = 0x0201
+WM_LBUTTONUP = 0x0202
+WM_RBUTTONDOWN = 0x0204
+WM_RBUTTONUP = 0x0205
+WM_MBUTTONDOWN = 0x0207
+WM_MBUTTONUP = 0x0208
+WM_MOUSEWHEEL = 0x020A
+WM_XBUTTONDOWN = 0x020B
+WM_XBUTTONUP = 0x020C
+WM_MOUSEHWHEEL = 0x020E
class KBDLLHOOKSTRUCT(ctypes.Structure):
-
"""Data structure used with keuboard callbacks."""
_fields_ = (
- ("vkCode", wintypes.DWORD),
- ("scanCode", wintypes.DWORD),
- ("flags", wintypes.DWORD),
- ("time", wintypes.DWORD),
- ("dwExtraInfo", wintypes.WPARAM)
+ ("vkCode", wintypes.DWORD),
+ ("scanCode", wintypes.DWORD),
+ ("flags", wintypes.DWORD),
+ ("time", wintypes.DWORD),
+ ("dwExtraInfo", wintypes.WPARAM),
)
+
+
LPKBDLLHOOKSTRUCT = ctypes.POINTER(KBDLLHOOKSTRUCT)
class MSLLHOOKSTRUCT(ctypes.Structure):
-
"""Data structure used with mouse callbacks."""
_fields_ = (
- ("pt", wintypes.POINT),
- ("mouseData", wintypes.DWORD),
- ("flags", wintypes.DWORD),
- ("time", wintypes.DWORD),
- ("dwExtraInfo", wintypes.WPARAM)
+ ("pt", wintypes.POINT),
+ ("mouseData", wintypes.DWORD),
+ ("flags", wintypes.DWORD),
+ ("time", wintypes.DWORD),
+ ("dwExtraInfo", wintypes.WPARAM),
)
+
+
LPMSLLHOOKSTRUCT = ctypes.POINTER(MSLLHOOKSTRUCT)
@@ -230,8 +228,7 @@ def process_keyboard_event(n_code, w_param, l_param):
is_pressed = w_param in [0x0100, 0x0104]
is_injected = msg.flags is not None and bool(msg.flags & 0x0010)
- #print (f"****** KEYBOARD HOOK: raw scancode: 0x{msg.scanCode:X} w_param: 0x{w_param:X} flags: 0x{msg.flags:X} scan code: {scan_code} (0x{scan_code:x}) ext: {is_extended} pressed: {is_pressed}")
-
+ # print (f"****** KEYBOARD HOOK: raw scancode: 0x{msg.scanCode:X} w_param: 0x{w_param:X} flags: 0x{msg.flags:X} scan code: {scan_code} (0x{scan_code:x}) ext: {is_extended} pressed: {is_pressed}")
# A scan code of 541 indicates AltGr being pressed. AltGr is sent
# as a combination of RAlt + RCtrl to the system and as such
@@ -243,16 +240,24 @@ def process_keyboard_event(n_code, w_param, l_param):
# Create the event and pass it to all all registered callbacks
if msg.scanCode != 541:
- evt = KeyEvent(virtual_code = virtual_code, scan_code = scan_code, is_extended = is_extended, is_pressed = is_pressed, is_injected = is_injected)
+ evt = KeyEvent(
+ virtual_code=virtual_code,
+ scan_code=scan_code,
+ is_extended=is_extended,
+ is_pressed=is_pressed,
+ is_injected=is_injected,
+ )
for cb in g_keyboard_callbacks:
cb(evt)
# Pass the event on to the next callback in the chain
return user32.CallNextHookEx(None, n_code, w_param, l_param)
+
_mouse_h_wheel_time = None
_mouse_v_wheel_time = None
+
@HOOKPROC
def process_mouse_event(n_code, w_param, l_param):
"""Process a single mouse event.
@@ -292,15 +297,17 @@ def process_mouse_event(n_code, w_param, l_param):
process = True
else:
current_time = time.time()
- time_delta = (current_time - _mouse_v_wheel_time) * 1000 # in milliseconds
+ time_delta = (
+ current_time - _mouse_v_wheel_time
+ ) * 1000 # in milliseconds
_mouse_v_wheel_time = current_time
- process = time_delta > 500 # half a second
+ process = time_delta > 500 # half a second
if process:
delta = msg.mouseData >> 16
# print (f"mouse V received: data {msg.mouseData} (0x{msg.mouseData:X}) flags: {msg.flags} (0x{msg.flags:X}) time: {msg.time} (0x{msg.time:X}) extra: {msg.dwExtraInfo} (0x{msg.dwExtraInfo:X}) delta: {delta} (0x{delta:x}) delta / 120: {delta/120}")
if delta == 120:
button_id = gremlin.types.MouseButton.WheelUp
- elif delta == 65416: # -120
+ elif delta == 65416: # -120
button_id = gremlin.types.MouseButton.WheelDown
is_wheel = True
elif w_param == WM_MOUSEHWHEEL:
@@ -312,16 +319,18 @@ def process_mouse_event(n_code, w_param, l_param):
process = True
else:
current_time = time.time()
- time_delta = (current_time - _mouse_h_wheel_time) * 1000 # in milliseconds
+ time_delta = (
+ current_time - _mouse_h_wheel_time
+ ) * 1000 # in milliseconds
_mouse_h_wheel_time = current_time
- process = time_delta > 500 # half a second
+ process = time_delta > 500 # half a second
if process:
delta = msg.mouseData >> 16
# print (f"mouse H received: data {msg.mouseData} (0x{msg.mouseData:X}) flags: {msg.flags} (0x{msg.flags:X}) time: {msg.time} (0x{msg.time:X}) extra: {msg.dwExtraInfo} (0x{msg.dwExtraInfo:X}) delta: {delta} (0x{delta:x}) delta / 120: {delta/120}")
if delta == 120:
button_id = gremlin.types.MouseButton.WheelRight
- elif delta == 65416: # -120
+ elif delta == 65416: # -120
button_id = gremlin.types.MouseButton.WheelLeft
is_wheel = True
@@ -332,7 +341,7 @@ def process_mouse_event(n_code, w_param, l_param):
# Create the event and pass it to all all registered callbacks
evt = MouseEvent(button_id, is_pressed, False)
if is_wheel:
- # queue a release event for mouse wheel
+ # queue a release event for mouse wheel
threading.Thread(target=lambda: _queue_wheel_release(button_id)).start()
for cb in g_mouse_callbacks:
cb(evt)
@@ -341,10 +350,10 @@ def process_mouse_event(n_code, w_param, l_param):
return user32.CallNextHookEx(None, n_code, w_param, l_param)
-
def _queue_wheel_release(button_id):
- ''' queues a mouse wheel release for wheel events '''
+ """queues a mouse wheel release for wheel events"""
import time
+
# print (f"wheel release: {button_id}")
time.sleep(0.1)
evt = MouseEvent(button_id, False, False)
@@ -352,10 +361,8 @@ def _queue_wheel_release(button_id):
cb(evt)
-
@SingletonDecorator
class KeyboardHook:
-
"""Hooks into the event stream and grabs keyboard related events
and passes them on to registered callback functions.
"""
@@ -363,7 +370,6 @@ class KeyboardHook:
def __init__(self):
self._running = False
self._listen_thread = threading.Thread(target=self._listen, daemon=True)
-
def register(self, callback):
"""Registers a new message callback.
@@ -394,10 +400,7 @@ def stop(self):
def _listen(self):
"""Configures the hook and starts listening."""
self.hook_id = user32.SetWindowsHookExW(
- WH_KEYBOARD_LL,
- process_keyboard_event,
- None,
- 0
+ WH_KEYBOARD_LL, process_keyboard_event, None, 0
)
msg = wintypes.MSG()
@@ -411,10 +414,8 @@ def _listen(self):
user32.DispatchMessageW(ctypes.byref(msg))
-
@SingletonDecorator
class MouseHook:
-
"""Hooks into the event stream and grabs mouse related events
and passes them on to registered callback functions.
"""
@@ -432,7 +433,7 @@ def register(self, callback):
g_mouse_callbacks.append(callback)
def unregister(self, callback):
- ''' removes a mouse callback '''
+ """removes a mouse callback"""
global g_mouse_callbacks
if callback in g_mouse_callbacks:
g_mouse_callbacks.remove(callback)
@@ -458,13 +459,9 @@ def stop(self):
def _listen(self):
"""Configures the hook and starts listening."""
syslog = logging.getLogger("system")
- verbose = gremlin.config.Configuration().verbose
syslog.info("MOUSE: HOOK started")
self.hook_id = user32.SetWindowsHookExW(
- WH_MOUSE_LL,
- process_mouse_event,
- None,
- 0
+ WH_MOUSE_LL, process_mouse_event, None, 0
)
msg = wintypes.MSG()
@@ -476,4 +473,3 @@ def _listen(self):
raise ctypes.WinError(get_last_error())
user32.TranslateMessage(ctypes.byref(msg))
user32.DispatchMessageW(ctypes.byref(msg))
-
diff --git a/2024-08-03_18-10-21 labels.png b/jgEx-Example_pics/2024-08-03_18-10-21 labels.png
similarity index 100%
rename from 2024-08-03_18-10-21 labels.png
rename to jgEx-Example_pics/2024-08-03_18-10-21 labels.png
diff --git a/calibration_button.png b/jgEx-Example_pics/calibration_button.png
similarity index 100%
rename from calibration_button.png
rename to jgEx-Example_pics/calibration_button.png
diff --git a/calibration_dialog.png b/jgEx-Example_pics/calibration_dialog.png
similarity index 100%
rename from calibration_dialog.png
rename to jgEx-Example_pics/calibration_dialog.png
diff --git a/copy_paste_all_operations.png b/jgEx-Example_pics/copy_paste_all_operations.png
similarity index 100%
rename from copy_paste_all_operations.png
rename to jgEx-Example_pics/copy_paste_all_operations.png
diff --git a/copy_paste_operations.png b/jgEx-Example_pics/copy_paste_operations.png
similarity index 100%
rename from copy_paste_operations.png
rename to jgEx-Example_pics/copy_paste_operations.png
diff --git a/dark_manage_modes.svg b/jgEx-Example_pics/dark_manage_modes.svg
similarity index 100%
rename from dark_manage_modes.svg
rename to jgEx-Example_pics/dark_manage_modes.svg
diff --git a/gamepad_action.png b/jgEx-Example_pics/gamepad_action.png
similarity index 100%
rename from gamepad_action.png
rename to jgEx-Example_pics/gamepad_action.png
diff --git a/gate_axis_diagram_1.png b/jgEx-Example_pics/gate_axis_diagram_1.png
similarity index 100%
rename from gate_axis_diagram_1.png
rename to jgEx-Example_pics/gate_axis_diagram_1.png
diff --git a/gate_axis_gate_mapping.png b/jgEx-Example_pics/gate_axis_gate_mapping.png
similarity index 100%
rename from gate_axis_gate_mapping.png
rename to jgEx-Example_pics/gate_axis_gate_mapping.png
diff --git a/gate_axis_gate_widget.png b/jgEx-Example_pics/gate_axis_gate_widget.png
similarity index 100%
rename from gate_axis_gate_widget.png
rename to jgEx-Example_pics/gate_axis_gate_widget.png
diff --git a/gate_axis_range_widget.png b/jgEx-Example_pics/gate_axis_range_widget.png
similarity index 100%
rename from gate_axis_range_widget.png
rename to jgEx-Example_pics/gate_axis_range_widget.png
diff --git a/gated_axis.png b/jgEx-Example_pics/gated_axis.png
similarity index 100%
rename from gated_axis.png
rename to jgEx-Example_pics/gated_axis.png
diff --git a/gated_axis_range_action.png b/jgEx-Example_pics/gated_axis_range_action.png
similarity index 100%
rename from gated_axis_range_action.png
rename to jgEx-Example_pics/gated_axis_range_action.png
diff --git a/gremlin_ex_curve_editor.png b/jgEx-Example_pics/gremlin_ex_curve_editor.png
similarity index 100%
rename from gremlin_ex_curve_editor.png
rename to jgEx-Example_pics/gremlin_ex_curve_editor.png
diff --git a/gremlin_ex_curve_input.png b/jgEx-Example_pics/gremlin_ex_curve_input.png
similarity index 100%
rename from gremlin_ex_curve_input.png
rename to jgEx-Example_pics/gremlin_ex_curve_input.png
diff --git a/gremlin_ex_keyboard.png b/jgEx-Example_pics/gremlin_ex_keyboard.png
similarity index 100%
rename from gremlin_ex_keyboard.png
rename to jgEx-Example_pics/gremlin_ex_keyboard.png
diff --git a/gremlin_ex_profile_options.png b/jgEx-Example_pics/gremlin_ex_profile_options.png
similarity index 100%
rename from gremlin_ex_profile_options.png
rename to jgEx-Example_pics/gremlin_ex_profile_options.png
diff --git a/keyboard_input.png b/jgEx-Example_pics/keyboard_input.png
similarity index 100%
rename from keyboard_input.png
rename to jgEx-Example_pics/keyboard_input.png
diff --git a/joystick_gremlin.py b/joystick_gremlin.py
index f8c24a87..3c61662e 100644
--- a/joystick_gremlin.py
+++ b/joystick_gremlin.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -23,15 +23,12 @@
import argparse
import ctypes
-import hashlib
import logging
import os
import gc
-import weakref
import sys
import time
import traceback
-import threading
from threading import Lock
import webbrowser
@@ -54,23 +51,27 @@
import gremlin.curve_handler
import gremlin.gated_handler
import gremlin.input_types
-import anytree
-
-from gremlin.util import InvokeUiMethod, assert_ui_thread
# Import QtMultimedia so pyinstaller doesn't miss it
import PySide6
-from PySide6 import QtCore, QtGui, QtWidgets, QtMultimedia
+from PySide6 import QtCore, QtGui, QtWidgets
from gremlin.types import TabDeviceType
-
from gremlin.input_types import InputType
from gremlin.types import DeviceType
-from gremlin.util import load_icon, load_pixmap, userprofile_path, find_file, waitCursor, popCursor, pushCursor, isCursorActive
+from gremlin.util import (
+ load_icon,
+ load_pixmap,
+ userprofile_path,
+ find_file,
+ popCursor,
+ pushCursor,
+ isCursorActive,
+)
import gremlin.shared_state
import gremlin.base_profile
import gremlin.event_handler
@@ -79,7 +80,6 @@
import gremlin.gated_handler
-
import gremlin.code_runner
import gremlin.keyboard
@@ -96,15 +96,7 @@
from gremlin.util import log_sys_error, compare_path
import gremlin.util
-
-
-
-# Figure out the location of the code / executable and change the working
-# directory accordingly
-install_path = os.path.normcase(os.path.dirname(os.path.abspath(sys.argv[0])))
-os.chdir(install_path)
-
-
+from gremlin.singleton_decorator import SingletonDecorator
import gremlin.ui.axis_calibration
import gremlin.ui.ui_common
import gremlin.ui.joystick_device
@@ -114,11 +106,14 @@
import gremlin.ui.user_plugin_management
import gremlin.ui.profile_creator
import gremlin.ui.profile_settings
+from gremlin.ui.ui_gremlin import Ui_Gremlin
+# from gremlin.input_devices import remote_state
-from PySide6 import QtCore
+# Figure out the location of the code / executable and change the working
+# directory accordingly
+install_path = os.path.normcase(os.path.dirname(os.path.abspath(sys.argv[0])))
+os.chdir(install_path)
-from gremlin.ui.ui_gremlin import Ui_Gremlin
-#from gremlin.input_devices import remote_state
syslog = logging.getLogger("system")
@@ -131,60 +126,57 @@
ui = None
syslog = logging.getLogger("system")
-from gremlin.singleton_decorator import SingletonDecorator
@SingletonDecorator
-class Version():
+class Version:
version = APPLICATION_VERSION
class TabData:
- def __init__(self, device_guid, tab_type : TabDeviceType):
+ def __init__(self, device_guid, tab_type: TabDeviceType):
self._device_guid = device_guid
self._tab_type = tab_type
@property
def device_guid(self) -> str:
return self._device_guid
+
@property
def tab_type(self) -> TabDeviceType:
return self._tab_type
-class GremlinUi(QtWidgets.QMainWindow):
+class GremlinUi(QtWidgets.QMainWindow):
"""Main window of the Joystick Gremlin user interface."""
-
-
ui = None
# input_lock = threading.Lock() # critical code operations - prevents reentry
-
-
def __init__(self, parent=None):
"""Creates a new main ui window.
:param parent the parent of this window
"""
-
QtWidgets.QMainWindow.__init__(self, parent)
self.ui = Ui_Gremlin()
- #self.update_theme()
+ # self.update_theme()
self.ui.setupUi(self)
- #self._recreate_tab_widget()
+ # self._recreate_tab_widget()
- self._profile_hash = None # active profile hash to detect changes
+ self._profile_hash = None # active profile hash to detect changes
self.locked = False
self.activate_locked = False
self._selection_locked = False
- self.joystick_event_lock = Lock() # lock for joystick events
+ self.joystick_event_lock = Lock() # lock for joystick events
self.device_change_locked = False
- self._device_change_queue = 0 # count of device updates while the UI is already updating
- self._runtime_mode_map = {} # map of runtime processes to their last runtime mode
- self._process_runtime_map = {} # map of MODE to process associated with a profile - the process executable is the key
- self._active_process_path = None # active mapped process path
+ self._device_change_queue = (
+ 0 # count of device updates while the UI is already updating
+ )
+ self._runtime_mode_map = {} # map of runtime processes to their last runtime mode
+ self._process_runtime_map = {} # map of MODE to process associated with a profile - the process executable is the key
+ self._active_process_path = None # active mapped process path
self._resize_count = 0
@@ -196,35 +188,50 @@ def __init__(self, parent=None):
self.ui.devices.tabChanged.connect(self._tab_selected)
self.ui.devices.tabMoveCompleted.connect(self._tab_moved_cb)
- self._last_input_item = None # last selected input item
-
+ self._last_input_item = None # last selected input item
gremlin.shared_state.application_version = APPLICATION_BASE
self.config = gremlin.config.Configuration()
# last input from last run to restore
- self.restore_input = self.config.get_last_input()
-
-
+ self.restore_input = self.config.get_last_input()
# prevent saving anything until we have a profile loaded
el = gremlin.event_handler.EventListener()
el.push_input_selection()
- el.request_activate.connect(self.activate) # hook activation / deactivation requests
- el.refresh_devices.connect(self._create_tabs) # refresh device list
+ el.request_activate.connect(
+ self.activate
+ ) # hook activation / deactivation requests
+ el.refresh_devices.connect(self._create_tabs) # refresh device list
# highlighing options
- self._icon_on = gremlin.util.load_icon("mdi.checkbox-blank-circle", qta_color= gremlin.ui.ui_common.Color.activeColor())
- self._icon_off = gremlin.util.load_icon("mdi.checkbox-blank-circle", qta_color= gremlin.ui.ui_common.Color.inactiveColor())
- self._button_highlighting_enabled = self.config.highlight_input_buttons # true if highlighting on buttons
- self._axis_highlighting_enabled = self.config.highlight_input_axis # true if highligthing on axes
- self._input_highlighting_enabled = self.config.highlight_enabled # on/off global
-
- el.enable_highlight_changed.connect(self._highlight_enable_changed) # fires when highlight mode is toggled
-
- self._last_highlight_key = None # last event processed for input highlights
- el.toggle_highlight.connect(self._handle_highlight_state) # input highlighting states
+ self._icon_on = gremlin.util.load_icon(
+ "mdi.checkbox-blank-circle",
+ qta_color=gremlin.ui.ui_common.Color.activeColor(),
+ )
+ self._icon_off = gremlin.util.load_icon(
+ "mdi.checkbox-blank-circle",
+ qta_color=gremlin.ui.ui_common.Color.inactiveColor(),
+ )
+ self._button_highlighting_enabled = (
+ self.config.highlight_input_buttons
+ ) # true if highlighting on buttons
+ self._axis_highlighting_enabled = (
+ self.config.highlight_input_axis
+ ) # true if highligthing on axes
+ self._input_highlighting_enabled = (
+ self.config.highlight_enabled
+ ) # on/off global
+
+ el.enable_highlight_changed.connect(
+ self._highlight_enable_changed
+ ) # fires when highlight mode is toggled
+
+ self._last_highlight_key = None # last event processed for input highlights
+ el.toggle_highlight.connect(
+ self._handle_highlight_state
+ ) # input highlighting states
el.ui_ready.connect(self._ui_ready)
gremlin.shared_state.aborted = False
el.request_profile_stop.connect(lambda x: self.abort(x))
@@ -232,23 +239,18 @@ def __init__(self, parent=None):
# Process monitor
self.process_monitor = gremlin.process_monitor.ProcessMonitor()
self.process_monitor.process_changed.connect(self._process_changed_cb)
- self.current_process_path = None # current process
- self._last_tts_notify_time = None # last autoload process change TTS time
+ self.current_process_path = None # current process
+ self._last_tts_notify_time = None # last autoload process change TTS time
self._process_change_in_progress = False
# Default path variable before any runtime changes
self._base_path = list(sys.path)
-
self._init_tab_data()
self._reset_tab_data()
self.runner = gremlin.code_runner.CodeRunner()
- self.repeater = gremlin.repeater.Repeater(
- [],
- self._update_statusbar_repeater
- )
-
+ self.repeater = gremlin.repeater.Repeater([], self._update_statusbar_repeater)
self._status_bar_last_runtime_mode = None
self._status_bar_last_edit_mode = None
@@ -276,18 +278,22 @@ def __init__(self, parent=None):
self._last_input_change_timestamp = time.time()
self._last_input_event = None
- self._last_input_identifier = None # input id of the last triggered device
+ self._last_input_identifier = None # input id of the last triggered device
# self._last_device_guid = None # string representation of the last GUID of the last triggered device
# self._last_input_type = None # last input type (InputType) selected
# self._last_input_id = None # last input id selected
self._last_tab_switch = None
- self._input_delay = 0.25 # delay in seconds between joystick inputs for highlighting purposes
- self._joystick_axis_highlight_deviation = 0.5 # deviation needed before registering a highlight on axis change (this is to avoid noisy inputs and prevent the UI from going crazy) 1.0 = half travel
- self._joystick_axis_highlight_map = {} # map of device / axis values
+ self._input_delay = (
+ 0.25 # delay in seconds between joystick inputs for highlighting purposes
+ )
+ self._joystick_axis_highlight_deviation = 0.5 # deviation needed before registering a highlight on axis change (this is to avoid noisy inputs and prevent the UI from going crazy) 1.0 = half travel
+ self._joystick_axis_highlight_map = {} # map of device / axis values
self._event_process_registry = {}
- self._temp_input_axis_override = False # flag that tracks device swaps on axis
- self._temp_input_axis_only_override = False # flag that tracks device swaps but on axis only (shift + ctrl key)
+ self._temp_input_axis_override = False # flag that tracks device swaps on axis
+ self._temp_input_axis_only_override = (
+ False # flag that tracks device swaps but on axis only (shift + ctrl key)
+ )
# Create all required UI elements
self._create_system_tray()
@@ -299,19 +305,17 @@ def __init__(self, parent=None):
# hook status bar to events
el = gremlin.event_handler.EventListener()
el.broadcast_changed.connect(self._update_status_bar)
- el.keyboard_event.connect(self._kb_event_cb) # for repeaters
-
+ el.keyboard_event.connect(self._kb_event_cb) # for repeaters
+
el.suspend_keyboard_input.connect(self._kb_suspend_cb)
el.profile_start.connect(self._profile_start)
el.profile_stop.connect(self._profile_stop)
-
+
el.profile_changed.connect(self._profile_changed_cb)
el.button_state_change.connect(self._button_state_change)
el.axis_state_change.connect(self._axis_state_change)
el.input_selection_changed.connect(self._input_changed_handler)
-
-
# hook input selection
el.select_input.connect(self._select_input_handler)
@@ -322,7 +326,6 @@ def __init__(self, parent=None):
eh = gremlin.event_handler.EventHandler()
eh.profile_changed.connect(self._profile_changed_cb)
-
self._context_menu_tab_index = None
# Load existing configuration or create a new one otherwise
@@ -330,9 +333,9 @@ def __init__(self, parent=None):
# check if this was a profile swap that we load the profile from the current user folder
current_profile_folder = userprofile_path().lower()
last_profile = self.config.last_profile.lower()
- if not current_profile_folder in last_profile:
+ if current_profile_folder not in last_profile:
_, base_file = os.path.split(last_profile)
- located_profile = find_file(base_file,current_profile_folder)
+ located_profile = find_file(base_file, current_profile_folder)
if located_profile:
self.config.last_profile = located_profile
self._do_load_profile(self.config.last_profile)
@@ -360,112 +363,103 @@ def __init__(self, parent=None):
GremlinUi.ui = self
-
-
el.config_option_changed.connect(self._config_option_changed)
-
def _init_tab_data(self):
- self._widget_cache = {} # map of device widgets keyed by the device GUID
+ self._widget_cache = {} # map of device widgets keyed by the device GUID
def _reset_tab_data(self):
-
- self._tab_index_map = {} # map of device_guids indexed by their tab index from the tab header (index -> device_guid)
- self._tab_device_map = {} # map of tab positions index mapped by device guid for the tab header (device_guid -> index)
- self._tab_name_map = {} # map fo device guid to device name for tabs
-
- self._current_tab_widget = None # selected content widget for the current device
- self._current_tab_device_guid = None # selected device GUID
- self._current_tab_input_id = None # selected input in the current tab
+ self._tab_index_map = {} # map of device_guids indexed by their tab index from the tab header (index -> device_guid)
+ self._tab_device_map = {} # map of tab positions index mapped by device guid for the tab header (device_guid -> index)
+ self._tab_name_map = {} # map fo device guid to device name for tabs
+
+ self._current_tab_widget = (
+ None # selected content widget for the current device
+ )
+ self._current_tab_device_guid = None # selected device GUID
+ self._current_tab_input_id = None # selected input in the current tab
self._joystick_device_guids = []
self.tab_guids = []
-
self._clear_tabs()
-
def _clear_tabs(self):
- # remove all tab headers
- if self.ui.devices:
- with QtCore.QSignalBlocker(self.ui.devices):
- while self.ui.devices.count():
- self.ui.devices.removeTab(0)
-
- def _add_tab(self, device_guid, tab_type, index = None) -> int:
- ''' adds a tab to the tab header
- :param device_guid: the device guid of the device to add
- :param index: optiona, if specified, the index to add
- :returns int: the index of the tab added
- '''
+ # remove all tab headers
+ with QtCore.QSignalBlocker(self.ui.devices):
+ while self.ui.devices.count():
+ self.ui.devices.removeTab(0)
+ def _add_tab(self, device_guid, tab_type, index=None) -> int:
+ """adds a tab to the tab header
+ :param device_guid: the device guid of the device to add
+ :param index: optiona, if specified, the index to add
+ :returns int: the index of the tab added
+ """
-
# syslog = logging.getLogger("system")
device_name = gremlin.shared_state.get_device_name(device_guid)
if not device_name:
syslog.error(f"Unknown device GUID found in tabs: {device_guid}")
return
-
+
if device_guid in self._tab_device_map:
syslog.error(f"Duplicate GUID found in tabs: {device_name} {device_guid}")
assert False
-
-
if index is None:
position = self.ui.devices.addTab(device_name)
else:
position = self.ui.devices.insertTab(index, device_name)
-
+
self._tab_device_map[device_guid] = position
self._tab_index_map[position] = device_guid
self._tab_name_map[device_guid] = device_name
-
+
self.ui.devices.setTabData(position, TabData(device_guid, tab_type))
if tab_type == TabDeviceType.Joystick:
self._joystick_device_guids.append(device_guid)
-
+
verbose = gremlin.config.Configuration().verbose_mode_device
- if verbose: syslog.info(f"Add tab: {position} {device_name} {device_guid}")
+ if verbose:
+ syslog.info(f"Add tab: {position} {device_name} {device_guid}")
return position
-
-
+
def _get_tab_map(self):
- ''' gets tab configuration data as a dictionary indexed by tab index holding device id, device name and device widget type
+ """gets tab configuration data as a dictionary indexed by tab index holding device id, device name and device widget type
:returns: list of (device_guid, device_name, tabdevice_type, tab_index)
- '''
+ """
tab_count = self.ui.devices.count()
tab_map = {}
for index in range(tab_count):
- data : TabData = self.ui.devices.tabData(index)
+ data: TabData = self.ui.devices.tabData(index)
device_guid = data.device_guid
device_name = self._tab_name_map[device_guid]
-
+
tab_map[index] = (device_guid, device_name, data.tab_type, index)
return tab_map
-
+
def _get_tab_type(self, index):
- ''' gets the tab type for the given tab index '''
+ """gets the tab type for the given tab index"""
data = self.ui.devices.tabData(index)
return data.tab_type
-
def _reindex_tabs(self):
- ''' rebuilds the tab index '''
+ """rebuilds the tab index"""
self._tab_index_map.clear()
self._tab_device_map.clear()
self._tab_name_map.clear()
-
+
verbose = gremlin.config.Configuration().verbose_mode_device
# syslog = logging.getLogger("system")
- if verbose: syslog.info("UI: tab reindex")
+ if verbose:
+ syslog.info("UI: tab reindex")
for index in range(self.ui.devices.count()):
data = self.ui.devices.tabData(index)
device_guid = data.device_guid
@@ -473,30 +467,28 @@ def _reindex_tabs(self):
self._tab_index_map[index] = device_guid
self._tab_device_map[device_guid] = index
self._tab_name_map[device_guid] = device_name
-
- if verbose:
+
+ if verbose:
syslog.info(f"\t[{index}] {device_name} {device_guid}")
def _tabswitch_needed(self, device_guid) -> bool:
- ''' checks to see if the device tab is the current tab or not '''
-
+ """checks to see if the device tab is the current tab or not"""
+
tab_device_guid = self._current_tab_device_guid
if isinstance(device_guid, str):
device_guid = str(device_guid)
return tab_device_guid != device_guid
-
def _inputswitch_needed(self, device_guid, input_id) -> bool:
- ''' checks to see if an input switch is needed '''
+ """checks to see if an input switch is needed"""
if isinstance(device_guid, str):
device_guid = str(device_guid)
tab_device_guid = self._current_tab_device_guid
tab_input_id = self._current_tab_input_id
return tab_device_guid != device_guid or tab_input_id != input_id
-
def _button_state_change(self, event):
- ''' button changed - triggered only at design time '''
+ """button changed - triggered only at design time"""
is_tabswitch_enabled = self.config.highlight_autoswitch
device_guid = event.device_guid
@@ -514,39 +506,38 @@ def _button_state_change(self, event):
if not is_button:
# highlight disabled
return
-
+
tab_switch_needed = self._tabswitch_needed(device_guid)
- input_switch_needed = tab_switch_needed or self._inputswitch_needed(device_guid, input_id)
+ input_switch_needed = tab_switch_needed or self._inputswitch_needed(
+ device_guid, input_id
+ )
if tab_switch_needed and not is_tabswitch_enabled and not input_switch_needed:
# not setup to auto change tabs (override via shift/control keys)
return
-
+
# see if input needs to change
- input_switch_needed = tab_switch_needed or self._inputswitch_needed(device_guid, input_id)
+ input_switch_needed = tab_switch_needed or self._inputswitch_needed(
+ device_guid, input_id
+ )
if not input_switch_needed:
return
-
+
# trigger switch
self._select_input_handler(device_guid, input_type, input_id)
-
-
def _axis_state_change(self, event):
- ''' axis changed - triggered only at design time '''
-
+ """axis changed - triggered only at design time"""
# avoid input spamming
if self._last_input_timestamp + self._input_delay > time.time():
# delay not occured yet
return
-
-
self._last_input_timestamp = time.time()
if gremlin.shared_state.is_highlighting_suspended:
return
-
+
is_axis = self.is_axis_highlighting
if not is_axis:
# highlight disabled
@@ -555,45 +546,49 @@ def _axis_state_change(self, event):
input_type = event.event_type
input_id = event.identifier
value = event.value
-
+
tab_switch_needed = self._tabswitch_needed(device_guid)
is_tabswitch_enabled = self.config.highlight_autoswitch
- input_switch_needed = tab_switch_needed or self._inputswitch_needed(device_guid, input_id)
+ input_switch_needed = tab_switch_needed or self._inputswitch_needed(
+ device_guid, input_id
+ )
if tab_switch_needed and not is_tabswitch_enabled and not input_switch_needed:
# not setup to auto change tabs (override via shift/control keys)
return
-
+
# see if input needs to change
-
+
if not input_switch_needed:
return
-
+
# check for axis deviation
- if not device_guid in self._joystick_axis_highlight_map:
+ if device_guid not in self._joystick_axis_highlight_map:
self._joystick_axis_highlight_map[device_guid] = {}
- if not input_id in self._joystick_axis_highlight_map[device_guid]:
+ if input_id not in self._joystick_axis_highlight_map[device_guid]:
self._joystick_axis_highlight_map[device_guid][input_id] = value
deviation = 2.0
else:
- deviation = abs(self._joystick_axis_highlight_map[device_guid][input_id] - value)
+ deviation = abs(
+ self._joystick_axis_highlight_map[device_guid][input_id] - value
+ )
if deviation < self._joystick_axis_highlight_deviation:
# deviation insufficient to trigger a tab switch
return
-
+
# trigger switch
self._select_input_handler(device_guid, input_type, input_id)
@QtCore.Slot(int)
def _tab_selected(self, index):
- ''' called when the device tab selection is changed
+ """called when the device tab selection is changed
:param: index = the index of the tab that was selected
- '''
+ """
if self.ui.devices.moveInProgress:
# ignore if the tab is being dragged
- return
+ return
device_guid = self.getDeviceGuidForTabIndex(index)
@@ -602,22 +597,45 @@ def _tab_selected(self, index):
if device_guid is not None:
verbose = gremlin.config.Configuration().verbose
if verbose:
- syslog.info(f"Tab index change: new tab [{index}] {self.ui.devices.tabText(index)} - device {device_guid} {gremlin.shared_state.get_device_name(device_guid)}")
+ syslog.info(
+ f"Tab index change: new tab [{index}] {self.ui.devices.tabText(index)} - device {device_guid} {gremlin.shared_state.get_device_name(device_guid)}"
+ )
self.last_tab_index = index
- _, restore_input_type, restore_input_id = self.config.get_last_input(device_guid)
- self._select_input(device_guid = device_guid, input_type = restore_input_type, input_id = restore_input_id, force_update =True, force_switch=True, tab_changed = True)
+ _, restore_input_type, restore_input_id = self.config.get_last_input(
+ device_guid
+ )
+ self._select_input(
+ device_guid=device_guid,
+ input_type=restore_input_type,
+ input_id=restore_input_id,
+ force_update=True,
+ force_switch=True,
+ tab_changed=True,
+ )
gremlin.util.popCursor()
def add_custom_tools_menu(self, menuTools):
- ''' adds custom tools to the menu '''
- self._actionTabSort = QtGui.QAction("Sort Devices", self, triggered = self._tab_sort_cb)
- self._actionTabSort.setToolTip("Sorts input hardware devices in alphabetical order")
- self._actionTabSubstitute = QtGui.QAction("Device Substitution...", self, triggered = self._tab_substitute_cb)
+ """adds custom tools to the menu"""
+ self._actionTabSort = QtGui.QAction(
+ "Sort Devices", self, triggered=self._tab_sort_cb
+ )
+ self._actionTabSort.setToolTip(
+ "Sorts input hardware devices in alphabetical order"
+ )
+ self._actionTabSubstitute = QtGui.QAction(
+ "Device Substitution...", self, triggered=self._tab_substitute_cb
+ )
self._actionTabSubstitute.setToolTip("Substitute device GUIDs")
- self._actionTabClearMap = QtGui.QAction("Clear Mappings", self, triggered = self._tab_clear_map_cb)
- self._actionTabClearMap.setToolTip("Clears all mappings from the current device")
- self._actionTabImport = QtGui.QAction("Import Profile...", self, triggered = self._tab_import_cb)
+ self._actionTabClearMap = QtGui.QAction(
+ "Clear Mappings", self, triggered=self._tab_clear_map_cb
+ )
+ self._actionTabClearMap.setToolTip(
+ "Clears all mappings from the current device"
+ )
+ self._actionTabImport = QtGui.QAction(
+ "Import Profile...", self, triggered=self._tab_import_cb
+ )
self._actionTabImport.setToolTip("Import profile data into the current device")
menuTools.addSeparator()
@@ -626,22 +644,22 @@ def add_custom_tools_menu(self, menuTools):
menuTools.addAction(self._actionTabImport)
menuTools.addAction(self._actionTabClearMap)
-
-
def _tab_context_menu_cb(self, pos):
- ''' tab context menu '''
+ """tab context menu"""
tab_index = self.ui.devices.tabAt(pos)
if tab_index == -1:
return
self._context_menu_tab_index = tab_index
- data : TabData = self.ui.devices.tabData(tab_index)
+ data: TabData = self.ui.devices.tabData(tab_index)
tab_type = data.tab_type
- #device_guid = data.device_guid
+ # device_guid = data.device_guid
# substitution is only available if the profile has been saved (a new profile matches the current devices by definition)
- is_enabled = tab_type == TabDeviceType.Joystick \
- and self.profile is not None\
- and self.profile.profile_file is not None\
+ is_enabled = (
+ tab_type == TabDeviceType.Joystick
+ and self.profile is not None
+ and self.profile.profile_file is not None
and os.path.isfile(self.profile.profile_file)
+ )
self._actionTabSubstitute.setEnabled(is_enabled)
menu = QtWidgets.QMenu(self)
menu.addAction(self._actionTabSort)
@@ -651,29 +669,31 @@ def _tab_context_menu_cb(self, pos):
menu.exec_(QtGui.QCursor.pos())
def _tab_sort_cb(self):
- ''' sorts the tabs '''
+ """sorts the tabs"""
self._sort_tabs()
-
def _tab_clear_map_cb(self):
- ''' clears the mappings from the current tab '''
+ """clears the mappings from the current tab"""
tab_guid = gremlin.util.parse_guid(self._active_tab_guid())
- device : gremlin.base_profile.Device = gremlin.shared_state.current_profile.devices[tab_guid]
+ device: gremlin.base_profile.Device = (
+ gremlin.shared_state.current_profile.devices[tab_guid]
+ )
current_mode = gremlin.shared_state.current_mode
- msgbox = gremlin.ui.ui_common.ConfirmBox(f"Remove all mappings from {device.name}, mode [{current_mode}]?")
+ msgbox = gremlin.ui.ui_common.ConfirmBox(
+ f"Remove all mappings from {device.name}, mode [{current_mode}]?"
+ )
result = msgbox.show()
if result == QtWidgets.QMessageBox.StandardButton.Ok:
self._tab_clear_map_execute(device, current_mode)
-
def _tab_import_cb(self):
- ''' imports a profile into the device '''
+ """imports a profile into the device"""
# tab_guid = gremlin.util.parse_guid(gremlin.shared_state.ui._active_tab_guid())
# device : gremlin.base_profile.Device = gremlin.shared_state.current_profile.devices[tab_guid]
gremlin.import_profile.import_profile()
def _tab_clear_map_execute(self, device, mode_name):
- ''' removes all mappings from the given device in the active mode '''
+ """removes all mappings from the given device in the active mode"""
mode = device.modes[mode_name]
for input_type in mode.config.keys():
@@ -681,10 +701,8 @@ def _tab_clear_map_execute(self, device, mode_name):
entry.containers.clear()
self._create_tabs()
-
-
def _tab_substitute_cb(self, pos):
- ''' substitution dialog for devices '''
+ """substitution dialog for devices"""
if self._context_menu_tab_index is None:
# not setup yet - use the first discovered device in the profile
profile = gremlin.shared_state.current_profile
@@ -693,32 +711,35 @@ def _tab_substitute_cb(self, pos):
if self._context_menu_tab_index is None:
# no hardware tab found
- gremlin.ui.dialogs.ok_message_box("No input hardware was found to substitute.")
+ gremlin.ui.dialogs.ok_message_box(
+ "No input hardware was found to substitute."
+ )
return
# verify we have hardware to substitute with
- data : TabData = self.ui.devices.tabData(self._context_menu_tab_index)
+ data: TabData = self.ui.devices.tabData(self._context_menu_tab_index)
device_guid = data.device_guid
device_name = self.ui.devices.tabText(self._context_menu_tab_index)
- dialog = gremlin.ui.dialogs.SubstituteDialog(device_guid=device_guid, device_name=device_name, parent = self)
+ dialog = gremlin.ui.dialogs.SubstituteDialog(
+ device_guid=device_guid, device_name=device_name, parent=self
+ )
dialog.setModal(True)
dialog.accepted.connect(self._substitute_complete_cb)
gremlin.util.centerDialog(dialog)
dialog.show()
def _substitute_complete_cb(self):
- ''' substitution complete - reload profile '''
- profile : gremlin.base_profile.Profile = gremlin.shared_state.current_profile
+ """substitution complete - reload profile"""
+ profile: gremlin.base_profile.Profile = gremlin.shared_state.current_profile
self.load_profile(profile.profile_file)
-
- def _profile_changed_cb(self, new_profile = None):
- ''' called when the a profile should be loaded '''
+ def _profile_changed_cb(self, new_profile=None):
+ """called when the a profile should be loaded"""
if new_profile is None:
# save current contents to a temporary file
- profile : gremlin.base_profile.Profile = gremlin.shared_state.current_profile
+ profile: gremlin.base_profile.Profile = gremlin.shared_state.current_profile
tmp_file = os.path.join(os.getenv("temp"), gremlin.util.get_guid() + ".xml")
profile.save(tmp_file)
self._do_load_profile(tmp_file)
@@ -730,10 +751,9 @@ def _profile_changed_cb(self, new_profile = None):
@property
def current_profile(self):
- ''' gets the curernt active profile '''
+ """gets the curernt active profile"""
return self.profile
-
def closeEvent(self, evt):
"""Terminate the entire application if the main window is closed.
@@ -744,13 +764,12 @@ def closeEvent(self, evt):
self.hide()
evt.ignore()
else:
-
# terminate the idle thread
self.process_monitor.running = False
try:
if self.ui.tray_icon:
self.ui_tray_icon = None
- except:
+ except Exception:
pass
QtCore.QCoreApplication.quit()
@@ -787,36 +806,33 @@ def about(self):
lambda: self._remove_modal_window("about")
)
-
@property
def current_mode(self) -> str:
- ''' returns the current active profile mode '''
+ """returns the current active profile mode"""
return gremlin.shared_state.current_mode
-
@property
def current_profile(self) -> gremlin.base_profile.Profile:
return gremlin.shared_state.current_profile
-
def calibration(self):
"""Opens the calibration window."""
# indicate the feature has been deprecated
return
-
def device_information(self):
"""Opens the device information window."""
- self.modal_windows["device_information"] = \
+ self.modal_windows["device_information"] = (
gremlin.ui.dialogs.DeviceInformationUi(self.profile)
+ )
geom = self.geometry()
w = 600
h = 400
self.modal_windows["device_information"].setGeometry(
- int(geom.x() + geom.width() / 2 - w/2),
- int(geom.y() + geom.height() / 2 - h/2),
+ int(geom.x() + geom.width() / 2 - w / 2),
+ int(geom.y() + geom.height() / 2 - h / 2),
w,
- h
+ h,
)
self.modal_windows["device_information"].show()
self.modal_windows["device_information"].closed.connect(
@@ -832,12 +848,11 @@ def log_window(self):
)
def log_edit(self):
- ''' opens the log file in the editor '''
+ """opens the log file in the editor"""
log_file = os.path.join(gremlin.util.userprofile_path(), "system.log")
if os.path.isfile(log_file):
gremlin.util.display_file(log_file)
-
def manage_modes(self):
"""Opens the mode management window."""
dialog = gremlin.ui.dialogs.ModeManagerUi(self.profile)
@@ -846,7 +861,6 @@ def manage_modes(self):
dialog.closed.connect(lambda: self._remove_modal_window("mode_manager"))
dialog.show()
-
def merge_axis(self):
"""Opens the modal window to define axis merging."""
dialog = gremlin.ui.merge_axis.MergeAxisUi(self.profile)
@@ -856,18 +870,16 @@ def merge_axis(self):
dialog.closed.connect(lambda: self._remove_modal_window("merge_axis"))
dialog.show()
-
def options_dialog(self):
"""Opens the options dialog."""
dialog = gremlin.ui.dialogs.OptionsUi()
self.modal_windows["options"] = dialog
dialog.setWindowModality(QtCore.Qt.ApplicationModal)
dialog.ensurePolished()
- gremlin.util.centerDialog(dialog, width = dialog.width(), height=dialog.height())
- dialog.closed.connect(lambda: self.apply_user_settings(ignore_minimize=True)
- )
+ gremlin.util.centerDialog(dialog, width=dialog.width(), height=dialog.height())
+ dialog.closed.connect(lambda: self.apply_user_settings(ignore_minimize=True))
dialog.closed.connect(lambda: self._remove_modal_window("options"))
-
+
dialog.closed.connect(self.options_closed)
dialog.show()
@@ -880,14 +892,13 @@ def options_closed(self):
el = gremlin.event_handler.EventListener()
el.options_changed.emit()
-
def profile_creator(self):
"""Opens the UI used to create a profile from an existing one."""
fname, _ = QtWidgets.QFileDialog.getOpenFileName(
None,
"Profile to load as template",
gremlin.util.userprofile_path(),
- "XML files (*.xml)"
+ "XML files (*.xml)",
)
if fname == "":
return
@@ -895,8 +906,9 @@ def profile_creator(self):
profile_data = gremlin.base_profile.Profile()
profile_data.from_xml(fname)
- self.modal_windows["profile_creator"] = \
+ self.modal_windows["profile_creator"] = (
gremlin.ui.profile_creator.ProfileCreator(profile_data)
+ )
self.modal_windows["profile_creator"].show()
gremlin.shared_state.push_suspend_highlighting()
self.modal_windows["profile_creator"].closed.connect(
@@ -908,14 +920,15 @@ def profile_creator(self):
def swap_devices(self):
"""Opens the UI used to swap devices."""
- self.modal_windows["swap_devices"] = \
- gremlin.ui.dialogs.SwapDevicesUi(self.profile)
+ self.modal_windows["swap_devices"] = gremlin.ui.dialogs.SwapDevicesUi(
+ self.profile
+ )
geom = self.geometry()
self.modal_windows["swap_devices"].setGeometry(
int(geom.x() + geom.width() / 2 - 150),
int(geom.y() + geom.height() / 2 - 75),
300,
- 150
+ 150,
)
self.modal_windows["swap_devices"].show()
self.modal_windows["swap_devices"].closed.connect(
@@ -923,7 +936,6 @@ def swap_devices(self):
)
self.modal_windows["swap_devices"].accepted.connect(self._create_tabs)
-
def _remove_modal_window(self, name):
"""Removes the modal window widget from the system.
@@ -942,34 +954,32 @@ def menu_activate(self, activate):
el.profile_stop_toolbar.emit()
self.activate(activate)
-
- def abort(self, message = None):
- ''' aborts profile execution on error '''
+ def abort(self, message=None):
+ """aborts profile execution on error"""
if gremlin.shared_state.aborted:
return
-
- gremlin.shared_state.aborted = True # mark aborting globally
+
+ gremlin.shared_state.aborted = True # mark aborting globally
el = gremlin.event_handler.EventListener()
el.abort.emit()
self.ui.actionActivate.setChecked(False)
self.activate(False)
if message:
- gremlin.ui.ui_common.MessageBox(prompt = message)
-
+ gremlin.ui.ui_common.MessageBox(prompt=message)
def setUiMode(self):
- ''' enables or disables the UI based on the runtime mode and options
-
- this can lock the UI while a profile is running to prevent inadvertent changes
+ """enables or disables the UI based on the runtime mode and options
- '''
+ this can lock the UI while a profile is running to prevent inadvertent changes
+
+ """
enabled = True
if gremlin.shared_state.is_running:
enabled = self.config.runtime_ui_update
self.push_highlighting()
else:
self.pop_highlighting(True)
-
+
self.ui.tab_bar_widget.setEnabled(enabled)
self.ui.tab_content_widget.setEnabled(enabled)
self.ui.menuTools.setEnabled(enabled)
@@ -992,12 +1002,6 @@ def setUiMode(self):
self.ui.actionImportProfile.setEnabled(enabled)
self.ui.actionLoadProfile.setEnabled(enabled)
-
-
-
-
-
-
def activate(self, activate):
"""Activates and deactivates the code runner.
@@ -1011,64 +1015,58 @@ def activate(self, activate):
import gremlin.shared_state
if self.activate_locked:
- #syslog.info("Activate: re-entry")
+ # syslog.info("Activate: re-entry")
return
-
el = gremlin.event_handler.EventListener()
try:
-
self.abort_received = False
self.abort_reason = None
- #syslog.info("Activate: start")
+ # syslog.info("Activate: start")
self.activate_locked = True
is_running = gremlin.shared_state.is_running
- gremlin.shared_state.profile_state = True # assume all ok
-
-
+ gremlin.shared_state.profile_state = True # assume all ok
from gremlin.config import Configuration
+
config = Configuration()
verbose = config.verbose
- verbose_mode_exec = config.verbose_mode_exec
if activate:
# Generate the code for the profile and run it
- if verbose: syslog.info(f"Activate: activate profile")
+ if verbose:
+ syslog.info("Activate: activate profile")
self._profile_auto_activated = False
- #ec = gremlin.execution_graph.ExecutionContext()
- #ec.reset()
- gremlin.shared_state.aborted = False # reset abort flag
-
+ # ec = gremlin.execution_graph.ExecutionContext()
+ # ec.reset()
+ gremlin.shared_state.aborted = False # reset abort flag
# start the profile with the specified runtime mode
self.runner.start(
self.profile.build_inheritance_tree(),
self.profile.settings,
self._last_runtime_mode(),
- self.profile
+ self.profile,
)
-
-
if gremlin.shared_state.profile_state:
- #print ("set icon ACTIVE")
+ # print ("set icon ACTIVE")
self.ui.tray_icon.setIcon(load_icon("gfx/icon_active.ico"))
with QtCore.QSignalBlocker(self.ui.actionActivate):
- self.ui.actionActivate.setChecked(True) # toolbar icon "on"
+ self.ui.actionActivate.setChecked(True) # toolbar icon "on"
return
-
if not gremlin.shared_state.profile_state or is_running:
# Stop running the code
# running - save the last running mode to the executing profile
- if verbose: syslog.info(f"Deactivate profile requested")
+ if verbose:
+ syslog.info("Deactivate profile requested")
self.profile.set_last_runtime_mode(gremlin.shared_state.runtime_mode)
-
+
# stop listen
el.stop()
@@ -1088,27 +1086,27 @@ def activate(self, activate):
if widget:
tab_type = widget.data[0]
if tab_type in (
- TabDeviceType.Joystick,
- TabDeviceType.Keyboard,
- TabDeviceType.Osc,
- TabDeviceType.Midi):
+ TabDeviceType.Joystick,
+ TabDeviceType.Keyboard,
+ TabDeviceType.Osc,
+ TabDeviceType.Midi,
+ ):
widget.refresh()
# toolbar icon
with QtCore.QSignalBlocker(self.ui.actionActivate):
- self.ui.actionActivate.setChecked(False) # toolbar icon "off"
+ self.ui.actionActivate.setChecked(False) # toolbar icon "off"
try:
if self.ui.tray_icon is not None:
self.ui.tray_icon.setIcon(load_icon("gfx/icon.ico"))
- except:
- pass
+ except Exception as err:
+ syslog.error(f"Activate: error: {err}\n{traceback.format_exc()}")
except Exception as err:
syslog.error(f"Activate: error: {err}\n{traceback.format_exc()}")
finally:
-
- #syslog.info("Activate: completed")
+ # syslog.info("Activate: completed")
self.activate_locked = False
self.setUiMode()
@@ -1134,7 +1132,7 @@ def input_viewer(self):
int(geom.x() + geom.width() / 2 - 350),
int(geom.y() + geom.height() / 2 - 150),
700,
- 300
+ 300,
)
gremlin.shared_state.push_suspend_highlighting()
self.modal_windows["input_viewer"].show()
@@ -1144,44 +1142,38 @@ def _close_input_viewer(self):
gremlin.shared_state.pop_suspend_highlighting()
self._remove_modal_window("input_viewer")
-
- def load_profile(self, fname = None):
+ def load_profile(self, fname=None):
"""Prompts the user to select a profile file to load."""
if not self._save_changes_request():
return
if not fname:
-
fname, _ = QtWidgets.QFileDialog.getOpenFileName(
None,
"Load Profile",
gremlin.util.userprofile_path(),
- "XML files (*.xml)"
+ "XML files (*.xml)",
)
if os.path.isfile(fname):
self._load_recent_profile(fname)
def import_profile(self):
- ''' import a profile '''
+ """import a profile"""
gremlin.import_profile.import_profile()
-
-
def new_profile(self):
"""Creates a new empty profile."""
# Disable Gremlin if active before opening a new profile
pushCursor()
-
-
self.ui.actionActivate.setChecked(False)
self.activate(False)
if not self._save_changes_request():
return
-
+
self.clearWidgets()
gremlin.shared_state.resetState()
@@ -1189,9 +1181,9 @@ def new_profile(self):
eh.reset()
el = gremlin.event_handler.EventListener()
- el.profile_unloaded.emit() # tell the UI we're about to load a new profile
+ el.profile_unloaded.emit() # tell the UI we're about to load a new profile
- new_profile = gremlin.base_profile.Profile()
+ new_profile = gremlin.base_profile.Profile()
self.profile = new_profile
# default active mode
@@ -1203,29 +1195,25 @@ def new_profile(self):
for device in gremlin.joystick_handling.physical_devices():
self.profile.initialize_joystick_device(device, ["Default"])
-
# non regular devices
self.profile.initialize_regular_devices()
# Update profile information
self._update_window_title()
-
# Create device tabs
self._create_tabs()
# reset modes
current_mode = gremlin.shared_state.current_mode
- self.mode_selector.populate_selector(new_profile, current_mode, emit = False)
+ self.mode_selector.populate_selector(new_profile, current_mode, emit=False)
# Create a default mode
for device in self.profile.devices.values():
device.ensure_mode_exists("Default")
# Update everything to the new mode
- #self._mode_configuration_changed()
-
-
+ # self._mode_configuration_changed()
popCursor()
@@ -1242,15 +1230,10 @@ def save_profile(self):
else:
self.save_profile_as()
-
-
def save_profile_as(self):
"""Prompts the user for a file to save to profile to."""
fname, _ = QtWidgets.QFileDialog.getSaveFileName(
- None,
- "Save Profile",
- gremlin.util.userprofile_path(),
- "XML files (*.xml)"
+ None, "Save Profile", gremlin.util.userprofile_path(), "XML files (*.xml)"
)
if fname != "":
self.profile.setProfileFile(fname)
@@ -1262,7 +1245,7 @@ def save_profile_as(self):
self._update_window_title()
def reveal_profile(self):
- ''' opens the profile in explorer '''
+ """opens the profile in explorer"""
profile_fname = self.profile.profile_file
if profile_fname and os.path.isfile(profile_fname):
path = os.path.dirname(profile_fname)
@@ -1270,28 +1253,26 @@ def reveal_profile(self):
webbrowser.open(path)
def reveal_logfile(self):
- ''' opens the logfile in the current text editor '''
+ """opens the logfile in the current text editor"""
logfile = os.path.join(gremlin.util.userprofile_path(), "system.log")
if os.path.isfile(logfile):
webbrowser.open(logfile)
def open_profile_xml(self):
- ''' views the profile as an xml in the default text editor '''
+ """views the profile as an xml in the default text editor"""
profile_fname = self.profile.profile_file
if profile_fname:
# save first
self.profile.to_xml(profile_fname)
- if os.path.isfile(profile_fname):
+ if os.path.isfile(profile_fname):
path = os.path.realpath(profile_fname)
webbrowser.open(path)
-
def open_gremlinex_folder(self):
- ''' opens the gremlin EX folder '''
+ """opens the gremlin EX folder"""
path = userprofile_path()
webbrowser.open(path)
-
# +---------------------------------------------------------------
# | Create UI elements
# +---------------------------------------------------------------
@@ -1320,7 +1301,7 @@ def _connect_actions(self):
self.ui.actionDeviceInformation.triggered.connect(self.device_information)
self.ui.actionManageModes.triggered.connect(self.manage_modes)
self.ui.actionInputRepeater.triggered.connect(self.input_repeater)
- #self.ui.actionCalibration.triggered.connect(self.calibration)
+ # self.ui.actionCalibration.triggered.connect(self.calibration)
self.ui.actionInputViewer.triggered.connect(self.input_viewer)
self.ui.actionPDFCheatsheet.triggered.connect(lambda: self._create_cheatsheet())
self.ui.actionViewInput.triggered.connect(lambda: self._view_input_map())
@@ -1341,25 +1322,26 @@ def _connect_actions(self):
# Simconnect configuration
self.ui.actionSimconnectOptions.triggered.connect(self.showSimconnectOptions)
-
def showSimconnectOptions(self):
- ''' displays the simconnect options dialog '''
+ """displays the simconnect options dialog"""
from action_plugins.map_to_simconnect import SimconnectOptionsUi
from action_plugins.map_to_simconnect.SimConnectManager import SimConnectManager
+
profile = gremlin.shared_state.current_profile
profile_file = profile.profile_file
if not profile_file or not os.path.isfile(profile_file):
- gremlin.ui.ui_common.MessageBox(prompt="Please save the current profile before accessing Simconnect options.")
- return
+ gremlin.ui.ui_common.MessageBox(
+ prompt="Please save the current profile before accessing Simconnect options."
+ )
+ return
dialog = SimconnectOptionsUi(SimConnectManager().simconnect)
dialog.exec()
def _create_1to1_mapping(self):
- ''' maps one to one '''
+ """maps one to one"""
mapper = gremlin.import_profile.Mapper()
mapper.create_1to1_mapping()
-
def _create_recent_profiles(self):
"""Populates the Recent submenu entry with the most recent profiles."""
self.ui.menuRecent.clear()
@@ -1378,38 +1360,52 @@ def _create_statusbar(self):
self.status_bar_highlight_tabswitch_widget = QtWidgets.QPushButton()
self.status_bar_highlight_tabswitch_widget.setStyleSheet("border: none")
- self.status_bar_highlight_tabswitch_widget.clicked.connect(self._toggle_tabswitch_highlight)
- self.status_bar_highlight_tabswitch_widget.setToolTip("Enable auto-tab switch on device input")
-
+ self.status_bar_highlight_tabswitch_widget.clicked.connect(
+ self._toggle_tabswitch_highlight
+ )
+ self.status_bar_highlight_tabswitch_widget.setToolTip(
+ "Enable auto-tab switch on device input"
+ )
self.status_bar_highlight_axis_widget = QtWidgets.QPushButton()
self.status_bar_highlight_axis_widget.setStyleSheet("border: none")
- self.status_bar_highlight_axis_widget.clicked.connect(self._toggle_axis_highlight)
- self.status_bar_highlight_axis_widget.setToolTip("Enable axis input highlighting")
+ self.status_bar_highlight_axis_widget.clicked.connect(
+ self._toggle_axis_highlight
+ )
+ self.status_bar_highlight_axis_widget.setToolTip(
+ "Enable axis input highlighting"
+ )
self.status_bar_highlight_button_widget = QtWidgets.QPushButton()
self.status_bar_highlight_button_widget.setStyleSheet("border: none")
- self.status_bar_highlight_button_widget.clicked.connect(self._toggle_button_highlight)
- self.status_bar_highlight_button_widget.setToolTip("Enable button input highlighting")
-
+ self.status_bar_highlight_button_widget.clicked.connect(
+ self._toggle_button_highlight
+ )
+ self.status_bar_highlight_button_widget.setToolTip(
+ "Enable button input highlighting"
+ )
self.status_bar_highlight_enable_widget = QtWidgets.QPushButton()
self.status_bar_highlight_enable_widget.setStyleSheet("border: none")
- self.status_bar_highlight_enable_widget.setChecked(self.config.highlight_enabled)
- self.status_bar_highlight_enable_widget.clicked.connect(self._toggle_highlight_enabled)
+ self.status_bar_highlight_enable_widget.setChecked(
+ self.config.highlight_enabled
+ )
+ self.status_bar_highlight_enable_widget.clicked.connect(
+ self._toggle_highlight_enabled
+ )
self.status_bar_highlight_enable_widget.setToolTip("Enable highlighting")
-
self.status_bar_module_container_widget = QtWidgets.QWidget()
- self.status_bar_module_container_widget.setContentsMargins(0,0,0,0)
- self.status_bar_module_container_layout = QtWidgets.QHBoxLayout(self.status_bar_module_container_widget)
- self.status_bar_module_container_layout.setContentsMargins(0,0,0,0)
+ self.status_bar_module_container_widget.setContentsMargins(0, 0, 0, 0)
+ self.status_bar_module_container_layout = QtWidgets.QHBoxLayout(
+ self.status_bar_module_container_widget
+ )
+ self.status_bar_module_container_layout.setContentsMargins(0, 0, 0, 0)
self._status_bar_module_states = {}
el = gremlin.event_handler.EventListener()
el.module_state_change.connect(self._module_state_changed)
el.module_state_register.connect(self.registerStatusModule)
-
self.ui.statusbar_layout.addWidget(self.status_bar_is_active_widget)
self.ui.statusbar_layout.addWidget(self.status_bar_repeater_widget)
@@ -1418,67 +1414,97 @@ def _create_statusbar(self):
self.ui.statusbar_layout.addWidget(self.status_bar_module_container_widget)
self.ui_statusbar_highlight_container_widget = QtWidgets.QWidget()
- self.ui_statusbar_highlight_container_widget.setContentsMargins(0,0,0,0)
- self.ui_statusbar_highlight_container_layout = QtWidgets.QHBoxLayout(self.ui_statusbar_highlight_container_widget)
- self.ui_statusbar_highlight_container_layout.setContentsMargins(0,0,0,0)
+ self.ui_statusbar_highlight_container_widget.setContentsMargins(0, 0, 0, 0)
+ self.ui_statusbar_highlight_container_layout = QtWidgets.QHBoxLayout(
+ self.ui_statusbar_highlight_container_widget
+ )
+ self.ui_statusbar_highlight_container_layout.setContentsMargins(0, 0, 0, 0)
self.ui_statusbar_highlight_state_container_widget = QtWidgets.QWidget()
- self.ui_statusbar_highlight_state_container_widget.setContentsMargins(0,0,0,0)
- self.ui_statusbar_highlight_state_container_layout = QtWidgets.QHBoxLayout(self.ui_statusbar_highlight_state_container_widget)
- self.ui_statusbar_highlight_state_container_layout.setContentsMargins(0,0,0,0)
-
+ self.ui_statusbar_highlight_state_container_widget.setContentsMargins(
+ 0, 0, 0, 0
+ )
+ self.ui_statusbar_highlight_state_container_layout = QtWidgets.QHBoxLayout(
+ self.ui_statusbar_highlight_state_container_widget
+ )
+ self.ui_statusbar_highlight_state_container_layout.setContentsMargins(
+ 0, 0, 0, 0
+ )
-
- self.ui_statusbar_highlight_state_container_layout.addWidget(QtWidgets.QLabel("Device"))
- self.ui_statusbar_highlight_state_container_layout.addWidget(self.status_bar_highlight_tabswitch_widget)
- self.ui_statusbar_highlight_state_container_layout.addWidget(QtWidgets.QLabel("Axis"))
- self.ui_statusbar_highlight_state_container_layout.addWidget(self.status_bar_highlight_axis_widget)
- self.ui_statusbar_highlight_state_container_layout.addWidget(QtWidgets.QLabel("Button"))
- self.ui_statusbar_highlight_state_container_layout.addWidget(self.status_bar_highlight_button_widget)
+ self.ui_statusbar_highlight_state_container_layout.addWidget(
+ QtWidgets.QLabel("Device")
+ )
+ self.ui_statusbar_highlight_state_container_layout.addWidget(
+ self.status_bar_highlight_tabswitch_widget
+ )
+ self.ui_statusbar_highlight_state_container_layout.addWidget(
+ QtWidgets.QLabel("Axis")
+ )
+ self.ui_statusbar_highlight_state_container_layout.addWidget(
+ self.status_bar_highlight_axis_widget
+ )
+ self.ui_statusbar_highlight_state_container_layout.addWidget(
+ QtWidgets.QLabel("Button")
+ )
+ self.ui_statusbar_highlight_state_container_layout.addWidget(
+ self.status_bar_highlight_button_widget
+ )
self.ui_statusbar_highlight_container_layout.addStretch()
- self.ui_statusbar_highlight_container_layout.addWidget(QtWidgets.QLabel("Highlight"))
- self.ui_statusbar_highlight_container_layout.addWidget(self.ui_statusbar_highlight_state_container_widget)
- self.ui_statusbar_highlight_container_layout.addWidget(QtWidgets.QLabel("Enabled"))
- self.ui_statusbar_highlight_container_layout.addWidget(self.status_bar_highlight_enable_widget)
+ self.ui_statusbar_highlight_container_layout.addWidget(
+ QtWidgets.QLabel("Highlight")
+ )
+ self.ui_statusbar_highlight_container_layout.addWidget(
+ self.ui_statusbar_highlight_state_container_widget
+ )
+ self.ui_statusbar_highlight_container_layout.addWidget(
+ QtWidgets.QLabel("Enabled")
+ )
+ self.ui_statusbar_highlight_container_layout.addWidget(
+ self.status_bar_highlight_enable_widget
+ )
self.ui.statusbar_layout.addStretch()
self.ui.statusbar_layout.addWidget(self.ui_statusbar_highlight_container_widget)
-
-
-
- icon_size = QtCore.QSize(16,16)
- icon = gremlin.util.load_icon("mdi.checkbox-blank-circle", use_qta=True,qta_color= gremlin.ui.ui_common.Color.recordColor())
+ icon_size = QtCore.QSize(16, 16)
+ icon = gremlin.util.load_icon(
+ "mdi.checkbox-blank-circle",
+ use_qta=True,
+ qta_color=gremlin.ui.ui_common.Color.recordColor(),
+ )
self._icon_red = icon
self._status_red = icon.pixmap(icon_size)
- icon = gremlin.util.load_icon("mdi.checkbox-blank-circle", use_qta=True,qta_color=gremlin.ui.ui_common.Color.activeColor())
+ icon = gremlin.util.load_icon(
+ "mdi.checkbox-blank-circle",
+ use_qta=True,
+ qta_color=gremlin.ui.ui_common.Color.activeColor(),
+ )
self._icon_green = icon
self._status_green = icon.pixmap(icon_size)
- icon = gremlin.util.load_icon("mdi.checkbox-blank-circle", use_qta=True,qta_color=gremlin.ui.ui_common.Color.inactiveColor())
+ icon = gremlin.util.load_icon(
+ "mdi.checkbox-blank-circle",
+ use_qta=True,
+ qta_color=gremlin.ui.ui_common.Color.inactiveColor(),
+ )
self._icon_gray = icon
self._status_gray = icon.pixmap(icon_size)
-
self._update_highlight_toolbar_enabled()
-
-
def _update_highlight_toolbar_enabled(self):
- ''' updates the enabled status of the highlight status bar buttons based on current enabled state '''
+ """updates the enabled status of the highlight status bar buttons based on current enabled state"""
enabled = self.config.highlight_enabled
icon = self._icon_green if enabled else self._icon_gray
self.status_bar_highlight_enable_widget.setIcon(icon)
-
-
@QtCore.Slot()
def _profile_start(self):
self.setUiMode()
self._update_status_bar_active(True)
self._update_status_bar_modules_ui()
self.ui_statusbar_highlight_state_container_widget.setEnabled(False)
-
+
@QtCore.Slot()
def _profile_stop(self):
self.setUiMode()
@@ -1487,16 +1513,15 @@ def _profile_stop(self):
self.ui_statusbar_highlight_state_container_widget.setEnabled(True)
@QtCore.Slot(str, str, object)
- def registerStatusModule(self, key, label : str, state : object, callback):
- ''' registers a module with a state '''
- if not key in self._status_bar_module_states:
+ def registerStatusModule(self, key, label: str, state: object, callback):
+ """registers a module with a state"""
+ if key not in self._status_bar_module_states:
self._status_bar_module_states[key] = (label, state, callback)
self._update_status_bar_modules_ui()
-
@QtCore.Slot(str, object)
- def _module_state_changed(self, key, state : object):
+ def _module_state_changed(self, key, state: object):
# syslog = logging.getLogger("system")
syslog.info(f"module state: {key} state: {state}")
if key in self._status_bar_module_states:
@@ -1506,7 +1531,7 @@ def _module_state_changed(self, key, state : object):
self._update_status_bar_modules_ui()
def _update_status_bar_modules_ui(self):
- ''' recreates the module status bar UI based on current status'''
+ """recreates the module status bar UI based on current status"""
gremlin.ui.ui_common.clear_layout(self.status_bar_module_container_layout)
for label, state, callback in self._status_bar_module_states.values():
if state is None:
@@ -1514,7 +1539,6 @@ def _update_status_bar_modules_ui(self):
else:
pixmap = self._status_green if state else self._status_red
-
widget = QtWidgets.QLabel()
widget.setPixmap(pixmap)
@@ -1531,21 +1555,18 @@ def _update_status_bar_modules_ui(self):
self._update_highlight_toolbar_enabled()
-
@QtCore.Slot()
def _toggle_tabswitch_highlight(self):
eh = gremlin.event_handler.EventListener()
status = self.config.highlight_autoswitch
eh.toggle_highlight.emit(not status, None, None)
-
-
+
@QtCore.Slot()
def _toggle_axis_highlight(self):
eh = gremlin.event_handler.EventListener()
status = self.config.highlight_input_axis
eh.toggle_highlight.emit(None, not status, None)
-
@QtCore.Slot()
def _toggle_button_highlight(self, checked):
eh = gremlin.event_handler.EventListener()
@@ -1558,27 +1579,18 @@ def _toggle_highlight_enabled(self, checked):
el = gremlin.event_handler.EventListener()
el.enable_highlight_changed.emit(checked)
-
-
@QtCore.Slot(bool)
- def _highlight_enable_changed(self, enabled : bool):
+ def _highlight_enable_changed(self, enabled: bool):
self._update_highlight_toolbar_enabled()
if enabled:
# reset the highlight stack
gremlin.shared_state.pop_suspend_highlighting(True)
-
-
-
-
-
def _create_system_tray(self):
"""Creates the system tray icon and menu."""
self.ui.tray_menu = QtWidgets.QMenu("Menu")
- self.ui.action_tray_show = \
- QtGui.QAction("Show / Hide", self)
- self.ui.action_tray_enable = \
- QtGui.QAction("Start/Stop profile", self)
+ self.ui.action_tray_show = QtGui.QAction("Show / Hide", self)
+ self.ui.action_tray_enable = QtGui.QAction("Start/Stop profile", self)
self.ui.action_tray_quit = QtGui.QAction("Quit", self)
self.ui.tray_menu.addAction(self.ui.action_tray_show)
self.ui.tray_menu.addAction(self.ui.action_tray_enable)
@@ -1587,39 +1599,34 @@ def _create_system_tray(self):
self.ui.action_tray_show.triggered.connect(
lambda: self.setHidden(not self.isHidden())
)
- self.ui.action_tray_enable.triggered.connect(
- self.ui.actionActivate.trigger
- )
- self.ui.action_tray_quit.triggered.connect(
- self._force_close
- )
+ self.ui.action_tray_enable.triggered.connect(self.ui.actionActivate.trigger)
+ self.ui.action_tray_quit.triggered.connect(self._force_close)
self.ui.tray_icon = QtWidgets.QSystemTrayIcon()
self.ui.tray_icon.setIcon(load_icon("gfx/icon.ico"))
self.ui.tray_icon.setContextMenu(self.ui.tray_menu)
self.ui.tray_icon.show()
-
- def registerWidget(self, device_guid, widget, hide = True):
- ''' registers widget for cleanup - this is needed because QT doesn't tell us when widgets are discarded so we need to manually track this here
- so widgets cleanup correctly and remove any hooks / references '''
+ def registerWidget(self, device_guid, widget, hide=True):
+ """registers widget for cleanup - this is needed because QT doesn't tell us when widgets are discarded so we need to manually track this here
+ so widgets cleanup correctly and remove any hooks / references"""
device_name = gremlin.shared_state.get_device_name(device_guid)
if not isinstance(device_guid, str):
device_guid = str(device_guid)
if device_guid in self._widget_cache:
- syslog.error(f"TAB: widget already exists for tab: {device_guid} {device_name}")
+ syslog.error(
+ f"TAB: widget already exists for tab: {device_guid} {device_name}"
+ )
return
self._widget_cache[device_guid] = widget
self.ui.tab_content_layout.addWidget(widget)
-
+
if hide:
widget.setVisible(False)
-
-
def unregisterWidget(self, device_guid):
- ''' removes a widget from the cleanup list'''
+ """removes a widget from the cleanup list"""
if not isinstance(device_guid, str):
device_guid = str(device_guid)
if device_guid in self._widget_cache:
@@ -1631,9 +1638,8 @@ def unregisterWidget(self, device_guid):
if device_guid in gremlin.shared_state.device_widget_map:
del gremlin.shared_state.device_widget_map[device_guid]
-
def unregisterAllWidgets(self):
- ''' clears the widgets '''
+ """clears the widgets"""
for widget in self._widget_cache.values():
# cleanup widgets
if hasattr(widget, "_cleanup_ui"):
@@ -1641,11 +1647,10 @@ def unregisterAllWidgets(self):
widget.setParent(None)
self._widget_cache.clear()
gremlin.shared_state.device_widget_map = {}
-
def clearWidgets(self):
- ''' clears the device cache'''
- self._current_tab_widget = None # remove reference to tab widget
+ """clears the device cache"""
+ self._current_tab_widget = None # remove reference to tab widget
gremlin.util.clear_layout(self.ui.tab_content_layout)
tracker = gremlin.ui.ui_common.WidgetTracker()
tracker.clearRegisteredWidgets()
@@ -1654,8 +1659,8 @@ def clearWidgets(self):
self.unregisterAllWidgets()
verbose = gremlin.config.Configuration().verbose_mode_ui
gc.collect()
- if verbose: syslog.info("TABS TRACKER: clear()")
-
+ if verbose:
+ syslog.info("TABS TRACKER: clear()")
def getTabIndexForDevice(self, device_guid):
if not isinstance(device_guid, str):
@@ -1663,21 +1668,20 @@ def getTabIndexForDevice(self, device_guid):
if device_guid in self._tab_device_map:
return self._tab_device_map[device_guid]
return None
-
+
def getFirstTabDeviceGuid(self):
- ''' gets the device for the first tab '''
+ """gets the device for the first tab"""
for device_guid in self._tab_device_map.keys():
return device_guid
return None
def getDeviceGuidForTabIndex(self, index):
- ''' gets the device GUID for a given tab index '''
+ """gets the device GUID for a given tab index"""
if index in self._tab_index_map:
return self._tab_index_map[index]
-
def swapTab(self, index, other):
- ''' swaps two values in the map '''
+ """swaps two values in the map"""
if index in self._tab_index_map and other in self._tab_index_map:
d1 = self._tab_index_map[index]
d2 = self._tab_index_map[other]
@@ -1686,22 +1690,18 @@ def swapTab(self, index, other):
self._tab_device_map[d1] = other
self._tab_device_map[d2] = index
-
def clearTabIndex(self):
- self._tab_index_map.clear()
-
+ self._tab_index_map.clear()
def getWidgetByTabIndex(self, index):
- ''' gets the device widget by the tab index'''
+ """gets the device widget by the tab index"""
if index in self._tab_index_map:
- device_guid = self._tab_index_map[index]
+ device_guid = self._tab_index_map[index]
return self.getWidget(device_guid)
return None
-
-
def clearRegisteredWidgets(self):
- ''' cleanup all widgets '''
+ """cleanup all widgets"""
for widget in self._widget_cache.values():
self.ui.tab_content_layout.removeWidget(widget)
if hasattr(widget, "_cleanup_ui"):
@@ -1709,58 +1709,55 @@ def clearRegisteredWidgets(self):
self._widget_cache.clear()
def getWidget(self, device_guid):
- ''' gets the content widget for the given device '''
+ """gets the content widget for the given device"""
if not isinstance(device_guid, str):
device_guid = str(device_guid)
if device_guid in self._widget_cache:
return self._widget_cache[device_guid]
return None
-
-
def hideTabWidgets(self):
- ''' hides all tab widgets '''
+ """hides all tab widgets"""
for widget in self._widget_cache.values():
widget.setVisible(False)
-
- def selectTabWidgetByIndex(self, index : int):
- ''' shows the page (content widget) for the specified tab index '''
+ def selectTabWidgetByIndex(self, index: int):
+ """shows the page (content widget) for the specified tab index"""
if index in self._tab_index_map:
device_guid = self._tab_index_map[index]
self.selectTabWidget(device_guid)
-
def selectTabWidget(self, device_guid):
- ''' shows the page (content widget) for the specified device'''
+ """shows the page (content widget) for the specified device"""
if not isinstance(device_guid, str):
device_guid = str(device_guid)
el = gremlin.event_handler.EventListener()
verbose = gremlin.config.Configuration().verbose_mode_detailed
-
+
current_device_guid = self._current_tab_device_guid
if current_device_guid:
if self._current_tab_device_guid == device_guid:
# already shown
- return
-
+ return
+
device_name = gremlin.shared_state.get_device_name(current_device_guid)
- if verbose: syslog.info(f"TAB UNSELECT: {device_name}")
+ if verbose:
+ syslog.info(f"TAB UNSELECT: {device_name}")
el.tab_unselected.emit(current_device_guid)
-
- # select the tab index
+
+ # select the tab index
index = self.getTabIndexForDevice(device_guid)
if index and self.ui.devices.currentIndex() != index:
with QtCore.QSignalBlocker(self.ui.devices):
self.ui.devices.setCurrentIndex(index)
-
+
if device_guid in self._widget_cache:
widget = self._widget_cache[device_guid]
if self._current_tab_widget:
# hide the old widget
self._current_tab_widget.setVisible(False)
-
+
# show the new widget
widget.setVisible(True)
self._current_tab_widget = widget
@@ -1771,29 +1768,28 @@ def selectTabWidget(self, device_guid):
device_name = gremlin.shared_state.get_device_name(device_guid)
verbose = gremlin.config.Configuration().verbose_mode_detailed
- if verbose: syslog.info(f"TAB SELECT: {device_name}")
+ if verbose:
+ syslog.info(f"TAB SELECT: {device_name}")
el.tab_selected.emit(device_guid)
def getActiveTabWidget(self) -> gremlin.ui.ui_common.QSplitTabWidget:
- ''' gets the current tab widget '''
+ """gets the current tab widget"""
return self._current_tab_widget
-
+
def getActiveTabIndex(self) -> int:
- ''' gets the current tab index '''
+ """gets the current tab index"""
return self.ui.devices.currentIndex()
def getActiveTabType(self) -> TabDeviceType:
index = self.ui.devices.currentIndex()
- data : TabData = self.ui.devices.tabData(index)
- return TabDeviceType(data.tab_type)
-
+ data: TabData = self.ui.devices.tabData(index)
+ return TabDeviceType(data.tab_type)
def _create_tabs(self, activate_tab=None):
"""Creates the tabs of the configuration dialog representing
the different connected devices.
"""
try:
-
device_guid = None
midi_enabled = self.config.midi_enabled
@@ -1801,8 +1797,8 @@ def _create_tabs(self, activate_tab=None):
self.push_highlighting()
el = gremlin.event_handler.EventListener()
- gremlin.shared_state.push_input_selection() # prevent selections
-
+ gremlin.shared_state.push_input_selection() # prevent selections
+
self._reset_tab_data()
self.clearWidgets()
@@ -1813,10 +1809,7 @@ def _create_tabs(self, activate_tab=None):
verbose_detailed = config.verbose_mode_detailed
verbose = config.verbose_mode_device
-
gremlin.shared_state.is_tab_loading = True
-
-
# clear the widget map as it's recreated here
gremlin.shared_state.device_widget_map.clear()
@@ -1829,24 +1822,23 @@ def _create_tabs(self, activate_tab=None):
self._active_devices = gremlin.joystick_handling.joystick_devices()
- # index of the current tab being addded
+ # index of the current tab being addded
index = 0
-
+
# =======================================================
# Create physical joystick device tabs
for device in sorted(phys_devices, key=lambda x: x.name):
device_profile = self.profile.get_device_modes(
- device.device_guid,
- DeviceType.Joystick,
- device.name
+ device.device_guid, DeviceType.Joystick, device.name
)
# this needs to be registered before widgets are created because widgets may need this data
- gremlin.shared_state.device_profile_map[device.device_guid] = device_profile
- gremlin.shared_state.device_type_map[device.device_guid] = DeviceType.Joystick
- tab_label = device.name.strip()
-
-
+ gremlin.shared_state.device_profile_map[device.device_guid] = (
+ device_profile
+ )
+ gremlin.shared_state.device_type_map[device.device_guid] = (
+ DeviceType.Joystick
+ )
device_guid = str(device.device_guid)
device_name = gremlin.shared_state.get_device_name(device_guid)
if device_name:
@@ -1858,19 +1850,17 @@ def _create_tabs(self, activate_tab=None):
self.current_mode,
)
-
self.registerWidget(device_guid, widget)
self._add_tab(device_guid, TabDeviceType.Joystick)
-
+
widget.data = (TabDeviceType.Joystick, device_guid, index)
-
-
-
+
index += 1
-
- gremlin.shared_state.device_widget_map[device_profile.device_guid] = widget
+ gremlin.shared_state.device_widget_map[device_profile.device_guid] = (
+ widget
+ )
widget.inputChanged.connect(self._device_input_changed_cb)
@@ -1882,8 +1872,9 @@ def _create_tabs(self, activate_tab=None):
if input_item:
input_id = input_item.input_id
input_type = input_item.input_type
- el.input_selection_changed.emit(device_guid, input_type, input_id)
-
+ el.input_selection_changed.emit(
+ device_guid, input_type, input_id
+ )
# =======================================================
# add the VJOY input devices to the device tabs
@@ -1899,26 +1890,21 @@ def _create_tabs(self, activate_tab=None):
device_name = gremlin.shared_state.get_device_name(device_guid)
if device_name:
device_profile = self.profile.get_device_modes(
- device.device_guid,
- DeviceType.Joystick,
- device.name
+ device.device_guid, DeviceType.Joystick, device.name
)
device_guid = str(device.device_guid)
widget = self.getWidget(device_guid)
if not widget:
widget = gremlin.ui.joystick_device.JoystickDeviceTabWidget(
- device,
- device_profile,
- self.current_mode
+ device, device_profile, self.current_mode
)
-
self.registerWidget(device_guid, widget)
- gremlin.shared_state.device_widget_map[device.device_guid] = widget
-
+ gremlin.shared_state.device_widget_map[device.device_guid] = (
+ widget
+ )
-
widget.data = (TabDeviceType.VjoyInput, device_guid, index)
self._add_tab(device_guid, TabDeviceType.VjoyInput)
index += 1
@@ -1929,7 +1915,7 @@ def _create_tabs(self, activate_tab=None):
device_profile = self.profile.get_device_modes(
dinput.GUID_Keyboard,
DeviceType.Keyboard,
- DeviceType.to_string(DeviceType.Keyboard)
+ DeviceType.to_string(DeviceType.Keyboard),
)
device_guid = str(gremlin.shared_state.keyboard_tab_guid)
@@ -1942,21 +1928,20 @@ def _create_tabs(self, activate_tab=None):
widget = self.getWidget(device_guid)
if not widget:
widget = gremlin.ui.keyboard_device.KeyboardDeviceTabWidget(
- device_profile,
- self.current_mode
+ device_profile, self.current_mode
)
self.registerWidget(device_guid, widget)
- gremlin.shared_state.device_type_map[dinput.GUID_Keyboard] = DeviceType.Keyboard
+ gremlin.shared_state.device_type_map[dinput.GUID_Keyboard] = (
+ DeviceType.Keyboard
+ )
gremlin.shared_state.device_widget_map[dinput.GUID_Keyboard] = widget
-
widget.data = (TabDeviceType.Keyboard, device_guid, index)
self._keyboard_device_guid = device_guid
-
-
+
self._add_tab(device_guid, TabDeviceType.Keyboard)
- index+=1
-
+ index += 1
+
# =======================================================
# Create MIDI tab
device_guid = str(gremlin.shared_state.midi_tab_guid)
@@ -1971,28 +1956,29 @@ def _create_tabs(self, activate_tab=None):
device_profile = self.profile.get_device_modes(
gremlin.ui.midi_device.MidiDeviceTabWidget.device_guid,
DeviceType.Midi,
- DeviceType.to_string(DeviceType.Midi)
- )
+ DeviceType.to_string(DeviceType.Midi),
+ )
widget = self.getWidget(device_guid)
if not widget:
widget = gremlin.ui.midi_device.MidiDeviceTabWidget(
- device_profile,
- self.current_mode
+ device_profile, self.current_mode
)
-
self.registerWidget(device_guid, widget)
self._midi_device_guid = device_guid
-
- gremlin.shared_state.device_type_map[gremlin.ui.midi_device.MidiDeviceTabWidget.device_guid] = DeviceType.Midi
- gremlin.shared_state.device_widget_map[gremlin.ui.midi_device.MidiDeviceTabWidget.device_guid] = widget
-
+
+ gremlin.shared_state.device_type_map[
+ gremlin.ui.midi_device.MidiDeviceTabWidget.device_guid
+ ] = DeviceType.Midi
+ gremlin.shared_state.device_widget_map[
+ gremlin.ui.midi_device.MidiDeviceTabWidget.device_guid
+ ] = widget
+
widget.data = (TabDeviceType.Midi, device_guid, index)
-
- self._add_tab(device_guid,TabDeviceType.Midi)
- index+=1
+ self._add_tab(device_guid, TabDeviceType.Midi)
+ index += 1
# =======================================================
# Create OSC tab
@@ -2008,29 +1994,28 @@ def _create_tabs(self, activate_tab=None):
device_profile = self.profile.get_device_modes(
gremlin.ui.osc_device.OscDeviceTabWidget.device_guid,
DeviceType.Osc,
- DeviceType.to_string(DeviceType.Osc)
- )
+ DeviceType.to_string(DeviceType.Osc),
+ )
widget = self.getWidget(device_guid)
if not widget:
widget = gremlin.ui.osc_device.OscDeviceTabWidget(
- device_profile,
- self.current_mode
+ device_profile, self.current_mode
)
-
-
self.registerWidget(device_guid, widget)
self._osc_device_guid = device_guid
-
- gremlin.shared_state.device_type_map[gremlin.ui.osc_device.OscDeviceTabWidget.device_guid] = DeviceType.Osc
- gremlin.shared_state.device_widget_map[gremlin.ui.osc_device.OscDeviceTabWidget.device_guid] = widget
-
- self._add_tab(device_guid,TabDeviceType.Osc)
+ gremlin.shared_state.device_type_map[
+ gremlin.ui.osc_device.OscDeviceTabWidget.device_guid
+ ] = DeviceType.Osc
+ gremlin.shared_state.device_widget_map[
+ gremlin.ui.osc_device.OscDeviceTabWidget.device_guid
+ ] = widget
+
+ self._add_tab(device_guid, TabDeviceType.Osc)
widget.data = (TabDeviceType.Osc, device_guid, index)
-
+
index += 1
-
# =======================================================
# create mode control tab
@@ -2044,16 +2029,15 @@ def _create_tabs(self, activate_tab=None):
gremlin.joystick_handling.registerSpecialDevice(dev)
device_profile = self.profile.get_device_modes(
- guid,
- DeviceType.ModeControl,
- DeviceType.to_string(DeviceType.ModeControl)
- )
-
+ guid,
+ DeviceType.ModeControl,
+ DeviceType.to_string(DeviceType.ModeControl),
+ )
+
widget = self.getWidget(device_guid)
if not widget:
widget = gremlin.ui.mode_device.ModeDeviceTabWidget(
- device_profile,
- self.current_mode
+ device_profile, self.current_mode
)
self.registerWidget(device_guid, widget)
self._mode_device_guid = device_guid
@@ -2061,14 +2045,11 @@ def _create_tabs(self, activate_tab=None):
widget.data = (TabDeviceType.ModeControl, device_guid, index)
self._add_tab(device_guid, TabDeviceType.ModeControl)
-
-
# =======================================================
# show output VJOY devices
self._vjoy_output_device_guids = []
if self.config.show_output_vjoy:
-
# Create the vjoy as output device tab
for device in sorted(vjoy_devices, key=lambda x: x.vjoy_id):
# Ignore vJoy as input devices
@@ -2076,39 +2057,31 @@ def _create_tabs(self, activate_tab=None):
continue
device_profile = self.profile.get_device_modes(
- device.device_guid,
- DeviceType.VJoy,
- device.name
+ device.device_guid, DeviceType.VJoy, device.name
)
device_guid = str(device.device_guid)
widget = self.getWidget(device_guid)
if not widget:
widget = gremlin.ui.joystick_device.JoystickDeviceTabWidget(
- device,
- device_profile,
- self.current_mode
+ device, device_profile, self.current_mode
)
-
+
self.registerWidget(device_guid, widget)
-
- gremlin.shared_state.device_widget_map[device.device_guid] = widget
- self._vjoy_output_device_guids.append(device.device_guid)
+ gremlin.shared_state.device_widget_map[device.device_guid] = (
+ widget
+ )
+ self._vjoy_output_device_guids.append(device.device_guid)
widget.data = (TabDeviceType.VjoyOutput, device_guid, index)
-
+
index += 1
self.ui.tab_content_layout.addWidget(widget)
-
self._add_tab(device_guid, TabDeviceType.VjoyOutput)
-
-
-
-
# =======================================================
# Add profile configuration tab
@@ -2121,20 +2094,19 @@ def _create_tabs(self, activate_tab=None):
dev.is_special = True
gremlin.joystick_handling.registerSpecialDevice(dev)
if device_name:
-
widget = self.getWidget(device_guid)
if not widget:
- widget = gremlin.ui.profile_settings.ProfileSettingsWidget(self.profile.settings)
+ widget = gremlin.ui.profile_settings.ProfileSettingsWidget(
+ self.profile.settings
+ )
self.registerWidget(device_guid, widget)
widget.changed.connect(lambda: self._create_tabs())
-
- self._settings_device_guid = device_guid
+ self._settings_device_guid = device_guid
widget.data = (TabDeviceType.Settings, device_guid, index)
self._add_tab(device_guid, TabDeviceType.Settings)
-
-
+
# =======================================================
# Add a plugin custom modules tab
device_guid = str(gremlin.shared_state.plugins_tab_guid)
@@ -2147,18 +2119,19 @@ def _create_tabs(self, activate_tab=None):
gremlin.joystick_handling.registerSpecialDevice(dev)
widget = self.getWidget(device_guid)
if not widget:
- widget = gremlin.ui.user_plugin_management.ModuleManagementController(self.profile)
+ widget = gremlin.ui.user_plugin_management.ModuleManagementController(
+ self.profile
+ )
self.mm = widget
widget = self.mm.view
self.registerWidget(device_guid, widget)
-
+
self._plugins_device_guid = device_guid
-
+
widget.data = (TabDeviceType.Plugins, device_guid, index)
self._add_tab(device_guid, TabDeviceType.Plugins)
index += 1
-
# reorder the tabs based on user preferences if a tab order was previously saved
config_tab_map = self.config.tab_list
@@ -2177,12 +2150,21 @@ def _create_tabs(self, activate_tab=None):
if remove_index:
for index in remove_index:
del config_tab_map[index]
-
- current_tab_guids = [device_guid for device_guid, _, _, _ in current_tab_map.values()]
-
- config_tab_guids = [device_guid for device_guid, _, _, _ in config_tab_map.values()] if config_tab_map else []
- missing_tab_guids = [device_guid for device_guid in current_tab_guids if device_guid not in config_tab_guids]
+ current_tab_guids = [
+ device_guid for device_guid, _, _, _ in current_tab_map.values()
+ ]
+
+ config_tab_guids = (
+ [device_guid for device_guid, _, _, _ in config_tab_map.values()]
+ if config_tab_map
+ else []
+ )
+ missing_tab_guids = [
+ device_guid
+ for device_guid in current_tab_guids
+ if device_guid not in config_tab_guids
+ ]
missing_data = []
# remove MIDI tab if not enabled
@@ -2193,16 +2175,14 @@ def _create_tabs(self, activate_tab=None):
reordered_data = list(config_tab_map.values())
# list of (device_guid, device_name, data.tab_type, index)
-
+
for device_guid in missing_tab_guids:
index = self._get_tab_index(device_guid)
tab_type = self._get_tab_type(index)
missing_data.append((device_guid, tab_type))
reordered_data.append((device_guid, device_name, tab_type, index))
-
-
- reordered_data.sort(key = lambda x: x[3])
-
+
+ reordered_data.sort(key=lambda x: x[3])
if verbose_detailed:
syslog.info("UI: Current device tabs ----------")
@@ -2210,7 +2190,9 @@ def _create_tabs(self, activate_tab=None):
syslog.info("UI: Stored device tabs ----------")
self._dump_tab_map(config_tab_map)
if missing_tab_guids:
- syslog.info(f"Found {len(missing_data)} devices from the saved ordered list:")
+ syslog.info(
+ f"Found {len(missing_data)} devices from the saved ordered list:"
+ )
for device_guid, tab_type in missing_data:
device_name = gremlin.shared_state.get_device_name(device_guid)
syslog.info(f"\t{device_name} {device_guid}")
@@ -2218,9 +2200,10 @@ def _create_tabs(self, activate_tab=None):
syslog.info("UI: Final reordered tabs ----------")
for device_guid, _, tab_type, index in reordered_data:
device_name = gremlin.shared_state.get_device_name(device_guid)
- syslog.info(f"\t[{index}] {device_name} {device_guid} {tab_type}")
+ syslog.info(
+ f"\t[{index}] {device_name} {device_guid} {tab_type}"
+ )
-
# clear and rebuild the tabs in the new order
self._reset_tab_data()
for device_guid, device_name, tab_type, index in reordered_data:
@@ -2228,43 +2211,47 @@ def _create_tabs(self, activate_tab=None):
self._reindex_tabs()
-
el = gremlin.event_handler.EventListener()
el.tabs_loaded.emit()
# select the tab that was last selected (if it exists)
gremlin.shared_state.is_tab_loading = False
-
- except Exception as err:
- pass
+ except Exception:
+ pass
finally:
try:
- gremlin.shared_state.pop_input_selection(reset = True) # allow selections
+ gremlin.shared_state.pop_input_selection(reset=True) # allow selections
selected = False
# if not selected, select a default
device_guid = self.config.last_device_guid
if device_guid is not None:
- if not device_guid in self._tab_device_map:
+ if device_guid not in self._tab_device_map:
# the last selected device is no longer in the device list
device_guid = self.ui.devices.tabData(0).device_guid
selected = True
self._select_input(device_guid, force_switch=True)
-
if device_guid is not None and not selected:
- _, restore_input_type, restore_input_id = self.config.get_last_input(device_guid)
- self._select_input(device_guid, restore_input_type, restore_input_id, force_switch=True)
-
- except Exception as err:
+ _, restore_input_type, restore_input_id = (
+ self.config.get_last_input(device_guid)
+ )
+ self._select_input(
+ device_guid,
+ restore_input_type,
+ restore_input_id,
+ force_switch=True,
+ )
+
+ except Exception:
pass
self.pop_highlighting()
self._update_highlight_toolbar_enabled()
- if verbose:
+ if verbose:
syslog.info("Tab recreated:")
for index in range(self.ui.devices.count()):
device_guid = self.ui.devices.tabData(index).device_guid
@@ -2272,34 +2259,30 @@ def _create_tabs(self, activate_tab=None):
device_name = gremlin.shared_state.get_device_name(device_guid)
else:
device_name = "(unknown)"
- syslog.info(f"\t[{index}] {self.ui.devices.tabText(index)} {device_name} {device_guid}")
-
-
-
+ syslog.info(
+ f"\t[{index}] {self.ui.devices.tabText(index)} {device_name} {device_guid}"
+ )
- def get_ordered_device_guid_list(self, filter_tab_type : TabDeviceType = TabDeviceType.NotSet):
- ''' returns the list of device guids as directinput GUIDs
+ def get_ordered_device_guid_list(
+ self, filter_tab_type: TabDeviceType = TabDeviceType.NotSet
+ ):
+ """returns the list of device guids as directinput GUIDs
:param: filter_tab_type = the type of tab device to filter for
:returns: list of DINPUT GUID
- '''
+ """
data = self._get_tab_map()
device_guid_list = []
for index in range(len(data)):
- (device_guid, device_name, tab_type, index) = data[index]
- if filter_tab_type == TabDeviceType.NotSet or tab_type == filter_tab_type:
- device_guid_list.append(gremlin.util.parse_guid(device_guid))
-
- return device_guid_list
-
-
-
-
+ (device_guid, device_name, tab_type, index) = data[index]
+ if filter_tab_type == TabDeviceType.NotSet or tab_type == filter_tab_type:
+ device_guid_list.append(gremlin.util.parse_guid(device_guid))
+ return device_guid_list
- def _find_tab_data(self, search_widget_type : TabDeviceType):
- ''' gets tab data based on widget type'''
+ def _find_tab_data(self, search_widget_type: TabDeviceType):
+ """gets tab data based on widget type"""
tab_map = self._get_tab_map()
data = []
for device_guid, device_name, device_type, tab_index in tab_map.values():
@@ -2308,22 +2291,26 @@ def _find_tab_data(self, search_widget_type : TabDeviceType):
return data
def _find_joystick_tab_data(self):
- ''' gets the joystick tab data '''
+ """gets the joystick tab data"""
return self._find_tab_data(TabDeviceType.Joystick)
def _find_tab_data_guid(self, search_guid):
- ''' gets tab data based on the device guid '''
- if not isinstance(search_guid,str):
- search_guid = str(search_guid) # tab map stores the GUID as a string
+ """gets tab data based on the device guid"""
+ if not isinstance(search_guid, str):
+ search_guid = str(search_guid) # tab map stores the GUID as a string
tab_map = self._get_tab_map()
- data = [(device_guid, device_name, device_type, tab_index) for device_guid, device_name, device_type, tab_index in tab_map.values() if device_guid == search_guid]
+ data = [
+ (device_guid, device_name, device_type, tab_index)
+ for device_guid, device_name, device_type, tab_index in tab_map.values()
+ if device_guid == search_guid
+ ]
if data:
return data[0]
return None, None, None, None
def _get_tab_widget_guid(self, device_guid):
- ''' gets a tab by device guid '''
+ """gets a tab by device guid"""
widgets = self._get_tab_widgets()
# widget data holds (tab_type, device_guid)
data = [widget for widget in widgets if widget.data[1] == device_guid]
@@ -2332,16 +2319,15 @@ def _get_tab_widget_guid(self, device_guid):
return None
def _get_tab_index(self, device_guid):
- ''' gets the tab index for the given GUID '''
+ """gets the tab index for the given GUID"""
if not isinstance(device_guid, str):
device_guid = str(device_guid)
if device_guid in self._tab_device_map:
return self._tab_device_map[device_guid]
return None
-
- def _get_tab_widgets_by_type(self, tab_type : TabDeviceType):
- ''' gets widgets by the tab type '''
+ def _get_tab_widgets_by_type(self, tab_type: TabDeviceType):
+ """gets widgets by the tab type"""
widgets = self._get_tab_widgets()
# widget data holds (tab_type, device_guid)
data = [widget for widget in widgets if widget.data[0] == tab_type]
@@ -2354,14 +2340,12 @@ def _get_tab_name_guid(self, device_guid):
_, device_name, _, _ = data
return device_name
-
def _get_tab_widgets(self):
- ''' returns the tab objects '''
+ """returns the tab objects"""
return self._widget_cache.values()
-
def _select_last_tab(self):
- ''' restore the last selected tab '''
+ """restore the last selected tab"""
# print (f"select last tab: {self.config.last_tab_guid}")
device_guid, input_type, input_id = self.config.get_last_input()
eh = gremlin.event_handler.EventListener()
@@ -2369,13 +2353,13 @@ def _select_last_tab(self):
@QtCore.Slot()
def _ui_ready(self):
- ''' UI loop is about to start '''
+ """UI loop is about to start"""
- # update the UI widgets that listen to inputs to disable the ones not visible
+ # update the UI widgets that listen to inputs to disable the ones not visible
device_guid, input_type, input_id = self.restore_input
# syslog = logging.getLogger("system")
verbose = self.config.verbose_mode_details
-
+
if device_guid is None:
# no default selected, pick the first tab
device_guid = self.getFirstTabDeviceGuid()
@@ -2401,14 +2385,13 @@ def _ui_ready(self):
input_id = default_input_id
if input_id is None:
input_id = default_input_id
-
+
self._select_input(device_guid, input_type, input_id)
if verbose:
device_name = gremlin.joystick_handling.device_name_from_guid(device_guid)
- syslog.info(f"UI: startup selection: {device_name} {gremlin.input_types.InputType.to_string(input_type)} {input_id}")
-
-
-
+ syslog.info(
+ f"UI: startup selection: {device_name} {gremlin.input_types.InputType.to_string(input_type)} {input_id}"
+ )
# enable highlighting
self.pop_highlighting(True)
@@ -2416,9 +2399,11 @@ def _ui_ready(self):
# update status nar
self._update_mode_status_bar()
# startup setups
- el.toggle_highlight.emit(self.is_autoswitch_highlighting, self.is_axis_highlighting, self.is_button_highlighting)
-
-
+ el.toggle_highlight.emit(
+ self.is_autoswitch_highlighting,
+ self.is_axis_highlighting,
+ self.is_button_highlighting,
+ )
def _select_last_input(self):
# if there is a last input - select that input as well
@@ -2428,11 +2413,11 @@ def _select_last_input(self):
eh.select_input.emit(device_guid, input_type, input_id, False, True, False)
def _get_device_name(self, device_guid):
- ''' gets the name of the specified device '''
+ """gets the name of the specified device"""
return gremlin.shared_state.get_device_name(device_guid)
- def _get_last_input(self, device_guid : str) -> tuple:
- ''' Gets the last input selection for the given device
+ def _get_last_input(self, device_guid: str) -> tuple:
+ """Gets the last input selection for the given device
If there was no prior selection, the first input for the device is returned.
If there is no first input because it's empty, return None.
@@ -2440,63 +2425,93 @@ def _get_last_input(self, device_guid : str) -> tuple:
:param: device_guid id of the device to get as a string
:returns: (input_type, Input_id)
- '''
+ """
input_type, input_id = gremlin.shared_state.last_input_id(device_guid)
if not input_type:
# pick the first input for that tab
- widget = self._get_tab_widget_guid(device_guid)
- input_item: gremlin.base_profile.InputItem = self._get_input_item(device_guid, 0)
+ input_item: gremlin.base_profile.InputItem = self._get_input_item(
+ device_guid, 0
+ )
if input_item:
return (input_item.input_type, input_item.input_id)
return (None, None)
- def _get_input_item(self, device_guid : str, index : int) -> gremlin.base_profile.InputItem:
- ''' get the input item at the specified index in the device - index is 0 based '''
+ def _get_input_item(
+ self, device_guid: str, index: int
+ ) -> gremlin.base_profile.InputItem:
+ """get the input item at the specified index in the device - index is 0 based"""
widget = self._get_tab_widget_guid(device_guid)
- if widget is None or not hasattr(widget,"input_item_list_model"):
+ if widget is None or not hasattr(widget, "input_item_list_model"):
return None
row_count = widget.input_item_list_model.rows()
if row_count == 0 or index > row_count:
return None
return widget.input_item_list_model.data(index)
-
- def _get_input_items(self, device_guid : str) -> list[gremlin.base_profile.InputItem]:
- ''' gets the list of all input items for a given device '''
+
+ def _get_input_items(
+ self, device_guid: str
+ ) -> list[gremlin.base_profile.InputItem]:
+ """gets the list of all input items for a given device"""
widget = self._get_tab_widget_guid(device_guid)
- if widget is None or not hasattr(widget,"input_item_list_model"):
+ if widget is None or not hasattr(widget, "input_item_list_model"):
return None
row_count = widget.input_item_list_model.rows()
return [widget.input_item_list_model.data(index) for index in range(row_count)]
-
- def _find_input_item(self, device_guid : str, input_type, input_id) -> gremlin.base_profile.InputItem:
- ''' find the input item matching the input id for a given device '''
+
+ def _find_input_item(
+ self, device_guid: str, input_type, input_id
+ ) -> gremlin.base_profile.InputItem:
+ """find the input item matching the input id for a given device"""
if not device_guid or input_id is None or input_type is None:
# nothing to match
return None
items = self._get_input_items(device_guid)
if items:
- return next((item for item in items if item.input_id == input_id and item.input_type == input_type), None)
+ return next(
+ (
+ item
+ for item in items
+ if item.input_id == input_id and item.input_type == input_type
+ ),
+ None,
+ )
return None
-
- def _select_input(self, device_guid, input_type : InputType = None, input_id = None, force_update = False, force_switch = False, tab_changed = False):
+ def _select_input(
+ self,
+ device_guid,
+ input_type: InputType = None,
+ input_id=None,
+ force_update=False,
+ force_switch=False,
+ tab_changed=False,
+ ):
if gremlin.shared_state.is_input_selection_suspended:
- return # skip if disabled
+ return # skip if disabled
eh = gremlin.event_handler.EventListener()
- eh.select_input.emit(device_guid, input_type, input_id, force_update, force_switch, tab_changed)
-
+ eh.select_input.emit(
+ device_guid, input_type, input_id, force_update, force_switch, tab_changed
+ )
def _config_changed_cb(self):
- ''' called when configuraition has changed '''
+ """called when configuraition has changed"""
self.refresh()
def _config_option_changed(self):
self._update_highlight_toolbar_enabled()
- def _select_input_handler(self, device_guid : dinput.GUID, restore_input_type : gremlin.input_types.InputType = None, restore_input_id = None, force_update : bool = False, force_switch = False, tab_changed = False):
- ''' Selects a specific input on the given tab
+ def _select_input_handler(
+ self,
+ device_guid: dinput.GUID,
+ restore_input_type: gremlin.input_types.InputType = None,
+ restore_input_id=None,
+ force_update: bool = False,
+ force_switch=False,
+ tab_changed=False,
+ ):
+ """Selects a specific input on the given tab
The tab is changed if different from the current tab.
:params:
@@ -2505,17 +2520,12 @@ def _select_input_handler(self, device_guid : dinput.GUID, restore_input_type :
input_id = id of the input, none to auto determine
- '''
-
-
+ """
verbose = gremlin.config.Configuration().verbose_mode_inputs
-
-
if gremlin.shared_state.is_input_selection_suspended:
- return # skip if disabled
-
+ return # skip if disabled
el = gremlin.event_handler.EventListener()
@@ -2525,41 +2535,45 @@ def _select_input_handler(self, device_guid : dinput.GUID, restore_input_type :
if self._selection_locked:
return
-
+
# avoid spamming
- if not force_update and self._last_input_change_timestamp + self._input_delay > time.time():
- # delay not occured yet
- return
+ if (
+ not force_update
+ and self._last_input_change_timestamp + self._input_delay > time.time()
+ ):
+ # delay not occured yet
+ return
self._last_input_change_timestamp = time.time()
-
# syslog = logging.getLogger("system")
input_id = restore_input_id
input_type = restore_input_type
switch_enabled = self.is_autoswitch_highlighting
s_device_guid = str(device_guid)
- if not force_switch and self._current_tab_device_guid != s_device_guid and not switch_enabled:
+ if (
+ not force_switch
+ and self._current_tab_device_guid != s_device_guid
+ and not switch_enabled
+ ):
if verbose:
- syslog.info(f"Select input event: {device_guid} {self._get_device_name(device_guid)} disabled: highlight switch is disabled)")
+ syslog.info(
+ f"Select input event: {device_guid} {self._get_device_name(device_guid)} disabled: highlight switch is disabled)"
+ )
return
-
try:
-
gremlin.util.pushCursor()
self._selection_locked = True
- #self.push_highlighting()
-
- #el.push_joystick() # suspend joystick input while changing UI
+ # self.push_highlighting()
+
+ # el.push_joystick() # suspend joystick input while changing UI
-
if not isinstance(device_guid, str):
device_guid = str(device_guid)
-
# index of current device tab
index = self.ui.devices.currentIndex()
current_device_guid = self._current_tab_device_guid
@@ -2578,91 +2592,117 @@ def _select_input_handler(self, device_guid : dinput.GUID, restore_input_type :
input_id = input_item.input_id
if verbose:
- syslog.info(f"Select input event: {device_guid} {self._get_device_name(device_guid)} input: {InputType.to_display_name(input_type)} input ID: {input_id} current mode: {gremlin.shared_state.current_mode}")
-
+ syslog.info(
+ f"Select input event: {device_guid} {self._get_device_name(device_guid)} input: {InputType.to_display_name(input_type)} input ID: {input_id} current mode: {gremlin.shared_state.current_mode}"
+ )
# guid of current device tab
- if not current_device_guid or current_device_guid.casefold() != device_guid.casefold():
+ if (
+ not current_device_guid
+ or current_device_guid.casefold() != device_guid.casefold()
+ ):
# change tabs
- if verbose: syslog.info("Tab change requested")
+ if verbose:
+ syslog.info("Tab change requested")
index = self._find_tab_index(device_guid)
if index is not None:
with QtCore.QSignalBlocker(self.ui.devices):
self.ui.devices.setCurrentIndex(index)
- if verbose: syslog.info("Tab change complete")
+ if verbose:
+ syslog.info("Tab change complete")
if input_id is None:
# get the default item to select
- if verbose: syslog.info(f"Select input: last input ID {input_id} not found - selecting default input ID")
- last_device_guid, last_input_type, input_id = self.config.get_last_input(device_guid)
- if verbose: syslog.info(f"Select input: found {last_device_guid} {last_input_type} {input_id} ")
+ if verbose:
+ syslog.info(
+ f"Select input: last input ID {input_id} not found - selecting default input ID"
+ )
+ last_device_guid, last_input_type, input_id = (
+ self.config.get_last_input(device_guid)
+ )
+ if verbose:
+ syslog.info(
+ f"Select input: found {last_device_guid} {last_input_type} {input_id} "
+ )
if input_id is None:
input_item = self._get_input_item(device_guid, 0)
if input_item and self._last_input_item != input_item:
-
-
input_id = input_item.input_id
input_type = input_item.input_type
last_device_guid = device_guid
last_input_type = input_type
- if verbose: syslog.info(f"Select input: defaulting to first item on list {last_device_guid} {last_input_type} {input_id} ")
+ if verbose:
+ syslog.info(
+ f"Select input: defaulting to first item on list {last_device_guid} {last_input_type} {input_id} "
+ )
self._last_input_item = input_item
-
- self._update_highlight_toolbar_enabled()
+ self._update_highlight_toolbar_enabled()
- if input_id is not None:
+ if input_id is not None:
# within the inputs = select it
- #syslog.info("ID change started")
+ # syslog.info("ID change started")
widget = self.getWidget(device_guid)
if widget:
- if verbose: syslog.info(f"Select input: select widget {input_type} {input_id}")
+ if verbose:
+ syslog.info(
+ f"Select input: select widget {input_type} {input_id}"
+ )
if tab_changed:
widget.refresh()
else:
-
- widget.input_item_list_view.select_input(input_type, input_id, force_update = tab_changed)
+ widget.input_item_list_view.select_input(
+ input_type, input_id, force_update=tab_changed
+ )
index = widget.input_item_list_view.current_index
widget.input_item_list_view.redraw_index(index)
widget.input_item_list_view.select_item(index, False)
-
# should have contents now
- #has_content = widget.hasRightContent()
- #assert has_content,"Device widget has no content to display"
+ # has_content = widget.hasRightContent()
+ # assert has_content,"Device widget has no content to display"
- if verbose: syslog.info(f"Select input: selected widget {input_type} {input_id}")
+ if verbose:
+ syslog.info(
+ f"Select input: selected widget {input_type} {input_id}"
+ )
# remember the last input id
self._current_tab_input_id = input_id
-
# save settings as the last input
el.input_selection_changed.emit(device_guid, input_type, input_id)
el.update_input_state.emit(device_guid)
-
finally:
self._selection_locked = False
gremlin.util.popCursor()
-
-
@QtCore.Slot(object, object, object)
def _input_changed_handler(self, device_guid, input_type, input_id):
- ''' called when an input changes '''
- current_device_guid, current_input_type, current_input_id = gremlin.shared_state.get_last_input_id()
- if current_device_guid != device_guid or current_input_type != input_type or current_input_id != input_id:
+ """called when an input changes"""
+ current_device_guid, current_input_type, current_input_id = (
+ gremlin.shared_state.get_last_input_id()
+ )
+ if (
+ current_device_guid != device_guid
+ or current_input_type != input_type
+ or current_input_id != input_id
+ ):
# syslog = logging.getLogger("system")
verbose = self.config.verbose_mode_device
- if verbose:
- device_name = gremlin.joystick_handling.device_name_from_guid(device_guid)
- syslog.info(f"INPUT CHANGE: selected {device_name} {device_guid} {InputType.to_display_name(input_type)} input: {input_id}")
+ if verbose:
+ device_name = gremlin.joystick_handling.device_name_from_guid(
+ device_guid
+ )
+ syslog.info(
+ f"INPUT CHANGE: selected {device_name} {device_guid} {InputType.to_display_name(input_type)} input: {input_id}"
+ )
gremlin.shared_state.set_last_input_id(device_guid, input_type, input_id)
- def _find_tab_index(self, search_guid : str):
+ def _find_tab_index(self, search_guid: str):
tab_map = self._get_tab_map()
if not isinstance(search_guid, str):
search_guid = str(search_guid)
@@ -2673,15 +2713,15 @@ def _find_tab_index(self, search_guid : str):
return None
def _active_tab_guid(self):
- ''' gets the GUID of the device for the active tab '''
+ """gets the GUID of the device for the active tab"""
return self._get_tab_guid(self.ui.devices.currentIndex())
def _active_tab_index(self):
- ''' gets the index of the current tab '''
+ """gets the index of the current tab"""
return self.ui.devices.currentIndex()
def _active_input_item(self) -> gremlin.base_profile.InputItem:
- ''' gets the current selected input item '''
+ """gets the current selected input item"""
widget = self._current_tab_widget
if hasattr(widget, "input_item_list_view"):
item_index = widget.input_item_list_view.current_index
@@ -2690,18 +2730,15 @@ def _active_input_item(self) -> gremlin.base_profile.InputItem:
return None
-
-
-
- def _get_tab_guid(self, index : int) -> str:
- ''' gets the tab GUID from its index '''
+ def _get_tab_guid(self, index: int) -> str:
+ """gets the tab GUID from its index"""
widget = self.getWidgetByTabIndex(index)
if hasattr(widget, "data"):
- return widget.data[1] # id is index 1
+ return widget.data[1] # id is index 1
return None
def _get_tab_input_type(self, index: int):
- ''' gets the input type of the tab '''
+ """gets the input type of the tab"""
widget = self.getWidgetByTabIndex(index)
if hasattr(widget, "input_item_list_view"):
item_index = widget.input_item_list_view.current_index
@@ -2718,7 +2755,7 @@ def _get_tab_input_id(self, index: int):
return None
def _get_tab_input_data(self, index: int):
- ''' returns (input_type, input_id) for a given tab index '''
+ """returns (input_type, input_id) for a given tab index"""
widget = self.getWidgetByTabIndex(index)
if hasattr(widget, "input_item_list_view"):
item_index = widget.input_item_list_view.current_index
@@ -2729,19 +2766,24 @@ def _get_tab_input_data(self, index: int):
def _dump_tab_map(self, tab_map):
log = syslog
- for index, (device_guid, device_name, device_class, tab_index) in tab_map.items():
- log.info(f"[{index}] Tab index: [{tab_index}] {device_name} {device_class} {device_guid}")
+ for index, (
+ device_guid,
+ device_name,
+ device_class,
+ tab_index,
+ ) in tab_map.items():
+ log.info(
+ f"[{index}] Tab index: [{tab_index}] {device_name} {device_class} {device_guid}"
+ )
def _refresh_tab(self):
- ''' refreshes the current device tab '''
+ """refreshes the current device tab"""
current_widget = self._current_tab_widget
- if current_widget and hasattr(current_widget,"refresh"):
+ if current_widget and hasattr(current_widget, "refresh"):
current_widget.refresh()
-
-
def _sort_tabs(self):
- ''' sorts device tabs by default order name '''
+ """sorts device tabs by default order name"""
# sorted list of item GUIDs
guid_list = []
@@ -2750,20 +2792,24 @@ def _sort_tabs(self):
syslog.info("SORT: pre sort state:")
self._dump_tab_map(tab_map)
- # add hardware joystick devices by their alphabetical name
+ # add hardware joystick devices by their alphabetical name
joystick_devices = self._find_joystick_tab_data()
joystick_devices.sort(key=lambda x: x[1].casefold())
guid_list.extend(joystick_devices)
-
-
# add the Keyboard, OSC and MIDI
-
- guid_list.append(self._find_tab_data_guid(gremlin.shared_state.keyboard_tab_guid))
+
+ guid_list.append(
+ self._find_tab_data_guid(gremlin.shared_state.keyboard_tab_guid)
+ )
if self.config.midi_enabled:
- guid_list.append(self._find_tab_data_guid(gremlin.shared_state.midi_tab_guid))
+ guid_list.append(
+ self._find_tab_data_guid(gremlin.shared_state.midi_tab_guid)
+ )
if self.config.osc_enabled:
- guid_list.append(self._find_tab_data_guid(gremlin.shared_state.osc_tab_guid))
+ guid_list.append(
+ self._find_tab_data_guid(gremlin.shared_state.osc_tab_guid)
+ )
# add the input vjoy devices
for device_guid in self._vjoy_input_device_guids:
@@ -2774,17 +2820,24 @@ def _sort_tabs(self):
guid_list.append(self._find_tab_data_guid(device_guid))
# add the settings tab
- guid_list.append(self._find_tab_data_guid(gremlin.shared_state.settings_tab_guid))
+ guid_list.append(
+ self._find_tab_data_guid(gremlin.shared_state.settings_tab_guid)
+ )
# add the user plugin tab
- guid_list.append(self._find_tab_data_guid(gremlin.shared_state.plugins_tab_guid))
-
+ guid_list.append(
+ self._find_tab_data_guid(gremlin.shared_state.plugins_tab_guid)
+ )
# move the tabs to the correct location
- tab_data = [self.ui.devices.tabData(index) for index in range(self.ui.devices.count())]
+ tab_data = [
+ self.ui.devices.tabData(index) for index in range(self.ui.devices.count())
+ ]
self._reset_tab_data()
- for index, (device_guid, device_name, device_type, tab_index) in enumerate(guid_list):
- data : TabData = tab_data[index]
+ for index, (device_guid, device_name, device_type, tab_index) in enumerate(
+ guid_list
+ ):
+ data: TabData = tab_data[index]
self._add_tab(device_guid, data.tab_type, index)
tab_map = self._get_tab_map()
@@ -2792,12 +2845,9 @@ def _sort_tabs(self):
syslog.info("SORT: post result:")
self._dump_tab_map(tab_map)
-
self._select_last_tab()
self._select_last_input()
-
-
def _setup_icons(self):
"""Sets the icons of all QAction items."""
# Menu actions
@@ -2811,32 +2861,33 @@ def _setup_icons(self):
gfx_folder = os.path.join(parent, "gfx")
if not os.path.isdir(gfx_folder):
raise gremlin.error.GremlinError(f"Unable to find icons: {folder}")
-
- normal_color = gremlin.ui.ui_common.Color.normalColor()
- is_dark = gremlin.shared_state.is_dark_theme
- profile_icon = "gfx/dark_profile_open.svg" if is_dark else "gfx/profile_open.svg"
+ normal_color = gremlin.ui.ui_common.Color.normalColor()
+ is_dark = gremlin.shared_state.is_dark_theme
+
+ profile_icon = (
+ "gfx/dark_profile_open.svg" if is_dark else "gfx/profile_open.svg"
+ )
icon = load_icon(profile_icon)
- #icon = self.load_icon("profile_open.svg"))
+ # icon = self.load_icon("profile_open.svg"))
self.ui.actionLoadProfile.setIcon(icon)
prefix = "dark_" if is_dark else ""
- profile_new_icon = f"gfx/{prefix}profile_new.svg"
+ profile_new_icon = f"gfx/{prefix}profile_new.svg"
icon = load_icon(profile_new_icon)
self.ui.actionNewProfile.setIcon(icon)
- profile_save_icon = f"gfx/{prefix}profile_save.svg"
+ profile_save_icon = f"gfx/{prefix}profile_save.svg"
icon = load_icon(profile_save_icon)
self.ui.actionSaveProfile.setIcon(icon)
- profile_save_as_icon = f"gfx/{prefix}profile_save_as.svg"
+ profile_save_as_icon = f"gfx/{prefix}profile_save_as.svg"
icon = load_icon(profile_save_as_icon)
self.ui.actionSaveProfileAs.setIcon(icon)
-
device_information_icon = f"gfx/{prefix}device_information.svg"
icon = load_icon(device_information_icon)
self.ui.actionDeviceInformation.setIcon(icon)
@@ -2853,7 +2904,6 @@ def _setup_icons(self):
icon = load_icon(input_repeater_icon)
self.ui.actionInputRepeater.setIcon(icon)
-
input_viewer_icon = f"gfx/{prefix}input_viewer.svg"
icon = load_icon(input_viewer_icon)
self.ui.actionInputViewer.setIcon(icon)
@@ -2873,12 +2923,10 @@ def _setup_icons(self):
icon = load_icon("ei.adjust-alt", qta_color=normal_color)
self.ui.actionInputViewer.setIcon(icon)
-
# Toolbar actions
-
activate_icon = f"gfx/{prefix}activate.svg"
- activate_on_icon = f"gfx/activate_on.svg"
+ activate_on_icon = "gfx/activate_on.svg"
pixmap_off = load_pixmap(activate_icon)
pixmap_on = load_pixmap(activate_on_icon)
if pixmap_off and pixmap_on:
@@ -2889,47 +2937,49 @@ def _setup_icons(self):
else:
self.ui.actionActivate.setText("Run")
-
self.ui.actionOpen.setIcon(load_icon(profile_icon))
-
self.ui.actionSave.setIcon(load_icon("fa5s.save", qta_color=normal_color))
-
# +---------------------------------------------------------------
# | Signal handlers
# +---------------------------------------------------------------
-
def _device_change_cb(self):
"""Handles addition and removal of joystick devices."""
# Update device tabs
# record the device change
- self._device_change_queue +=1
- #print (f"device change detected {self._device_change_queue}")
-
+ self._device_change_queue += 1
+ # print (f"device change detected {self._device_change_queue}")
if not self.device_change_locked:
-
config = gremlin.config.Configuration()
verbose = config.verbose_mode_device
verbose_detailed = config.verbose_mode_details
self.device_change_locked = True
while self._device_change_queue > 0:
-
try:
# syslog =syslog
if verbose:
- syslog.info(f"Device change begin")
+ syslog.info("Device change begin")
# list which device is different
- old_devices = [(device.device_guid, device.name) for device in self._active_devices]
+ old_devices = [
+ (device.device_guid, device.name)
+ for device in self._active_devices
+ ]
detected_devices = gremlin.joystick_handling.joystick_devices()
- new_devices = [(device.device_guid, device.name) for device in detected_devices]
- added_devices = [item for item in new_devices if not item in old_devices]
- removed_devices = [item for item in old_devices if not item in new_devices]
+ new_devices = [
+ (device.device_guid, device.name) for device in detected_devices
+ ]
+ added_devices = [
+ item for item in new_devices if item not in old_devices
+ ]
+ removed_devices = [
+ item for item in old_devices if item not in new_devices
+ ]
if verbose:
if added_devices:
syslog.info("\tDevice added detected:")
@@ -2950,39 +3000,41 @@ def _device_change_cb(self):
self.ui.actionActivate.setChecked(False)
restart = self.runner.is_running()
if restart:
- syslog.info(f"Profile restart due to device change")
+ syslog.info("Profile restart due to device change")
self.activate(restart)
finally:
-
if verbose_detailed:
- syslog.info(f"Device change end")
+ syslog.info("Device change end")
self.device_change_locked = False
# mark items processed
self._device_change_queue = 0
-
@QtCore.Slot()
def _device_input_changed_cb(self, device_guid, input_type, input_id):
- ''' called when device input changed '''
+ """called when device input changed"""
el = gremlin.event_handler.EventListener()
el.input_selection_changed.emit(device_guid, input_type, input_id)
-
def _tab_moved_cb(self, tab_from, tab_to):
- ''' occurs when a tab is moved '''
+ """occurs when a tab is moved"""
# persist tab order
- print (f"tab move detected {tab_from} {tab_to}")
+ print(f"tab move detected {tab_from} {tab_to}")
# rebuild the tab order
self._reindex_tabs()
# update new order
self.config.tab_list = self._get_tab_map()
index = self.ui.devices.currentIndex()
device_guid = self.ui.devices.tabData(index).device_guid
- _, restore_input_type, restore_input_id = self.config.get_last_input(device_guid)
- self._select_input(device_guid = device_guid, input_type = restore_input_type, input_id = restore_input_id, force_update =True, force_switch=True)
-
-
-
+ _, restore_input_type, restore_input_id = self.config.get_last_input(
+ device_guid
+ )
+ self._select_input(
+ device_guid=device_guid,
+ input_type=restore_input_type,
+ input_id=restore_input_id,
+ force_update=True,
+ force_switch=True,
+ )
def _edit_mode_selector_changed(self, new_mode):
"""Updates the current mode to the provided one.
@@ -2992,18 +3044,21 @@ def _edit_mode_selector_changed(self, new_mode):
# refresh the modes
eh = gremlin.event_handler.EventHandler()
- eh.change_mode(new_mode, force_update = True)
-
+ eh.change_mode(new_mode, force_update=True)
def _get_process_mode(self, process_path):
# syslog = logging.getLogger("system")
verbose = gremlin.config.Configuration().verbose_mode_process
if process_path in self._process_runtime_map:
mode = self._process_runtime_map[process_path]
- if verbose: syslog.info(f"PROC MODE: using last mode [{mode}] for process {process_path}")
+ if verbose:
+ syslog.info(
+ f"PROC MODE: using last mode [{mode}] for process {process_path}"
+ )
else:
mode = self.profile.get_last_runtime_mode()
- if verbose: syslog.info(f"PROC MODE: using last saved profile mode mode [{mode}]")
+ if verbose:
+ syslog.info(f"PROC MODE: using last saved profile mode mode [{mode}]")
return mode
def _process_changed_cb(self, new_process_path):
@@ -3019,7 +3074,7 @@ def _process_changed_cb(self, new_process_path):
if self._process_change_in_progress:
return
-
+
self._process_change_in_progress = True
config = gremlin.config.Configuration()
@@ -3031,40 +3086,50 @@ def _process_changed_cb(self, new_process_path):
self.current_process_path = new_process_path
# get auto load options
- option_auto_load = config.autoload_profiles # if true, change the profile if a process is mapped to one, do not activate unless gremlin was already activated
- option_auto_load_on_focus = config.activate_on_process_focus # if true, also activate the mapped profile if not activated
+ option_auto_load = config.autoload_profiles # if true, change the profile if a process is mapped to one, do not activate unless gremlin was already activated
+ option_auto_load_on_focus = (
+ config.activate_on_process_focus
+ ) # if true, also activate the mapped profile if not activated
process_base = os.path.basename(new_process_path)
-
- option_keep_focus = config.keep_profile_active_on_focus_loss # if true, do not deactivate the profile on gremlinEx focus loss
- option_reset_mode_on_process_activate = config.reset_mode_on_process_activate # if true, reset the profile to the default start mode on process focus
+ option_keep_focus = (
+ config.keep_profile_active_on_focus_loss
+ ) # if true, do not deactivate the profile on gremlinEx focus loss
+ option_reset_mode_on_process_activate = (
+ config.reset_mode_on_process_activate
+ ) # if true, reset the profile to the default start mode on process focus
option_restore_mode = config.restore_profile_mode_on_start # if true, restore last used profile mode on process focus (overrides the reset to default mode)
-
if verbose:
- syslog.info("="*50)
- syslog.info(f"PROC: Process change detected: new process: >>>>>> [{process_base}] <<<<<<<")
+ syslog.info("=" * 50)
+ syslog.info(
+ f"PROC: Process change detected: new process: >>>>>> [{process_base}] <<<<<<<"
+ )
syslog.info(f"\t autoload: [{option_auto_load}]")
syslog.info(f"\t autoload on focus: [{option_auto_load}]")
syslog.info(f"\t keep focus: [{option_keep_focus}]")
- syslog.info(f"\t reset to default mode on process activate: [{option_reset_mode_on_process_activate}]")
+ syslog.info(
+ f"\t reset to default mode on process activate: [{option_reset_mode_on_process_activate}]"
+ )
syslog.info(f"\t restore mode on profile start: [{option_restore_mode}]")
try:
-
if not option_auto_load or not option_auto_load_on_focus:
# skip if we are not auto starting or auto loading profiles
- if verbose: syslog.info(f"PROC: Process change detected [{process_base}]: ignoring because auto-load options are disabled")
+ if verbose:
+ syslog.info(
+ f"PROC: Process change detected [{process_base}]: ignoring because auto-load options are disabled"
+ )
return
-
-
eh = gremlin.event_handler.EventHandler()
current_profile_path = self.profile.profile_file
-
- is_running = gremlin.shared_state.is_running # true if gremlin is running at process change
- if is_running :
+
+ is_running = (
+ gremlin.shared_state.is_running
+ ) # true if gremlin is running at process change
+ if is_running:
current_profile_save_mode = gremlin.shared_state.current_mode
else:
current_profile_save_mode = None
@@ -3075,165 +3140,242 @@ def _process_changed_cb(self, new_process_path):
new_profile_path = profile_item.profile if profile_item else None
if not current_profile_path or not os.path.isfile(current_profile_path):
- syslog.error("PROC: current profile is not saved - auto process start is unable to function")
- gremlin.ui.ui_common.MessageBox(prompt = f"Current profile [{current_profile_path}] is not saved or the XML could not be found. Process auto-start disabled.")
+ syslog.error(
+ "PROC: current profile is not saved - auto process start is unable to function"
+ )
+ gremlin.ui.ui_common.MessageBox(
+ prompt=f"Current profile [{current_profile_path}] is not saved or the XML could not be found. Process auto-start disabled."
+ )
return
-
+
current_profile_base = os.path.basename(current_profile_path)
-
+
if not new_profile_path:
# no profile was found for the new process that received focus
if not option_keep_focus:
# keep focus is off so we disable the profile
- if verbose: syslog.info(f"PROC: process change: unmapped process [{process_base}] - keep focus is disabled - deactivate profile")
- self.activate(False) # this saves the current profile runtime mode
- self.ui.actionActivate.setChecked(False) # this turns "on" the run icon
+ if verbose:
+ syslog.info(
+ f"PROC: process change: unmapped process [{process_base}] - keep focus is disabled - deactivate profile"
+ )
+ self.activate(False) # this saves the current profile runtime mode
+ self.ui.actionActivate.setChecked(
+ False
+ ) # this turns "on" the run icon
# done
return
- if verbose: syslog.info(f"PROC: process change: unmapped process [{process_base}] - ignoring process change")
+ if verbose:
+ syslog.info(
+ f"PROC: process change: unmapped process [{process_base}] - ignoring process change"
+ )
return
-
-
+
if not os.path.isfile(new_profile_path):
- syslog.error(f"PROC: process [{new_process_path}] profile file [{new_profile_path}] not found - ignoring process change")
- #gremlin.ui.ui_common.MessageBox(prompt = f"New profile [{new_profile_path}] profile XML could not be found. Process auto-start disabled.")
+ syslog.error(
+ f"PROC: process [{new_process_path}] profile file [{new_profile_path}] not found - ignoring process change"
+ )
+ # gremlin.ui.ui_common.MessageBox(prompt = f"New profile [{new_profile_path}] profile XML could not be found. Process auto-start disabled.")
return
-
new_profile_base = os.path.basename(new_profile_path)
-
- if verbose:
- syslog.info(f"PROC: found profile map [{new_profile_base}] for process {process_base} - current profile: [{current_profile_base}] runtime mode: [{gremlin.shared_state.runtime_mode}]")
+ if verbose:
+ syslog.info(
+ f"PROC: found profile map [{new_profile_base}] for process {process_base} - current profile: [{current_profile_base}] runtime mode: [{gremlin.shared_state.runtime_mode}]"
+ )
# profile entry found - see if we need to change profiles
if not current_profile_path or not os.path.isfile(current_profile_path):
- syslog.error("PROC: Profile does not exist or is not saved. Ignoring process activation as this feature requires the current profile to be saved.")
+ syslog.error(
+ "PROC: Profile does not exist or is not saved. Ignoring process activation as this feature requires the current profile to be saved."
+ )
return
-
-
+
eh = gremlin.event_handler.EventHandler()
- restore_mode = None # derived profile mode to change to
- mode_changed = False # true if a mode change occured
+ restore_mode = None # derived profile mode to change to
+ mode_changed = False # true if a mode change occured
if not compare_path(current_profile_path, new_profile_path):
# current profile and new profile are different - swap to the new profile
-
+
# deactivate any current profile if active
if verbose:
current_base_name = os.path.basename(current_profile_path)
- syslog.info(f"PROC: process change: deactivate current profile: [{current_base_name}] - saving last used mode: [{gremlin.shared_state.runtime_mode}]")
+ syslog.info(
+ f"PROC: process change: deactivate current profile: [{current_base_name}] - saving last used mode: [{gremlin.shared_state.runtime_mode}]"
+ )
- self.activate(False) # this saves the current profile runtime mode
- self.ui.actionActivate.setChecked(False) # this turns "on" the run icon
+ self.activate(False) # this saves the current profile runtime mode
+ self.ui.actionActivate.setChecked(False) # this turns "on" the run icon
# remember the last used mode for the profile before we change to the new - only do this if we are in runtime
if current_profile_save_mode:
- if verbose: syslog.info(f"\tSave last used mode: {current_profile_save_mode} for profile [{current_base_name}]")
- self._runtime_mode_map[current_profile_path] = current_profile_save_mode
+ if verbose:
+ syslog.info(
+ f"\tSave last used mode: {current_profile_save_mode} for profile [{current_base_name}]"
+ )
+ self._runtime_mode_map[current_profile_path] = (
+ current_profile_save_mode
+ )
if self.current_process_path:
- if verbose:
- base_process_name = os.path.basename(self.current_process_path)
- syslog.info(f"\tAssociate last process [{base_process_name}] with mode [{current_profile_save_mode}]")
- self._process_runtime_map[self.current_process_path] = current_profile_save_mode
+ if verbose:
+ base_process_name = os.path.basename(
+ self.current_process_path
+ )
+ syslog.info(
+ f"\tAssociate last process [{base_process_name}] with mode [{current_profile_save_mode}]"
+ )
+ self._process_runtime_map[self.current_process_path] = (
+ current_profile_save_mode
+ )
else:
- if verbose: syslog.info(f"\tSave last used mode: no active mode found for profile [{current_base_name}]")
+ if verbose:
+ syslog.info(
+ f"\tSave last used mode: no active mode found for profile [{current_base_name}]"
+ )
self._active_process_path = new_process_path
# change profile
- if verbose: syslog.info(f"PROC: process change: switch profile [{current_base_name}] -> [{new_profile_base}]")
-
+ if verbose:
+ syslog.info(
+ f"PROC: process change: switch profile [{current_base_name}] -> [{new_profile_base}]"
+ )
+
# load the new profile
self._do_load_profile(new_profile_path)
loaded_profile = gremlin.shared_state.current_profile
self.profile = loaded_profile
- if verbose: syslog.info(f"PROC: process change: loaded profile [{new_profile_base}]")
+ if verbose:
+ syslog.info(
+ f"PROC: process change: loaded profile [{new_profile_base}]"
+ )
if not is_running:
# gremlin was not running at the time of the process change - see if we should auto-activate the profile based on the auto activate option
if not option_auto_load_on_focus:
# activation is not requested - load only, we're done
- if verbose: syslog.info(f"PROC: Profile loaded, activation is not requested and GremlinEx wasn't running at process change. Process change completed.")
+ if verbose:
+ syslog.info(
+ "PROC: Profile loaded, activation is not requested and GremlinEx wasn't running at process change. Process change completed."
+ )
return
# activate the new profile
- if verbose: syslog.info(f"PROC: Activate profile [{new_profile_base}]")
+ if verbose:
+ syslog.info(f"PROC: Activate profile [{new_profile_base}]")
self.ui.actionActivate.setChecked(True)
- self.activate(True) # this will also restore the profile runtime mode based on current options
-
+ self.activate(
+ True
+ ) # this will also restore the profile runtime mode based on current options
- if verbose: syslog.info(f"PROC: Profile [{new_profile_base}] activated")
+ if verbose:
+ syslog.info(f"PROC: Profile [{new_profile_base}] activated")
# update flag if a mode should be restored
- option_restore_mode = option_restore_mode or loaded_profile.get_restore_mode()
+ option_restore_mode = (
+ option_restore_mode or loaded_profile.get_restore_mode()
+ )
- self._profile_auto_activated = True # remember the profile was auto activated by virtue of a process change
+ self._profile_auto_activated = True # remember the profile was auto activated by virtue of a process change
restore_mode = loaded_profile.get_restore_mode()
# figure out which mode to restore mode for the new profile
- if verbose: syslog.info(f"PROC: profile restore mode flag: [{option_restore_mode}]")
+ if verbose:
+ syslog.info(
+ f"PROC: profile restore mode flag: [{option_restore_mode}]"
+ )
- current_mode = gremlin.shared_state.current_mode # current mode of the loaded profile
+ current_mode = (
+ gremlin.shared_state.current_mode
+ ) # current mode of the loaded profile
- if verbose: syslog.info(f"PROC: profile load mode: {current_mode} derived mode to restore: [{restore_mode}]")
+ if verbose:
+ syslog.info(
+ f"PROC: profile load mode: {current_mode} derived mode to restore: [{restore_mode}]"
+ )
-
if option_reset_mode_on_process_activate:
# restore only the profile default mode
restore_mode = loaded_profile.get_default_mode()
- if verbose: syslog.info(f"PROC: Selected profile default mode [{restore_mode}] from profile mode dialog")
+ if verbose:
+ syslog.info(
+ f"PROC: Selected profile default mode [{restore_mode}] from profile mode dialog"
+ )
elif option_restore_mode:
# restore to the mapped profile default mode defined in mappings
restore_mode = self._get_process_mode(new_process_path)
- if verbose and restore_mode: syslog.info(f"PROC: Selected profile default mode [{restore_mode}] from runtime memory")
+ if verbose and restore_mode:
+ syslog.info(
+ f"PROC: Selected profile default mode [{restore_mode}] from runtime memory"
+ )
if not restore_mode:
- restore_mode = gremlin.shared_state.current_profile.get_restore_mode() # saved JSON mode
- if verbose and restore_mode: syslog.info(f"PROC: Selected profile default mode [{restore_mode}] from profile mode dialog")
+ restore_mode = (
+ gremlin.shared_state.current_profile.get_restore_mode()
+ ) # saved JSON mode
+ if verbose and restore_mode:
+ syslog.info(
+ f"PROC: Selected profile default mode [{restore_mode}] from profile mode dialog"
+ )
if not restore_mode:
- restore_mode = gremlin.shared_state.current_profile.get_default_mode()
- if verbose: syslog.info(f"PROC: Selected profile default mode [{restore_mode}] from profile mode dialog as a fallback")
+ restore_mode = (
+ gremlin.shared_state.current_profile.get_default_mode()
+ )
+ if verbose:
+ syslog.info(
+ f"PROC: Selected profile default mode [{restore_mode}] from profile mode dialog as a fallback"
+ )
# a mapping profile was found - new profile was loaded if needed - see if we need to change the mode
if restore_mode is not None:
if restore_mode != current_mode:
- if not restore_mode in loaded_profile.get_modes():
- syslog.error(f"PROC: Unable to find mode [{restore_mode}] in profile - defaulting to default mode dialog startup mode")
+ if restore_mode not in loaded_profile.get_modes():
+ syslog.error(
+ f"PROC: Unable to find mode [{restore_mode}] in profile - defaulting to default mode dialog startup mode"
+ )
restore_mode = loaded_profile.get_default_mode()
-
- if verbose: syslog.info(f"PROC: request mode change to [{restore_mode}]")
- eh.change_mode(restore_mode, force_update = True) # set the selected mode - note that this may fail if mode locking is enabled
+ if verbose:
+ syslog.info(
+ f"PROC: request mode change to [{restore_mode}]"
+ )
+ eh.change_mode(
+ restore_mode, force_update=True
+ ) # set the selected mode - note that this may fail if mode locking is enabled
mode_changed = True
# done
elif option_auto_load_on_focus and not is_running:
- # re-activate the profile if not activated
- if verbose: syslog.info(f"PROC: profile auto-focus ON: profile not activated, auto-activating profile [{new_profile_base}] ")
+ # re-activate the profile if not activated
+ if verbose:
+ syslog.info(
+ f"PROC: profile auto-focus ON: profile not activated, auto-activating profile [{new_profile_base}] "
+ )
self.ui.actionActivate.setChecked(True)
self.activate(True)
self._profile_auto_activated = True
-
-
if not mode_changed:
# if TTS is enabled and the process changed, issue a TTS message when the mode was not changed so we hear there was a change recorded in focus
eh.TTSNotify(f"Process focus mode {restore_mode}")
-
- # remember the last process that received focus
+ # remember the last process that received focus
self.current_process_path = new_process_path
finally:
- if verbose:
+ if verbose:
if gremlin.shared_state.current_profile.profile_file:
- base_profile = os.path.basename(gremlin.shared_state.current_profile.profile_file)
+ base_profile = os.path.basename(
+ gremlin.shared_state.current_profile.profile_file
+ )
else:
- base_profile= "Not Saved"
- syslog.info(f"PROC: END Process change detected: process: >>>>>> [{process_base}] <<<<<<< final profile: [{base_profile}] mode: [{gremlin.shared_state.current_mode}]")
+ base_profile = "Not Saved"
+ syslog.info(
+ f"PROC: END Process change detected: process: >>>>>> [{process_base}] <<<<<<< final profile: [{base_profile}] mode: [{gremlin.shared_state.current_mode}]"
+ )
self._process_change_in_progress = False
@@ -3245,25 +3387,24 @@ def _tray_icon_activated_cb(self, reason):
if reason == QtWidgets.QSystemTrayIcon.Trigger:
self.setHidden(not self.isHidden())
-
def _update_status_bar_active(self, is_active):
import gremlin.input_devices
+
self._is_active = is_active
self._update_status_bar(gremlin.input_devices.remote_state.to_state_event())
def _update_status_bar(self, event):
# updates the status bar
-
"""Updates the status bar with the current state of the system.
:param is_active True if the system is active, False otherwise
"""
try:
if self._is_active:
- text_active = "Active"
+ text_active = 'Active'
else:
- text_active = "Paused"
+ text_active = 'Paused'
if self.ui.actionActivate.isChecked():
text_running = f"Running and {text_active}"
else:
@@ -3271,68 +3412,70 @@ def _update_status_bar(self, event):
# remote control status
if event.is_local:
- local_msg = "Active"
+ local_msg = 'Active'
else:
- local_msg = "Disabled"
+ local_msg = 'Disabled'
if event.is_remote:
- remote_msg = "Active"
+ remote_msg = 'Active'
else:
- remote_msg = "Disabled"
+ remote_msg = 'Disabled'
- self.status_bar_is_active_widget.setText(f"Status: {text_running} Local Control {local_msg} Broadcast: {remote_msg}")
+ self.status_bar_is_active_widget.setText(
+ f"Status: {text_running} Local Control {local_msg} Broadcast: {remote_msg}"
+ )
self._update_mode_status_bar()
- except e:
- log_sys_error(f"Unable to update status bar event: {event}")
- log_sys_error(e)
+ except Exception as e:
+ log_sys_error(f"Unable to update status bar event: {event}\n {e}")
@QtCore.Slot()
def _update_mode_change(self, new_mode):
self._update_ui_mode(new_mode)
self._update_mode_status_bar()
-
@QtCore.Slot()
def _update_mode_status_bar(self):
- ''' updates the mode status bar with current runtime and edit modes '''
+ """updates the mode status bar with current runtime and edit modes"""
try:
-
is_running = gremlin.shared_state.is_running
runtime_mode = gremlin.shared_state.runtime_mode
# syslog = logging.getLogger("system")
-
edit_mode = gremlin.shared_state.edit_mode
if not edit_mode:
# get it from the mode drop down
edit_mode = self.mode_selector.currentMode()
-
-
if not is_running:
- msg = f" Edit Mode: {edit_mode if edit_mode else "n/a"}"
+ msg = f" Edit Mode: {edit_mode if edit_mode else 'n/a'}"
if self._status_bar_last_edit_mode != edit_mode:
- syslog.info(f"Mode: New edit mode: [{edit_mode}] (last mode [{self._status_bar_last_edit_mode}])")
+ syslog.info(
+ f"Mode: New edit mode: [{edit_mode}] (last mode [{self._status_bar_last_edit_mode}])"
+ )
self._status_bar_last_edit_mode = edit_mode
else:
- msg = f"Runtime Mode: {runtime_mode if runtime_mode else "n/a"}"
+ msg = f"Runtime Mode: {runtime_mode if runtime_mode else 'n/a'}"
if self._status_bar_last_runtime_mode != runtime_mode:
- syslog.info(f"Mode: New runtime mode: [{runtime_mode}] (last mode [{self._status_bar_last_runtime_mode}])")
+ syslog.info(
+ f"Mode: New runtime mode: [{runtime_mode}] (last mode [{self._status_bar_last_runtime_mode}])"
+ )
self._status_bar_last_runtime_mode = runtime_mode
-
-
self.status_bar_mode_widget.setText(msg)
if self.config.mode_change_message:
- self.ui.tray_icon.showMessage(f"Runtime Mode: {runtime_mode if runtime_mode else "n/a"} Edit mode: {edit_mode if edit_mode else "n/a"}","",QtWidgets.QSystemTrayIcon.MessageIcon.NoIcon,250)
+ self.ui.tray_icon.showMessage(
+ f"Runtime Mode: {runtime_mode if runtime_mode else 'n/a'} Edit mode: {edit_mode if edit_mode else 'n/a'}",
+ "",
+ QtWidgets.QSystemTrayIcon.MessageIcon.NoIcon,
+ 250,
+ )
except Exception as err:
syslog.error(f"Unable to update status bar mode:\n{err}")
-
def _update_ui_mode(self, new_mode):
- """ called when the profile mode changes
+ """called when the profile mode changes
:param mode the now current mode
"""
@@ -3346,7 +3489,7 @@ def _update_ui_mode(self, new_mode):
gremlin.util.pushCursor()
with QtCore.QSignalBlocker(self.mode_selector):
for tab in self._get_tab_widgets():
- if hasattr(tab,"set_mode"):
+ if hasattr(tab, "set_mode"):
tab.set_mode(new_mode)
# select the last input after mode change
# self._select_last_input()
@@ -3366,28 +3509,35 @@ def _kb_suspend_cb(self, suspend):
@QtCore.Slot(object)
def _kb_event_cb(self, event):
- ''' listen for keyboard modifiers and keyboard events at runtime '''
+ """listen for keyboard modifiers and keyboard events at runtime"""
key = gremlin.keyboard.KeyMap.from_event(event)
# ignore if we're running
- if key is None or self.runner.is_running() or gremlin.shared_state.ui_keyinput_suspended():
+ if (
+ key is None
+ or self.runner.is_running()
+ or gremlin.shared_state.ui_keyinput_suspended()
+ ):
return
if key.lookup_name == "f5":
# activate mode on F5
- if not self.config.is_debug and self.config.start_on_f5 and not gremlin.shared_state.ui_keyinput_suspended():
- self.ui.actionActivate.trigger()
+ if (
+ not self.config.is_debug
+ and self.config.start_on_f5
+ and not gremlin.shared_state.ui_keyinput_suspended()
+ ):
+ self.ui.actionActivate.trigger()
@property
def input_axis_override(self):
- ''' true if temporary override of monitoring axis is enabled '''
+ """true if temporary override of monitoring axis is enabled"""
return self._temp_input_axis_override
-
@property
def input_axis_only_override(self):
- ''' true if temporary override of monitoring exclusive axis is enabled '''
+ """true if temporary override of monitoring exclusive axis is enabled"""
return self._temp_input_axis_only_override
# +---------------------------------------------------------------
@@ -3402,7 +3552,9 @@ def apply_user_settings(self, ignore_minimize=False):
self._set_joystick_input_highlighting(self.config.highlight_enabled)
self._set_joystick_input_axis_highlighting(self.config.highlight_input_axis)
- self._set_joystick_input_buttons_highlighting(self.config.highlight_input_buttons)
+ self._set_joystick_input_buttons_highlighting(
+ self.config.highlight_input_buttons
+ )
if not ignore_minimize:
self.setHidden(self.config.start_minimized)
@@ -3425,20 +3577,22 @@ def _create_cheatsheet(self):
:param file_format the format of the cheatsheet, html or pdf
"""
import gremlin.cheatsheet
+
fname, _ = QtWidgets.QFileDialog.getSaveFileName(
None,
"Save cheatsheet",
gremlin.util.userprofile_path(),
- "PDF files (*.pdf)"
+ "PDF files (*.pdf)",
)
if len(fname) > 0:
gremlin.cheatsheet.generate_cheatsheet(fname, self.profile)
def _view_input_map(self):
- ''' display input map dialog '''
+ """display input map dialog"""
import gremlin.cheatsheet
import gremlin.util
- dialog = gremlin.cheatsheet.ViewInput(parent = self)
+
+ dialog = gremlin.cheatsheet.ViewInput(parent=self)
dialog.setMinimumHeight(600)
gremlin.util.centerDialog(dialog)
dialog.show()
@@ -3451,7 +3605,6 @@ def _create_load_profile_function(self, fname):
"""
return lambda: self._load_recent_profile(fname)
-
@property
def profile(self):
return gremlin.shared_state.current_profile
@@ -3465,7 +3618,6 @@ def profile(self, value):
gremlin.shared_state.current_profile = value
-
def _do_load_profile(self, fname):
"""Load the profile with the given filename.
@@ -3480,15 +3632,14 @@ def _do_load_profile(self, fname):
self.activate(False)
el = gremlin.event_handler.EventListener()
- el.profile_unloaded.emit() # tell the UI we're about to load a new profile
-
- el.push_input_selection() # suspend input selection
+ el.profile_unloaded.emit() # tell the UI we're about to load a new profile
+ el.push_input_selection() # suspend input selection
# mode to restore post-load if possible
last_device_guid, last_input_type, last_input_id = self.config.get_last_input()
- # Attempt to load the new profile
+ # Attempt to load the new profile
try:
new_profile = gremlin.base_profile.Profile()
if gremlin.shared_state.current_profile:
@@ -3504,23 +3655,22 @@ def _do_load_profile(self, fname):
self._sanitize_profile(new_profile)
-
-
last_edit_mode = gremlin.config.Configuration().get_profile_last_edit_mode()
if not last_edit_mode:
# pick the top mode if nothing was saved in the configuration
last_edit_mode = self.profile.get_root_mode()
- gremlin.config.Configuration().set_profile_last_edit_mode(last_edit_mode)
-
+ gremlin.config.Configuration().set_profile_last_edit_mode(
+ last_edit_mode
+ )
+
modes = new_profile.get_modes()
if last_edit_mode is None:
last_edit_mode = modes[0]
- if not last_edit_mode in modes:
+ if last_edit_mode not in modes:
# no longer in the current mode list
last_edit_mode = new_profile.get_default_mode()
-
eh = gremlin.event_handler.EventHandler()
eh.set_edit_mode(last_edit_mode)
@@ -3529,8 +3679,9 @@ def _do_load_profile(self, fname):
self._create_tabs()
# Make the first root node the default active mode
- self.mode_selector.populate_selector(new_profile, last_edit_mode, emit = False)
-
+ self.mode_selector.populate_selector(
+ new_profile, last_edit_mode, emit=False
+ )
# Save the profile at this point if it was converted from a prior
# profile version, as otherwise the change detection logic will
@@ -3544,10 +3695,6 @@ def _do_load_profile(self, fname):
# update the hash value
self._profile_hash = new_profile.getMappingHash()
-
-
-
-
except (KeyError, TypeError) as error:
# An error occurred while parsing an existing profile,
# creating an empty profile instead
@@ -3559,11 +3706,11 @@ def _do_load_profile(self, fname):
cfg = gremlin.config.Configuration()
cfg.last_profile = None
self.new_profile()
- gremlin.util.display_error(f"Failed to load the profile {fname} due to:\n\n{error}")
+ gremlin.util.display_error(
+ f"Failed to load the profile {fname} due to:\n\n{error}"
+ )
finally:
-
-
el.profile_loaded.emit()
# update the status bar
@@ -3573,28 +3720,22 @@ def _do_load_profile(self, fname):
# restore the mouse cursor
popCursor()
- el.pop_input_selection(True) # restore input selection and reset
+ el.pop_input_selection(True) # restore input selection and reset
self._select_input(last_device_guid, last_input_type, last_input_id, True)
-
-
def refresh(self):
- ''' refresh the UI '''
+ """refresh the UI"""
self._create_tabs()
- current_profile =gremlin.shared_state.current_profile
+ current_profile = gremlin.shared_state.current_profile
current_mode = gremlin.shared_state.current_mode
-
-
# Make the first root node the default active mode
- self.mode_selector.populate_selector(current_profile, current_mode, emit = False)
+ self.mode_selector.populate_selector(current_profile, current_mode, emit=False)
self._update_mode_status_bar()
-
# refresh current device tab
- #self._refresh_tab()
-
+ # self._refresh_tab()
def _force_close(self):
"""Forces the closure of the program."""
@@ -3632,14 +3773,13 @@ def _save_changes_request(self):
continue_process = True
if self._has_profile_changed():
-
message_box = QtWidgets.QMessageBox()
message_box.setText("The profile has been modified.")
message_box.setInformativeText("Do you want to save your changes?")
message_box.setStandardButtons(
- QtWidgets.QMessageBox.StandardButton.Save |
- QtWidgets.QMessageBox.StandardButton.Discard |
- QtWidgets.QMessageBox.StandardButton.Cancel
+ QtWidgets.QMessageBox.StandardButton.Save
+ | QtWidgets.QMessageBox.StandardButton.Discard
+ | QtWidgets.QMessageBox.StandardButton.Cancel
)
message_box.setDefaultButton(QtWidgets.QMessageBox.StandardButton.Save)
gremlin.util.centerDialog(message_box)
@@ -3669,14 +3809,13 @@ def _has_profile_changed(self):
return True
else:
-
# get the current hash to detect changes
current_hash = self.profile.getMappingHash()
if self._profile_hash == current_hash:
return False
# save the profile and compare to the original file
- #tmp_path = os.path.join(os.getenv("temp"), gremlin.util.get_guid() + ".xml")
+ # tmp_path = os.path.join(os.getenv("temp"), gremlin.util.get_guid() + ".xml")
tmp_path = os.path.join(os.getenv("temp"), "gremlin.xml")
self.profile.to_xml(tmp_path)
@@ -3686,14 +3825,15 @@ def _has_profile_changed(self):
try:
t1 = etree.parse(tmp_path, parser=parser)
t2 = etree.parse(profile_fname, parser=parser)
- except:
+ except Exception as e:
# error loading file - assume no changes
+ print(f"Error loading file: {e}")
return False
# remove container IDs and action IDs from xml
trees = (t1, t2)
- ignore_list = ("container_id","action_id")
- gate_ignore_list = ("id","min_id","max_id")
+ ignore_list = ("container_id", "action_id")
+ gate_ignore_list = ("id", "min_id", "max_id")
for t in trees:
remove_nodes = []
@@ -3707,12 +3847,12 @@ def _has_profile_changed(self):
description = node.get("description")
if not description:
del node.attrib["description"]
- if node.tag in ("button","axis","hat") and not description:
+ if node.tag in ("button", "axis", "hat") and not description:
children = list(node)
if not children:
# remove blank axis, button and hat nodes from the comparison
remove_nodes.append(node)
- if node.tag in ("gate","range"):
+ if node.tag in ("gate", "range"):
# ignore IDs that will change
for attrib in gate_ignore_list:
if attrib in node.attrib:
@@ -3722,24 +3862,19 @@ def _has_profile_changed(self):
for node in remove_nodes:
node.getparent().remove(node)
-
-
# tmp1_path = os.path.join(os.getenv("temp"), "g1.xml")
# tmp2_path = os.path.join(os.getenv("temp"), "g2.xml")
-
+
# t1.write(tmp1_path, pretty_print=True,xml_declaration=True,encoding="utf-8")
# t2.write(tmp2_path, pretty_print=True,xml_declaration=True,encoding="utf-8")
# gremlin.util.display_file(tmp1_path)
# gremlin.util.display_file(tmp2_path)
-
is_changed = etree.tostring(t1) != etree.tostring(t2)
-
return is_changed
-
def _last_runtime_mode(self):
"""Returns the name of the mode last active.
@@ -3747,9 +3882,14 @@ def _last_runtime_mode(self):
first top level mode if none was ever used before
"""
config = gremlin.config.Configuration()
- option_restore_mode = config.restore_profile_mode_on_start or gremlin.shared_state.current_profile.get_restore_mode()
+ option_restore_mode = (
+ config.restore_profile_mode_on_start
+ or gremlin.shared_state.current_profile.get_restore_mode()
+ )
# syslog = logging.getLogger("system")
- syslog.info(f"RUNTIME MODE: Runtime mode determination for profile [{self.profile.name}]")
+ syslog.info(
+ f"RUNTIME MODE: Runtime mode determination for profile [{self.profile.name}]"
+ )
if option_restore_mode:
syslog.info("\tAutomatic restore runtime mode is activated")
@@ -3764,18 +3904,16 @@ def _last_runtime_mode(self):
else:
last_mode = self.profile.get_default_mode()
syslog.info(f"\tusing profile default start mode [{last_mode}]")
-
+
mode_list = gremlin.profile.mode_list()
- if not last_mode in mode_list:
+ if last_mode not in mode_list:
syslog.info(f"\tMode {last_mode} not found")
default_mode = self.profile.get_root_mode()
syslog.info(f"\tMode {last_mode} not found - using [{default_mode}]")
return last_mode
-
-
def _load_recent_profile(self, fname):
"""Loads the provided profile and updates the list of recently used
profiles.
@@ -3790,36 +3928,37 @@ def _load_recent_profile(self, fname):
self._create_recent_profiles()
@QtCore.Slot(str, str)
- def _mode_name_changed(self, old_mode:str, new_mode:str):
+ def _mode_name_changed(self, old_mode: str, new_mode: str):
self._update_mode_status_bar()
- def _edit_mode_changed(self, mode : str):
- ''' called when mode list has changed '''
+ def _edit_mode_changed(self, mode: str):
+ """called when mode list has changed"""
# update the mode selector to the correct edit mode
self.mode_selector.select_mode(mode)
gremlin.event_handler.EventHandler().set_edit_mode(mode)
self._update_mode_status_bar()
- def _runtime_mode_changed(self, mode : str):
- ''' called when runtime mode changes '''
+ def _runtime_mode_changed(self, mode: str):
+ """called when runtime mode changes"""
gremlin.shared_state.runtime_mode = mode
if self._active_process_path:
verbose = gremlin.config.Configuration().verbose_mode_process
# syslog = logging.getLogger("system")
-
+
if verbose:
base_name = os.path.basename(self._active_process_path)
base_profile = os.path.basename(self.profile.profile_file)
- syslog.info(f"PROC: save runtime mode process: [{base_name}] mode [{mode}] profile [{base_profile}]")
+ syslog.info(
+ f"PROC: save runtime mode process: [{base_name}] mode [{mode}] profile [{base_profile}]"
+ )
self._process_runtime_map[self._active_process_path] = mode
# save to JSON as well
self.profile.set_last_runtime_mode(mode)
self._update_mode_status_bar()
-
def _sanitize_profile(self, profile_data):
"""Validates a profile file before actually loading it.
@@ -3846,7 +3985,6 @@ def _set_joystick_input_highlighting(self, is_enabled):
el = gremlin.event_handler.EventListener()
el.enable_highlight_changed.emit(is_enabled)
-
def _set_joystick_input_buttons_highlighting(self, is_enabled):
"""Enables / disables the highlighting of the current input button when used.
@@ -3863,12 +4001,10 @@ def _set_joystick_input_axis_highlighting(self, is_enabled):
disabled otherwise
"""
eh = gremlin.event_handler.EventListener()
- eh.toggle_highlight.emit(None, is_enabled, None)
+ eh.toggle_highlight.emit(None, is_enabled, None)
@QtCore.Slot(object, object)
def _handle_highlight_state(self, autoswitch_state, axis_state, button_state):
-
-
if autoswitch_state is not None:
self.config.highlight_autoswitch = autoswitch_state
@@ -3876,7 +4012,7 @@ def _handle_highlight_state(self, autoswitch_state, axis_state, button_state):
self.config.highlight_input_axis = axis_state
if button_state is not None:
- self.config.highlight_input_buttons = button_state
+ self.config.highlight_input_buttons = button_state
# update status bar widgets
@@ -3889,63 +4025,65 @@ def _handle_highlight_state(self, autoswitch_state, axis_state, button_state):
self.status_bar_highlight_axis_widget.setIcon(icon)
with QtCore.QSignalBlocker(self.status_bar_highlight_button_widget):
- icon = self._icon_on if self.config.highlight_input_buttons else self._icon_off
+ icon = (
+ self._icon_on if self.config.highlight_input_buttons else self._icon_off
+ )
self.status_bar_highlight_button_widget.setIcon(icon)
-
-
-
@property
def is_button_highlighting(self) -> bool:
- ''' true if button highlighting is currently enabled '''
+ """true if button highlighting is currently enabled"""
if not self.config.highlight_enabled:
return False
if gremlin.shared_state.is_highlighting_suspended():
# skip if highlighting is currently suspended
return False
-
+
el = gremlin.event_handler.EventListener()
is_hotkey_autoswitch = self.config.highlight_hotkey_autoswitch
is_shifted = el.get_shifted_state() if is_hotkey_autoswitch else False
- return self.config.highlight_input_buttons or is_shifted or self._button_highlighting_enabled
-
+ return (
+ self.config.highlight_input_buttons
+ or is_shifted
+ or self._button_highlighting_enabled
+ )
+
@property
def is_axis_highlighting(self) -> bool:
- ''' true if button highlighting is currently enabled '''
+ """true if button highlighting is currently enabled"""
if not self.config.highlight_enabled:
return False
if gremlin.shared_state.is_highlighting_suspended():
# skip if highlighting is currently suspended
return False
-
-
+
el = gremlin.event_handler.EventListener()
is_hotkey_autoswitch = self.config.highlight_hotkey_autoswitch
is_control = el.get_shifted_state() if is_hotkey_autoswitch else False
- return self.config.highlight_input_axis or is_control or self._axis_highlighting_enabled
-
+ return (
+ self.config.highlight_input_axis
+ or is_control
+ or self._axis_highlighting_enabled
+ )
+
@property
def is_autoswitch_highlighting(self) -> bool:
- ''' true if tab switch highlighting is enabled '''
+ """true if tab switch highlighting is enabled"""
if gremlin.shared_state.is_highlighting_suspended():
# skip if highlighting is currently suspended
return False
-
+
return self.config.highlight_autoswitch
def push_highlighting(self):
- ''' disables the highlighting of devices '''
+ """disables the highlighting of devices"""
gremlin.shared_state.push_suspend_highlighting()
-
- def pop_highlighting(self, reset = False):
- ''' enables the highlighting of devices '''
+ def pop_highlighting(self, reset=False):
+ """enables the highlighting of devices"""
gremlin.shared_state.pop_suspend_highlighting(reset)
-
-
-
- def _should_process_input(self, event, widget, buttons_only = False):
+ def _should_process_input(self, event, widget, buttons_only=False):
"""Returns True when to process and input, False otherwise.
This enforces a certain downtime between subsequent inputs
@@ -3964,16 +4102,22 @@ def _should_process_input(self, event, widget, buttons_only = False):
if is_running:
return False
-
# minimum deviation to look for for an axis
deviation = self._joystick_axis_highlight_deviation
-
- if buttons_only and event.event_type == InputType.JoystickAxis and not self.input_axis_override:
+ if (
+ buttons_only
+ and event.event_type == InputType.JoystickAxis
+ and not self.input_axis_override
+ ):
# ignore axis moves if button only mode
return False
- if self.input_axis_override and self.input_axis_only_override and event.event_type == InputType.JoystickButton:
+ if (
+ self.input_axis_override
+ and self.input_axis_only_override
+ and event.event_type == InputType.JoystickButton
+ ):
# exclusive axis mode - ignore buttons
return False
@@ -3982,13 +4126,17 @@ def _should_process_input(self, event, widget, buttons_only = False):
if data:
# if event.event_type == InputType.JoystickButton:
# pass
- if data.input_type == event.event_type and data.input_id == event.identifier:
+ if (
+ data.input_type == event.event_type
+ and data.input_id == event.identifier
+ ):
return False
-
-
process_input = False
- is_new_device = self._last_input_event is None or self._last_input_event.hardwareKey != event.hardwareKey
+ is_new_device = (
+ self._last_input_event is None
+ or self._last_input_event.hardwareKey != event.hardwareKey
+ )
if event.event_type == InputType.JoystickAxis:
# only worry about axis deviation delta if it's an axis
@@ -3996,7 +4144,11 @@ def _should_process_input(self, event, widget, buttons_only = False):
if buttons_only and not self.input_axis_override:
return False
- process_input = gremlin.input_devices.JoystickInputSignificant().should_process(event, deviation)
+ process_input = (
+ gremlin.input_devices.JoystickInputSignificant().should_process(
+ event, deviation
+ )
+ )
self._input_delay = 1
@@ -4005,7 +4157,6 @@ def _should_process_input(self, event, widget, buttons_only = False):
# delay not occured yet
process_input = False
-
else:
process_input = is_new_device
self._input_delay = 0.25
@@ -4025,11 +4176,9 @@ def _update_statusbar_repeater(self, text):
:param text the text to display
"""
- self.status_bar_repeater_widget.setText(
- "Repeater: {}".format(text)
- )
+ self.status_bar_repeater_widget.setText("Repeater: {}".format(text))
- def _update_window_title(self, title = None):
+ def _update_window_title(self, title=None):
"""Updates the window title to include the current profile."""
if title is None:
profile_fname = None
@@ -4043,7 +4192,6 @@ def _update_window_title(self, title = None):
self.setWindowTitle(title)
-
def configure_logger(config):
"""Creates a new logger instance.
@@ -4055,8 +4203,8 @@ def configure_logger(config):
try:
if os.path.isfile(log_file):
os.unlink(log_file)
- except:
- pass
+ except Exception as e:
+ print(f"Unable to delete log file {log_file}:\n{e}")
logger = logging.getLogger(config["name"])
logger.setLevel(config["level"])
handler = logging.FileHandler(config["logfile"])
@@ -4087,7 +4235,6 @@ def exception_hook(exception_type, value, trace):
gremlin.util.display_error(msg)
-
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
@@ -4095,55 +4242,58 @@ def exception_hook(exception_type, value, trace):
help="Path to the profile to load on startup",
)
parser.add_argument(
- "--enable",
- help="Enable Joystick Gremlin upon launch",
- action="store_true"
+ "--enable", help="Enable Joystick Gremlin upon launch", action="store_true"
)
parser.add_argument(
"--start-minimized",
help="Start Joystick Gremlin minimized",
- action="store_true"
+ action="store_true",
)
args = parser.parse_args()
gremlin.shared_state.ui_ready = False
- # Configure logging for system and user events
- configure_logger({
- "name": "system",
- "level": logging.DEBUG,
- "logfile": os.path.join(gremlin.util.userprofile_path(), "system.log"),
- "format": "%(asctime)s %(levelname)10s %(message)s"
- })
- configure_logger({
- "name": "user",
- "level": logging.DEBUG,
- "logfile": os.path.join(gremlin.util.userprofile_path(), "user.log"),
- "format": "%(asctime)s %(message)s"
- })
+ # Configure logging for system and user events
+ configure_logger(
+ {
+ "name": "system",
+ "level": logging.DEBUG,
+ "logfile": os.path.join(gremlin.util.userprofile_path(), "system.log"),
+ "format": "%(asctime)s %(levelname)10s %(message)s",
+ }
+ )
+ configure_logger(
+ {
+ "name": "user",
+ "level": logging.DEBUG,
+ "logfile": os.path.join(gremlin.util.userprofile_path(), "user.log"),
+ "format": "%(asctime)s %(message)s",
+ }
+ )
# Path manging to ensure Gremlin starts independent of the CWD
sys.path.insert(0, gremlin.util.userprofile_path())
gremlin.util.setup_userprofile()
# Fix some dumb Qt bugs
- QtWidgets.QApplication.addLibraryPath(os.path.join(
- os.path.dirname(PySide6.__file__),
- "plugins"
- ))
-
-
+ QtWidgets.QApplication.addLibraryPath(
+ os.path.join(os.path.dirname(PySide6.__file__), "plugins")
+ )
# syslog = logging.getLogger("system")
- syslog.info(F"Joystick Gremlin Ex version {Version().version} (P{gremlin.util.getPythonVersion()})")
+ syslog.info(
+ f"Joystick Gremlin Ex version {Version().version} (P{gremlin.util.getPythonVersion()})"
+ )
# Initialize the vjoy interface
from vjoy.vjoy_interface import VJoyInterface
+
VJoyInterface.initialize()
# Initialize the direct input interface class
from dinput import DILL
+
DILL.init()
DILL.initialize_capi()
syslog.info(f"Found DirectInput Interface version {DILL.version}")
@@ -4156,22 +4306,21 @@ def exception_hook(exception_type, value, trace):
# Initialize HidGuardian before we let SDL grab joystick data
import gremlin.hid_guardian
+
hg = gremlin.hid_guardian.HidGuardian()
hg.add_process(os.getpid())
# Create user interface
- app_id = u"joystick.gremlinex"
+ app_id = "joystick.gremlinex"
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(app_id)
-
# disable dark mode for now while we sort icons in a future version
-
+
theme = gremlin.config.Configuration().theme
match theme:
case "auto":
gremlin.shared_state.is_dark_theme = gremlin.ui.theme.theme() == "Dark"
app = QtWidgets.QApplication(sys.argv)
-
case "light":
os.environ["QT_QPA_PLATFORM"] = "windows:darkmode=0"
@@ -4188,32 +4337,26 @@ def exception_hook(exception_type, value, trace):
app.setStyle("Fusion")
app.setStyleSheet(gremlin.ui.ui_common.Color.cssApplication())
-
-
- #gremlin.shared_state.is_dark_theme = gremlin.ui.theme.theme() == "Dark"
- #app.setStyle("Fusion")
- #app.setStyle("windowsvista")
-
-
+ # gremlin.shared_state.is_dark_theme = gremlin.ui.theme.theme() == "Dark"
+ # app.setStyle("Fusion")
+ # app.setStyle("windowsvista")
# for now force localization to use US English until we have proper localization support
locale = QtCore.QLocale("UnitedStates")
QtCore.QLocale.setDefault(locale)
-
app.setWindowIcon(load_icon("gfx/icon.png"))
app.setApplicationDisplayName(APPLICATION_NAME + " " + APPLICATION_VERSION)
app.setApplicationVersion(APPLICATION_VERSION)
# no longer needed in QT6
- #app.setAttribute(QtCore.Qt.ApplicationAttribute.AA_EnableHighDpiScaling)
+ # app.setAttribute(QtCore.Qt.ApplicationAttribute.AA_EnableHighDpiScaling)
# handle windows themes better
- app.setStyle('Fusion')
+ app.setStyle("Fusion")
# Ensure joystick devices are correctly setup
dinput.DILL.init()
time.sleep(0.25)
-
# check for gamepad availability via VIGEM
if gremlin.gamepad_handling.gamepadAvailable():
@@ -4226,7 +4369,13 @@ def exception_hook(exception_type, value, trace):
# and terminate GremlinEx
try:
syslog.info("Checking vJoy installation")
- vjoy_count = len([dev for dev in gremlin.joystick_handling.joystick_devices() if dev.is_virtual])
+ vjoy_count = len(
+ [
+ dev
+ for dev in gremlin.joystick_handling.joystick_devices()
+ if dev.is_virtual
+ ]
+ )
vjoy_working = vjoy_count != 0
syslog.info(f"\tFound {vjoy_count} vjoy device(s)")
@@ -4235,22 +4384,19 @@ def exception_hook(exception_type, value, trace):
if not vjoy_working:
msg = "No configured VJOY devices were found. VJOY output will be disabled. This is normal if VJOY is not installed or not configured."
syslog.warning(msg)
- #gremlin.ui.ui_common.MessageBox("Error Scanning Devices", msg)
+ # gremlin.ui.ui_common.MessageBox("Error Scanning Devices", msg)
# raise gremlin.error.GremlinError(msg)
except (gremlin.error.GremlinError, dinput.DILLError) as e:
error_display = QtWidgets.QMessageBox(
- QtWidgets.QMessageBox.Critical,
- "Error",
- e.value,
- QtWidgets.QMessageBox.Ok
+ QtWidgets.QMessageBox.Critical, "Error", e.value, QtWidgets.QMessageBox.Ok
)
error_display.show()
app.exec_()
gremlin.joystick_handling.VJoyProxy.reset()
el = gremlin.event_handler.EventListener()
- el.terminate() # terminates and sends the relevant shutdown triggers
+ el.terminate() # terminates and sends the relevant shutdown triggers
sys.exit(0)
gremlin.shared_state.reload_device_map()
@@ -4276,17 +4422,15 @@ def exception_hook(exception_type, value, trace):
if args.start_minimized:
ui.setHidden(True)
-
# state monitoring
- profile_state_monitor = gremlin.shared_state.ProfileStateMonitor()
+ profile_state_monitor = gremlin.shared_state.ProfileStateMonitor()
# automatic process monitoring check
-
+
pmgr = gremlin.process_monitor.ProcessMonitor()
el = gremlin.event_handler.EventListener()
el.process_monitor_changed.emit()
-
ec = gremlin.execution_graph.ExecutionContext()
gremlin.shared_state.char_width = gremlin.ui.ui_common.get_text_width("M")
@@ -4297,24 +4441,20 @@ def exception_hook(exception_type, value, trace):
el.ui_ready.emit()
-
# generate icons if needed
- #_icon_generator = gremlin.ui.ui_common.IconGenerator()
+ # _icon_generator = gremlin.ui.ui_common.IconGenerator()
try:
app.exec()
- except Exception as err:
+ except Exception:
syslog.error(traceback.format_exc())
syslog.info("GremlinEx UI terminated")
-
-
# Terminate potentially running EventListener loop
gremlin.joystick_handling.VJoyProxy.reset()
el = gremlin.event_handler.EventListener()
- el.terminate() # terminates and sends the relevant shutdown triggers
-
+ el.terminate() # terminates and sends the relevant shutdown triggers
if vjoy_working:
# Properly terminate the runner instance should it be running
@@ -4323,9 +4463,9 @@ def exception_hook(exception_type, value, trace):
# Relinquish control over all VJoy devices used
gremlin.joystick_handling.VJoyProxy.reset()
- #hg.remove_process(os.getpid())
+ # hg.remove_process(os.getpid())
syslog.info("Terminating GremlinEx")
-
+
gc.collect()
- sys.exit(0)
\ No newline at end of file
+ sys.exit(0)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..f5f7a8df
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,29 @@
+[project]
+name = "joystickgremlinex-develop"
+version = "0.1.0"
+description = "Add your description here"
+readme = "README.md"
+requires-python = ">=3.12"
+dependencies = [
+ "anytree",
+ "cx-freeze==7.1.1",
+ "dill==0.3.8",
+ "hardware==0.31.0",
+ "homeassistant-api>=5.0.0",
+ "lxml>=5.3.1",
+ "mido==1.3.2",
+ "mock==5.1.0",
+ "msgpack>=1.1.0",
+ "pymidi",
+ "pyside6>=6.8.2.1",
+ "pytest==8.2.0",
+ "python-decouple>=3.8",
+ "python-rtmidi",
+ "pyttsx3",
+ "pywin32==306",
+ "qtawesome>=1.4.0",
+ "reportlab==4.1.0",
+]
+
+[tool.ruff]
+exclude = ["test/"]
diff --git a/resources.py b/resources.py
index abf4ce44..541d9f0c 100644
--- a/resources.py
+++ b/resources.py
@@ -6,6 +6,7 @@
from PySide6 import QtCore
import logging
+
syslog = logging.getLogger("system")
qt_resource_data = b"\
@@ -15986,10 +15987,17 @@
\x00\x00\x01Y\x00\xca` \
"
+
def qInitResources():
- QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+ QtCore.qRegisterResourceData(
+ 0x03, qt_resource_struct, qt_resource_name, qt_resource_data
+ )
+
def qCleanupResources():
- QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+ QtCore.qUnregisterResourceData(
+ 0x03, qt_resource_struct, qt_resource_name, qt_resource_data
+ )
+
qInitResources()
diff --git a/scripts/configuration.py b/scripts/configuration.py
index b79f38b5..8f1b1c2c 100644
--- a/scripts/configuration.py
+++ b/scripts/configuration.py
@@ -3,7 +3,7 @@
import gremlin
-'''
+"""
2019-03-16 13:48:37 DEBUG Added: name=T-Rudder windows_id=11 hardware_id=72332921
2019-03-16 13:48:37 DEBUG Added: name=vJoy Device windows_id=10 hardware_id=305446573
2019-03-16 13:48:37 DEBUG Added: name=vJoy Device windows_id=9 hardware_id=305446573
@@ -30,9 +30,8 @@
2019-07-07 14:37:32 DEBUG Added: name=vJoy Device guid={2D3260A0-6FA6-11E7-8002-444553540000}
2019-07-07 14:37:32 DEBUG Added: name=vJoy Device guid={705DC1A0-C170-11E7-8003-444553540000}
2019-07-07 14:37:32 DEBUG Added: name=vJoy Device guid={8245C100-2705-11E9-8002-444553540000}
-'''
+"""
-
TM_STICK_ID = 1
TM_STICK_HWID = 72287234
@@ -123,9 +122,9 @@
MODE_A = "A"
-''' REFERENCE AXES '''
+""" REFERENCE AXES """
-'''
+"""
_AxisNames_to_enum_lookup = {
1 "X Axis": AxisNames.X,
2 "Y Axis": AxisNames.Y,
@@ -136,7 +135,7 @@
7 "Slider": AxisNames.SLIDER,
8 "Dial": AxisNames.DIAL
}
-'''
+"""
AXIS_X = 1
AXIS_Y = 2
@@ -190,61 +189,167 @@
# plugin decorator definitions
# decorators for mode A
-LEFT_VPC_Stick_WarBRD_A = gremlin.input_devices.JoystickDecorator(LEFT_VPC_Stick_WarBRD_NAME, LEFT_VPC_Stick_WarBRD_GUID, "A")
+LEFT_VPC_Stick_WarBRD_A = gremlin.input_devices.JoystickDecorator(
+ LEFT_VPC_Stick_WarBRD_NAME, LEFT_VPC_Stick_WarBRD_GUID, "A"
+)
T_Rudder_A = gremlin.input_devices.JoystickDecorator(T_Rudder_NAME, T_Rudder_GUID, "A")
-RaspberryPi_Pico_A = gremlin.input_devices.JoystickDecorator(RaspberryPi_Pico_NAME, RaspberryPi_Pico_GUID, "A")
-Alpha_Flight_Controls_A = gremlin.input_devices.JoystickDecorator(Alpha_Flight_Controls_NAME, Alpha_Flight_Controls_GUID, "A")
-Bravo_Throttle_Quadrant_A = gremlin.input_devices.JoystickDecorator(Bravo_Throttle_Quadrant_NAME, Bravo_Throttle_Quadrant_GUID, "A")
-RIGHT_VPC_Stick_WarBRD_A = gremlin.input_devices.JoystickDecorator(RIGHT_VPC_Stick_WarBRD_NAME, RIGHT_VPC_Stick_WarBRD_GUID, "A")
-Throttle_HOTAS_Warthog_A = gremlin.input_devices.JoystickDecorator(Throttle_HOTAS_Warthog_NAME, Throttle_HOTAS_Warthog_GUID, "A")
-DSD_Flight_Series_Button_Controller_A = gremlin.input_devices.JoystickDecorator(DSD_Flight_Series_Button_Controller_NAME, DSD_Flight_Series_Button_Controller_GUID, "A")
+RaspberryPi_Pico_A = gremlin.input_devices.JoystickDecorator(
+ RaspberryPi_Pico_NAME, RaspberryPi_Pico_GUID, "A"
+)
+Alpha_Flight_Controls_A = gremlin.input_devices.JoystickDecorator(
+ Alpha_Flight_Controls_NAME, Alpha_Flight_Controls_GUID, "A"
+)
+Bravo_Throttle_Quadrant_A = gremlin.input_devices.JoystickDecorator(
+ Bravo_Throttle_Quadrant_NAME, Bravo_Throttle_Quadrant_GUID, "A"
+)
+RIGHT_VPC_Stick_WarBRD_A = gremlin.input_devices.JoystickDecorator(
+ RIGHT_VPC_Stick_WarBRD_NAME, RIGHT_VPC_Stick_WarBRD_GUID, "A"
+)
+Throttle_HOTAS_Warthog_A = gremlin.input_devices.JoystickDecorator(
+ Throttle_HOTAS_Warthog_NAME, Throttle_HOTAS_Warthog_GUID, "A"
+)
+DSD_Flight_Series_Button_Controller_A = gremlin.input_devices.JoystickDecorator(
+ DSD_Flight_Series_Button_Controller_NAME,
+ DSD_Flight_Series_Button_Controller_GUID,
+ "A",
+)
# decorators for mode Default
-RaspberryPi_Pico_Default = gremlin.input_devices.JoystickDecorator(RaspberryPi_Pico_NAME, RaspberryPi_Pico_GUID, "Default")
-LEFT_VPC_Stick_WarBRD_Default = gremlin.input_devices.JoystickDecorator(LEFT_VPC_Stick_WarBRD_NAME, LEFT_VPC_Stick_WarBRD_GUID, "Default")
-T_Rudder_Default = gremlin.input_devices.JoystickDecorator(T_Rudder_NAME, T_Rudder_GUID, "Default")
-DSD_Flight_Series_Button_Controller_Default = gremlin.input_devices.JoystickDecorator(DSD_Flight_Series_Button_Controller_NAME, DSD_Flight_Series_Button_Controller_GUID, "Default")
-RIGHT_VPC_Stick_WarBRD_Default = gremlin.input_devices.JoystickDecorator(RIGHT_VPC_Stick_WarBRD_NAME, RIGHT_VPC_Stick_WarBRD_GUID, "Default")
-Alpha_Flight_Controls_Default = gremlin.input_devices.JoystickDecorator(Alpha_Flight_Controls_NAME, Alpha_Flight_Controls_GUID, "Default")
-Throttle_HOTAS_Warthog_Default = gremlin.input_devices.JoystickDecorator(Throttle_HOTAS_Warthog_NAME, Throttle_HOTAS_Warthog_GUID, "Default")
-Bravo_Throttle_Quadrant_Default = gremlin.input_devices.JoystickDecorator(Bravo_Throttle_Quadrant_NAME, Bravo_Throttle_Quadrant_GUID, "Default")
+RaspberryPi_Pico_Default = gremlin.input_devices.JoystickDecorator(
+ RaspberryPi_Pico_NAME, RaspberryPi_Pico_GUID, "Default"
+)
+LEFT_VPC_Stick_WarBRD_Default = gremlin.input_devices.JoystickDecorator(
+ LEFT_VPC_Stick_WarBRD_NAME, LEFT_VPC_Stick_WarBRD_GUID, "Default"
+)
+T_Rudder_Default = gremlin.input_devices.JoystickDecorator(
+ T_Rudder_NAME, T_Rudder_GUID, "Default"
+)
+DSD_Flight_Series_Button_Controller_Default = gremlin.input_devices.JoystickDecorator(
+ DSD_Flight_Series_Button_Controller_NAME,
+ DSD_Flight_Series_Button_Controller_GUID,
+ "Default",
+)
+RIGHT_VPC_Stick_WarBRD_Default = gremlin.input_devices.JoystickDecorator(
+ RIGHT_VPC_Stick_WarBRD_NAME, RIGHT_VPC_Stick_WarBRD_GUID, "Default"
+)
+Alpha_Flight_Controls_Default = gremlin.input_devices.JoystickDecorator(
+ Alpha_Flight_Controls_NAME, Alpha_Flight_Controls_GUID, "Default"
+)
+Throttle_HOTAS_Warthog_Default = gremlin.input_devices.JoystickDecorator(
+ Throttle_HOTAS_Warthog_NAME, Throttle_HOTAS_Warthog_GUID, "Default"
+)
+Bravo_Throttle_Quadrant_Default = gremlin.input_devices.JoystickDecorator(
+ Bravo_Throttle_Quadrant_NAME, Bravo_Throttle_Quadrant_GUID, "Default"
+)
# decorators for mode mining
-T_Rudder_mining = gremlin.input_devices.JoystickDecorator(T_Rudder_NAME, T_Rudder_GUID, "mining")
-RIGHT_VPC_Stick_WarBRD_mining = gremlin.input_devices.JoystickDecorator(RIGHT_VPC_Stick_WarBRD_NAME, RIGHT_VPC_Stick_WarBRD_GUID, "mining")
-Alpha_Flight_Controls_mining = gremlin.input_devices.JoystickDecorator(Alpha_Flight_Controls_NAME, Alpha_Flight_Controls_GUID, "mining")
-RaspberryPi_Pico_mining = gremlin.input_devices.JoystickDecorator(RaspberryPi_Pico_NAME, RaspberryPi_Pico_GUID, "mining")
-Bravo_Throttle_Quadrant_mining = gremlin.input_devices.JoystickDecorator(Bravo_Throttle_Quadrant_NAME, Bravo_Throttle_Quadrant_GUID, "mining")
-Throttle_HOTAS_Warthog_mining = gremlin.input_devices.JoystickDecorator(Throttle_HOTAS_Warthog_NAME, Throttle_HOTAS_Warthog_GUID, "mining")
-LEFT_VPC_Stick_WarBRD_mining = gremlin.input_devices.JoystickDecorator(LEFT_VPC_Stick_WarBRD_NAME, LEFT_VPC_Stick_WarBRD_GUID, "mining")
-DSD_Flight_Series_Button_Controller_mining = gremlin.input_devices.JoystickDecorator(DSD_Flight_Series_Button_Controller_NAME, DSD_Flight_Series_Button_Controller_GUID, "mining")
+T_Rudder_mining = gremlin.input_devices.JoystickDecorator(
+ T_Rudder_NAME, T_Rudder_GUID, "mining"
+)
+RIGHT_VPC_Stick_WarBRD_mining = gremlin.input_devices.JoystickDecorator(
+ RIGHT_VPC_Stick_WarBRD_NAME, RIGHT_VPC_Stick_WarBRD_GUID, "mining"
+)
+Alpha_Flight_Controls_mining = gremlin.input_devices.JoystickDecorator(
+ Alpha_Flight_Controls_NAME, Alpha_Flight_Controls_GUID, "mining"
+)
+RaspberryPi_Pico_mining = gremlin.input_devices.JoystickDecorator(
+ RaspberryPi_Pico_NAME, RaspberryPi_Pico_GUID, "mining"
+)
+Bravo_Throttle_Quadrant_mining = gremlin.input_devices.JoystickDecorator(
+ Bravo_Throttle_Quadrant_NAME, Bravo_Throttle_Quadrant_GUID, "mining"
+)
+Throttle_HOTAS_Warthog_mining = gremlin.input_devices.JoystickDecorator(
+ Throttle_HOTAS_Warthog_NAME, Throttle_HOTAS_Warthog_GUID, "mining"
+)
+LEFT_VPC_Stick_WarBRD_mining = gremlin.input_devices.JoystickDecorator(
+ LEFT_VPC_Stick_WarBRD_NAME, LEFT_VPC_Stick_WarBRD_GUID, "mining"
+)
+DSD_Flight_Series_Button_Controller_mining = gremlin.input_devices.JoystickDecorator(
+ DSD_Flight_Series_Button_Controller_NAME,
+ DSD_Flight_Series_Button_Controller_GUID,
+ "mining",
+)
# decorators for mode missile
-Alpha_Flight_Controls_missile = gremlin.input_devices.JoystickDecorator(Alpha_Flight_Controls_NAME, Alpha_Flight_Controls_GUID, "missile")
-Throttle_HOTAS_Warthog_missile = gremlin.input_devices.JoystickDecorator(Throttle_HOTAS_Warthog_NAME, Throttle_HOTAS_Warthog_GUID, "missile")
-RaspberryPi_Pico_missile = gremlin.input_devices.JoystickDecorator(RaspberryPi_Pico_NAME, RaspberryPi_Pico_GUID, "missile")
-Bravo_Throttle_Quadrant_missile = gremlin.input_devices.JoystickDecorator(Bravo_Throttle_Quadrant_NAME, Bravo_Throttle_Quadrant_GUID, "missile")
-RIGHT_VPC_Stick_WarBRD_missile = gremlin.input_devices.JoystickDecorator(RIGHT_VPC_Stick_WarBRD_NAME, RIGHT_VPC_Stick_WarBRD_GUID, "missile")
-LEFT_VPC_Stick_WarBRD_missile = gremlin.input_devices.JoystickDecorator(LEFT_VPC_Stick_WarBRD_NAME, LEFT_VPC_Stick_WarBRD_GUID, "missile")
-T_Rudder_missile = gremlin.input_devices.JoystickDecorator(T_Rudder_NAME, T_Rudder_GUID, "missile")
-DSD_Flight_Series_Button_Controller_missile = gremlin.input_devices.JoystickDecorator(DSD_Flight_Series_Button_Controller_NAME, DSD_Flight_Series_Button_Controller_GUID, "missile")
+Alpha_Flight_Controls_missile = gremlin.input_devices.JoystickDecorator(
+ Alpha_Flight_Controls_NAME, Alpha_Flight_Controls_GUID, "missile"
+)
+Throttle_HOTAS_Warthog_missile = gremlin.input_devices.JoystickDecorator(
+ Throttle_HOTAS_Warthog_NAME, Throttle_HOTAS_Warthog_GUID, "missile"
+)
+RaspberryPi_Pico_missile = gremlin.input_devices.JoystickDecorator(
+ RaspberryPi_Pico_NAME, RaspberryPi_Pico_GUID, "missile"
+)
+Bravo_Throttle_Quadrant_missile = gremlin.input_devices.JoystickDecorator(
+ Bravo_Throttle_Quadrant_NAME, Bravo_Throttle_Quadrant_GUID, "missile"
+)
+RIGHT_VPC_Stick_WarBRD_missile = gremlin.input_devices.JoystickDecorator(
+ RIGHT_VPC_Stick_WarBRD_NAME, RIGHT_VPC_Stick_WarBRD_GUID, "missile"
+)
+LEFT_VPC_Stick_WarBRD_missile = gremlin.input_devices.JoystickDecorator(
+ LEFT_VPC_Stick_WarBRD_NAME, LEFT_VPC_Stick_WarBRD_GUID, "missile"
+)
+T_Rudder_missile = gremlin.input_devices.JoystickDecorator(
+ T_Rudder_NAME, T_Rudder_GUID, "missile"
+)
+DSD_Flight_Series_Button_Controller_missile = gremlin.input_devices.JoystickDecorator(
+ DSD_Flight_Series_Button_Controller_NAME,
+ DSD_Flight_Series_Button_Controller_GUID,
+ "missile",
+)
# decorators for mode scan
-RIGHT_VPC_Stick_WarBRD_scan = gremlin.input_devices.JoystickDecorator(RIGHT_VPC_Stick_WarBRD_NAME, RIGHT_VPC_Stick_WarBRD_GUID, "scan")
-RaspberryPi_Pico_scan = gremlin.input_devices.JoystickDecorator(RaspberryPi_Pico_NAME, RaspberryPi_Pico_GUID, "scan")
-T_Rudder_scan = gremlin.input_devices.JoystickDecorator(T_Rudder_NAME, T_Rudder_GUID, "scan")
-Alpha_Flight_Controls_scan = gremlin.input_devices.JoystickDecorator(Alpha_Flight_Controls_NAME, Alpha_Flight_Controls_GUID, "scan")
-Bravo_Throttle_Quadrant_scan = gremlin.input_devices.JoystickDecorator(Bravo_Throttle_Quadrant_NAME, Bravo_Throttle_Quadrant_GUID, "scan")
-LEFT_VPC_Stick_WarBRD_scan = gremlin.input_devices.JoystickDecorator(LEFT_VPC_Stick_WarBRD_NAME, LEFT_VPC_Stick_WarBRD_GUID, "scan")
-DSD_Flight_Series_Button_Controller_scan = gremlin.input_devices.JoystickDecorator(DSD_Flight_Series_Button_Controller_NAME, DSD_Flight_Series_Button_Controller_GUID, "scan")
-Throttle_HOTAS_Warthog_scan = gremlin.input_devices.JoystickDecorator(Throttle_HOTAS_Warthog_NAME, Throttle_HOTAS_Warthog_GUID, "scan")
+RIGHT_VPC_Stick_WarBRD_scan = gremlin.input_devices.JoystickDecorator(
+ RIGHT_VPC_Stick_WarBRD_NAME, RIGHT_VPC_Stick_WarBRD_GUID, "scan"
+)
+RaspberryPi_Pico_scan = gremlin.input_devices.JoystickDecorator(
+ RaspberryPi_Pico_NAME, RaspberryPi_Pico_GUID, "scan"
+)
+T_Rudder_scan = gremlin.input_devices.JoystickDecorator(
+ T_Rudder_NAME, T_Rudder_GUID, "scan"
+)
+Alpha_Flight_Controls_scan = gremlin.input_devices.JoystickDecorator(
+ Alpha_Flight_Controls_NAME, Alpha_Flight_Controls_GUID, "scan"
+)
+Bravo_Throttle_Quadrant_scan = gremlin.input_devices.JoystickDecorator(
+ Bravo_Throttle_Quadrant_NAME, Bravo_Throttle_Quadrant_GUID, "scan"
+)
+LEFT_VPC_Stick_WarBRD_scan = gremlin.input_devices.JoystickDecorator(
+ LEFT_VPC_Stick_WarBRD_NAME, LEFT_VPC_Stick_WarBRD_GUID, "scan"
+)
+DSD_Flight_Series_Button_Controller_scan = gremlin.input_devices.JoystickDecorator(
+ DSD_Flight_Series_Button_Controller_NAME,
+ DSD_Flight_Series_Button_Controller_GUID,
+ "scan",
+)
+Throttle_HOTAS_Warthog_scan = gremlin.input_devices.JoystickDecorator(
+ Throttle_HOTAS_Warthog_NAME, Throttle_HOTAS_Warthog_GUID, "scan"
+)
# decorators for mode view
-LEFT_VPC_Stick_WarBRD_view = gremlin.input_devices.JoystickDecorator(LEFT_VPC_Stick_WarBRD_NAME, LEFT_VPC_Stick_WarBRD_GUID, "view")
-DSD_Flight_Series_Button_Controller_view = gremlin.input_devices.JoystickDecorator(DSD_Flight_Series_Button_Controller_NAME, DSD_Flight_Series_Button_Controller_GUID, "view")
-T_Rudder_view = gremlin.input_devices.JoystickDecorator(T_Rudder_NAME, T_Rudder_GUID, "view")
-Throttle_HOTAS_Warthog_view = gremlin.input_devices.JoystickDecorator(Throttle_HOTAS_Warthog_NAME, Throttle_HOTAS_Warthog_GUID, "view")
-RaspberryPi_Pico_view = gremlin.input_devices.JoystickDecorator(RaspberryPi_Pico_NAME, RaspberryPi_Pico_GUID, "view")
-Bravo_Throttle_Quadrant_view = gremlin.input_devices.JoystickDecorator(Bravo_Throttle_Quadrant_NAME, Bravo_Throttle_Quadrant_GUID, "view")
-RIGHT_VPC_Stick_WarBRD_view = gremlin.input_devices.JoystickDecorator(RIGHT_VPC_Stick_WarBRD_NAME, RIGHT_VPC_Stick_WarBRD_GUID, "view")
-Alpha_Flight_Controls_view = gremlin.input_devices.JoystickDecorator(Alpha_Flight_Controls_NAME, Alpha_Flight_Controls_GUID, "view")
+LEFT_VPC_Stick_WarBRD_view = gremlin.input_devices.JoystickDecorator(
+ LEFT_VPC_Stick_WarBRD_NAME, LEFT_VPC_Stick_WarBRD_GUID, "view"
+)
+DSD_Flight_Series_Button_Controller_view = gremlin.input_devices.JoystickDecorator(
+ DSD_Flight_Series_Button_Controller_NAME,
+ DSD_Flight_Series_Button_Controller_GUID,
+ "view",
+)
+T_Rudder_view = gremlin.input_devices.JoystickDecorator(
+ T_Rudder_NAME, T_Rudder_GUID, "view"
+)
+Throttle_HOTAS_Warthog_view = gremlin.input_devices.JoystickDecorator(
+ Throttle_HOTAS_Warthog_NAME, Throttle_HOTAS_Warthog_GUID, "view"
+)
+RaspberryPi_Pico_view = gremlin.input_devices.JoystickDecorator(
+ RaspberryPi_Pico_NAME, RaspberryPi_Pico_GUID, "view"
+)
+Bravo_Throttle_Quadrant_view = gremlin.input_devices.JoystickDecorator(
+ Bravo_Throttle_Quadrant_NAME, Bravo_Throttle_Quadrant_GUID, "view"
+)
+RIGHT_VPC_Stick_WarBRD_view = gremlin.input_devices.JoystickDecorator(
+ RIGHT_VPC_Stick_WarBRD_NAME, RIGHT_VPC_Stick_WarBRD_GUID, "view"
+)
+Alpha_Flight_Controls_view = gremlin.input_devices.JoystickDecorator(
+ Alpha_Flight_Controls_NAME, Alpha_Flight_Controls_GUID, "view"
+)
diff --git a/scripts/msfs.py b/scripts/msfs.py
index 9c1a5404..6d9d89ca 100644
--- a/scripts/msfs.py
+++ b/scripts/msfs.py
@@ -1,31 +1,43 @@
import gremlin
-from gremlin.user_plugin import PhysicalInputVariable
-import time
-import uuid
-import atexit
from gremlin.spline import CubicSpline
-from gremlin.input_devices import Keyboard
-from vjoy.vjoy import AxisName
-from configuration import * # load constants defining the devices connected to this PC
-from util import *
-from hardware import *
-from gremlin.macro import Macro, MacroManager
+#from configuration import * # load constants defining the devices connected to this PC
+#from util import *
+from gremlin.macro import Macro
from gremlin.util import parse_guid
+from scripts.configuration import (
+ ALPHA_NAME,
+ ALPHA_GUID,
+ MODE_ALL,
+ BRAVO_GUID,
+ BRAVO_NAME,
+ MODE_DEFAULT,
+ VPC_LEFT_GUID,
+ VPC_LEFT_NAME,
+ VJOY_INPUT_NAME,
+ VJOY_INPUT_GUID,
+ MFG_Crosswind_V2_3_NAME,
+ MFG_Crosswind_V2_3_GUID,
+)
+from util import register_handler, unregister_handler, fireButton, say, RunMacro
gremlin.util.log("Custom MSFS module enabled")
-from util import register_handler, unregister_handler
-
-
-
MODE_CHART = "chart"
-alpha = gremlin.input_devices.JoystickDecorator(ALPHA_NAME, ALPHA_GUID , MODE_ALL )
-bravo = gremlin.input_devices.JoystickDecorator(BRAVO_NAME,BRAVO_GUID , MODE_ALL )
-left_vpc = gremlin.input_devices.JoystickDecorator(VPC_LEFT_NAME, VPC_LEFT_GUID , MODE_DEFAULT )
-left_vpc_chart = gremlin.input_devices.JoystickDecorator(VPC_LEFT_NAME, VPC_LEFT_GUID , MODE_CHART )
-vjoy_input = gremlin.input_devices.JoystickDecorator(VJOY_INPUT_NAME, VJOY_INPUT_GUID , MODE_DEFAULT )
-t_rudder = gremlin.input_devices.JoystickDecorator(MFG_Crosswind_V2_3_NAME, MFG_Crosswind_V2_3_GUID, MODE_DEFAULT )
+alpha = gremlin.input_devices.JoystickDecorator(ALPHA_NAME, ALPHA_GUID, MODE_ALL)
+bravo = gremlin.input_devices.JoystickDecorator(BRAVO_NAME, BRAVO_GUID, MODE_ALL)
+left_vpc = gremlin.input_devices.JoystickDecorator(
+ VPC_LEFT_NAME, VPC_LEFT_GUID, MODE_DEFAULT
+)
+left_vpc_chart = gremlin.input_devices.JoystickDecorator(
+ VPC_LEFT_NAME, VPC_LEFT_GUID, MODE_CHART
+)
+vjoy_input = gremlin.input_devices.JoystickDecorator(
+ VJOY_INPUT_NAME, VJOY_INPUT_GUID, MODE_DEFAULT
+)
+t_rudder = gremlin.input_devices.JoystickDecorator(
+ MFG_Crosswind_V2_3_NAME, MFG_Crosswind_V2_3_GUID, MODE_DEFAULT
+)
# gremlin.util.log(f"Bravo type: {type(bravo)}")
@@ -36,8 +48,6 @@
# ''' implements a PID for rudder control '''
-
-
# def __init__(self, setpoint, vjoy_id, vjoy_axis):
# self._vjoy_id = vjoy_id
# self._vjoy_axis = vjoy_axis
@@ -46,8 +56,7 @@
# self._pid = PID(self.read, self.update, setpoint + 1, 1, 0.05, 0.25, True)
# self._pid.output_limits = (0, 2.0)
# self._pid.auto = True
-
-
+
# def update(self, value):
# ''' updates the output value '''
@@ -64,16 +73,16 @@
# def read(self):
# ''' gets the current value '''
# return self._value
-
+
# def tick(self):
# ''' updates the PID on the tick value'''
# self._pid.compute()
-
+
# @property
# def setpoint(self):
-# return self._pid.setpoint
-
+# return self._pid.setpoint
+
# @setpoint.setter
# def setpoint(self, value):
# ''' setpoint of the PID - input value is expected to be -1 to +1'''
@@ -93,13 +102,12 @@
# _rudder_pid.setpoint = event.value
# # vjoy[1].axis(3).value = event.value
# #_rudder_pid.setpoint = event.value
-
f1_macro = Macro()
-f1_macro.press('F1')
+f1_macro.press("F1")
f1_macro.pause(0.5)
-f1_macro.release('F1')
+f1_macro.release("F1")
joy = gremlin.input_devices.JoystickProxy()
@@ -107,23 +115,22 @@
vpc_left_raw = joy[parse_guid(VPC_LEFT_GUID)]
-
-
-
bravo_raw = joy[parse_guid(BRAVO_GUID)]
-RUDDER_FACTOR = 0.75 # scale value when scaling the rudder
-rudder_scaled = False # don't start with scaled rudder - left stick trigger toggles between the modes
-
-default_curve = CubicSpline([
- (-1.00, -1.00),
- (-0.75, -0.50),
- (-0.25, -0.05),
- ( 0.00, 0.00),
- ( 0.25, 0.05),
- ( 0.75, 0.50),
- ( 1.00, 1.00),
-])
+RUDDER_FACTOR = 0.75 # scale value when scaling the rudder
+rudder_scaled = False # don't start with scaled rudder - left stick trigger toggles between the modes
+
+default_curve = CubicSpline(
+ [
+ (-1.00, -1.00),
+ (-0.75, -0.50),
+ (-0.25, -0.05),
+ (0.00, 0.00),
+ (0.25, 0.05),
+ (0.75, 0.50),
+ (1.00, 1.00),
+ ]
+)
# airbus throttle gates
GATE_NONE = 0
@@ -145,8 +152,8 @@
TOGA_MIN = 0.9
TOGA_MAX = 1.0
-AXIS_CLB = (CLB_MIN + CLB_MAX)/2
-AXIS_FLEX = (FLEX_MIN + FLEX_MAX)/2
+AXIS_CLB = (CLB_MIN + CLB_MAX) / 2
+AXIS_FLEX = (FLEX_MIN + FLEX_MAX) / 2
AXIS_TOGA = 1.0
AXIS_IDLE = -1
AXIS_REV = -1
@@ -175,7 +182,7 @@
AB_TOGA_2 = 59
-active_curve = default_curve
+active_curve = default_curve
airbus_mode = False
airbus_gate = GATE_NONE
last_gate = GATE_NONE
@@ -186,27 +193,31 @@
@t_rudder.axis(3)
-def rudder(event, vjoy):
- ''' rudder axis = Z axis'''
+def rudder(event, vjoy):
+ """rudder axis = Z axis"""
vjoy[1].axis(3).value = active_curve(event.value)
+
# throttle 1 min trigger
@bravo.button(25)
def axis_1_toggle(event, vjoy):
fireButton(event, vjoy, 2, 16, 17, True, PULSE_DURATION)
+
# throttle 2 min trigger
@bravo.button(26)
def axis_2_toggle(event, vjoy):
# fireButton(event, vjoy, 2, 18, 19, True, PULSE_DURATION)
vjoy[1].button(17).is_pressed = event.is_pressed
+
# prop 1 min trigger
@bravo.button(27)
def axis_3_toggle(event, vjoy):
- #fireButton(event, vjoy, 2, 20, 21, True, PULSE_DURATION)
+ # fireButton(event, vjoy, 2, 20, 21, True, PULSE_DURATION)
vjoy[1].button(18).is_pressed = event.is_pressed
+
# prop 2 min trigger
@bravo.button(28)
def axis_4_toggle(event, vjoy):
@@ -220,7 +231,6 @@ def spoiler_axis(event, vjoy):
vjoy[2].button(2).is_pressed = event.value < -0.9
-
def update_reversers(vjoy):
global _rev_1_engaged, _rev_2_engaged
if _rev_1_engaged:
@@ -238,10 +248,6 @@ def update_reversers(vjoy):
vjoy[1].button(97).is_pressed = True
-
-
-
-
last_x_value = 0
last_y_value = 0
AXIS_THRESHOLD = 0.5
@@ -251,7 +257,7 @@ def update_reversers(vjoy):
VJOY_INDEX = 2
HAT_BUTTON = 5
hat_state = {}
-'''
+"""
Gremlin hat directions:
(x, y): for x and y
@@ -271,25 +277,28 @@ def update_reversers(vjoy):
(-1, -1): "South West",
(-1, 0): "West",
(-1, 1): "North West",
-'''
+"""
+
def ensure_hat():
- ''' builds the dictionary of hats indexed by the hat index '''
+ """builds the dictionary of hats indexed by the hat index"""
global hat_state
if len(hat_state.keys()) == 0:
for index in range(4):
- hat_state[index] = [0,0]
+ hat_state[index] = [0, 0]
+
def update_state(vjoy, vjoy_index, hat_index, state):
- ''' sets the hat state on a specific vjoy device and hat index '''
+ """sets the hat state on a specific vjoy device and hat index"""
device = vjoy[vjoy_index]
- device.hat(hat_index).direction = tuple(state[hat_index])
+ device.hat(hat_index).direction = tuple(state[hat_index])
# device.button(HAT_BUTTON).is_pressed = state[0] == 1
# device.button(HAT_BUTTON+1).is_pressed = state[0] == -1
# device.button(HAT_BUTTON+2).is_pressed = state[1] == 1
# device.button(HAT_BUTTON+3).is_pressed = state[1] == -1
+
# x rotation
@left_vpc_chart.axis(4)
def x_axis(event, vjoy):
@@ -297,17 +306,18 @@ def x_axis(event, vjoy):
value = event.value
if last_x_value != value:
last_x_value = value
- #gremlin.util.log(f"y value: {value}")
+ # gremlin.util.log(f"y value: {value}")
if value < -AXIS_THRESHOLD:
hat_state[HAT_INDEX_THUMB][0] = -1
- #gremlin.util.log(f"left")
+ # gremlin.util.log(f"left")
elif value > AXIS_THRESHOLD:
hat_state[HAT_INDEX_THUMB][0] = 1
- #gremlin.util.log(f"right")
+ # gremlin.util.log(f"right")
else:
hat_state[HAT_INDEX_THUMB][0] = 0
- #gremlin.util.log(f"center")
- update_state(vjoy,VJOY_INDEX,HAT_INDEX_THUMB,hat_state)
+ # gremlin.util.log(f"center")
+ update_state(vjoy, VJOY_INDEX, HAT_INDEX_THUMB, hat_state)
+
# y rotation
@left_vpc_chart.axis(5)
@@ -316,17 +326,18 @@ def y_axis(event, vjoy):
value = event.value
if last_y_value != value:
last_y_value = value
- #gremlin.util.log(f"y value: {value}")
+ # gremlin.util.log(f"y value: {value}")
if value < -AXIS_THRESHOLD:
- #gremlin.util.log(f"up")
+ # gremlin.util.log(f"up")
hat_state[HAT_INDEX_THUMB][1] = 1
elif value > AXIS_THRESHOLD:
hat_state[HAT_INDEX_THUMB][1] = -1
- #gremlin.util.log(f"down")
+ # gremlin.util.log(f"down")
else:
hat_state[HAT_INDEX_THUMB][1] = 0
- #gremlin.util.log(f"middle")
- update_state(vjoy,VJOY_INDEX,HAT_INDEX_THUMB,hat_state)
+ # gremlin.util.log(f"middle")
+ update_state(vjoy, VJOY_INDEX, HAT_INDEX_THUMB, hat_state)
+
# hat 1 up
@left_vpc.button(14)
@@ -334,31 +345,34 @@ def hat_1_up(event, vjoy):
global hat_state
value = 1 if event.is_pressed else 0
hat_state[HAT_INDEX_ONE][1] = value
- update_state(vjoy,VJOY_INDEX,HAT_INDEX_ONE,hat_state)
+ update_state(vjoy, VJOY_INDEX, HAT_INDEX_ONE, hat_state)
+
# hat 1 right
@left_vpc.button(15)
-def hat_1_up(event, vjoy):
+def hat_1_up(event, vjoy): # noqa: F811
global hat_state
value = 1 if event.is_pressed else 0
hat_state[HAT_INDEX_ONE][0] = value
- update_state(vjoy,VJOY_INDEX,HAT_INDEX_ONE,hat_state)
+ update_state(vjoy, VJOY_INDEX, HAT_INDEX_ONE, hat_state)
+
# hat 1 down
@left_vpc.button(16)
-def hat_1_up(event, vjoy):
+def hat_1_up(event, vjoy): # noqa: F811
global hat_state
value = -1 if event.is_pressed else 0
hat_state[HAT_INDEX_ONE][1] = value
- update_state(vjoy,VJOY_INDEX,HAT_INDEX_ONE,hat_state)
+ update_state(vjoy, VJOY_INDEX, HAT_INDEX_ONE, hat_state)
+
# hat 1 left
@left_vpc.button(17)
-def hat_1_up(event, vjoy):
+def hat_1_up(event, vjoy): # noqa: F811
global hat_state
value = -1 if event.is_pressed else 0
hat_state[HAT_INDEX_ONE][0] = value
- update_state(vjoy,VJOY_INDEX,HAT_INDEX_ONE,hat_state)
+ update_state(vjoy, VJOY_INDEX, HAT_INDEX_ONE, hat_state)
# make sure hats are setup
@@ -370,8 +384,6 @@ def hat_1_up(event, vjoy):
_rev_2_bottom = False
-
-
# BRAVO AXIS from left to right
# 1 spoiler - bravo axis 2
# 2 throttle 1 - bravo axis 1
@@ -381,10 +393,9 @@ def hat_1_up(event, vjoy):
# 6 flaps - bravo axis 3
-
def get_gate_value(value):
- ''' returns the axis value based on airbus gates '''
-
+ """returns the axis value based on airbus gates"""
+
gate = GATE_NONE
if value >= CLB_MIN and value <= CLB_MAX:
value = AXIS_CLB
@@ -410,7 +421,7 @@ def get_gate_value(value):
@bravo.button(35)
def set_airbus_mode(event):
- ''' left rocker button above throttles on bravo '''
+ """left rocker button above throttles on bravo"""
global airbus_mode
airbus_mode = event.is_pressed
if airbus_mode:
@@ -418,8 +429,10 @@ def set_airbus_mode(event):
else:
say("airbus mode disabled")
+
def osc_callback(cmd, value):
- ''' OSC message handler '''
+ """OSC message handler"""
+ global _rev_1_engaged, _rev_2_engaged
if cmd == "cmd":
vjoy = gremlin.joystick_handling.VJoyProxy()
if value == "toggle_rev_1":
@@ -472,14 +485,23 @@ def osc_callback(cmd, value):
vjoy[2].axis(3).value = AXIS_IDLE
elif value == "set_rev_2":
_rev_2_engaged = True
- vjoy[2].axis(3).value = AXIS_REV
-
-
+ vjoy[2].axis(3).value = AXIS_REV
def update_airbus_gate(vjoy, index, gate):
- ''' sets joystick buttons based on gate ranges for either throttle 1 or 2 '''
- gates = [AB_IDLE_1, AB_IDLE_2, AB_CLIMB_1, AB_CLIMB_2, AB_FLEX_1, AB_FLEX_2, AB_REV_1, AB_REV_2, AB_TOGA_1, AB_TOGA_2 ]
+ """sets joystick buttons based on gate ranges for either throttle 1 or 2"""
+ gates = [
+ AB_IDLE_1,
+ AB_IDLE_2,
+ AB_CLIMB_1,
+ AB_CLIMB_2,
+ AB_FLEX_1,
+ AB_FLEX_2,
+ AB_REV_1,
+ AB_REV_2,
+ AB_TOGA_1,
+ AB_TOGA_2,
+ ]
global airbus_gate
if airbus_gate != gate:
airbus_gate = gate
@@ -493,7 +515,6 @@ def update_airbus_gate(vjoy, index, gate):
# say("toga mode")
# elif gate == GATE_REV:
# say("reverse mode")
-
if gate == GATE_IDLE:
if index == 1 or index == 0:
@@ -502,9 +523,9 @@ def update_airbus_gate(vjoy, index, gate):
vjoy[2].button(AB_IDLE_2).is_pressed = True
gates.remove(AB_IDLE_1)
gates.remove(AB_IDLE_2)
-
+
elif gate == GATE_CLIMB:
- if index == 1 or index == 0:
+ if index == 1 or index == 0:
vjoy[2].button(AB_CLIMB_1).is_pressed = True
if index == 2 or index == 0:
vjoy[2].button(AB_CLIMB_2).is_pressed = True
@@ -530,19 +551,18 @@ def update_airbus_gate(vjoy, index, gate):
vjoy[2].button(gate).is_pressed = False
-
def update_mixture_gate(vjoy, index):
- ''' updates mixture gate value cutoff/low/high '''
+ """updates mixture gate value cutoff/low/high"""
global mixture_gate
gate = mixture_gate[index]
base = 60
vjoy[2].button(base).is_pressed = gate == MIX_CUTOFF
- vjoy[2].button(base+1).is_pressed = gate == MIX_LOW
- vjoy[2].button(base+2).is_pressed = gate == MIX_HIGH
-
+ vjoy[2].button(base + 1).is_pressed = gate == MIX_LOW
+ vjoy[2].button(base + 2).is_pressed = gate == MIX_HIGH
+
-def clear_gates(vjoy, index = 0):
- ''' clears the airbus gate buttons '''
+def clear_gates(vjoy, index=0):
+ """clears the airbus gate buttons"""
rlow = None
if index == 0:
rlow = 50
@@ -554,11 +574,11 @@ def clear_gates(vjoy, index = 0):
rlow = 55
rhigh = 59
if rlow:
- for b in range(rlow,rhigh+1):
+ for b in range(rlow, rhigh + 1):
vjoy[2].button(b).is_pressed = False
+
def gate_name(gate):
-
if gate == GATE_IDLE:
return "Idle"
elif gate == GATE_CLIMB:
@@ -568,16 +588,16 @@ def gate_name(gate):
elif gate == GATE_TOGA:
return "TGA"
return "None"
-
+
@bravo.axis(1)
def read_throttle_1(event, vjoy):
- ''' LEFT THROTTLE (ENGINE 1)'''
+ """LEFT THROTTLE (ENGINE 1)"""
global airbus_mode, _rev_1_engaged, airbus_gate
# throttle 1 idle
value = event.value
vjoy[1].button(105).is_pressed = value == -1
-
+
if airbus_mode:
if _rev_1_engaged:
value = AXIS_REV
@@ -588,15 +608,16 @@ def read_throttle_1(event, vjoy):
# gremlin.util.log(f"left {gate_name(gate)} {value} raw: {event.value}")
vjoy[2].axis(2).value = value
+
@bravo.axis(6)
def read_throttle_2(event, vjoy):
- ''' RIGHT THROTTLE (ENGINE 2)'''
+ """RIGHT THROTTLE (ENGINE 2)"""
global airbus_mode, _rev_2_engaged
# throttle 2 idle
-
+
value = event.value
vjoy[1].button(106).is_pressed = value == -1
-
+
if airbus_mode:
if _rev_2_engaged:
value = AXIS_REV
@@ -605,49 +626,46 @@ def read_throttle_2(event, vjoy):
# gate the throttle for three positions (CLB, FLT and TOGA)
value, gate = get_gate_value(value)
# gremlin.util.log(f"right {gate_name(gate)} output: {value} raw: {event.value}")
-
vjoy[2].axis(3).value = value
-
@bravo.axis(4)
def read_mixture(event, vjoy):
- ''' reads the mixture axis '''
+ """reads the mixture axis"""
global mixture_gate
value = event.value
- #gremlin.util.log(f"mixture {value}")
+ # gremlin.util.log(f"mixture {value}")
if value >= MIX_LOW_MIN and value <= MIX_LOW_MAX:
mixture_gate[1] = MIX_LOW
- #gremlin.util.log(f"mixture low")
+ # gremlin.util.log(f"mixture low")
elif value >= MIX_HI_MIN and value <= MIX_HI_MAX:
mixture_gate[1] = MIX_HIGH
- #gremlin.util.log(f"mixture high")
+ # gremlin.util.log(f"mixture high")
update_mixture_gate(vjoy, 1)
-
-
-
# # thrust lever
@bravo.button(30)
def toggle_rev_1(event, vjoy, joy):
global _rev_1_engaged
_rev_1_engaged = event.is_pressed
update_reversers(vjoy)
-
+
+
# thrust lever
@bravo.button(29)
def toggle_rev_2(event, vjoy, joy):
global _rev_2_engaged
_rev_2_engaged = event.is_pressed
update_reversers(vjoy)
-
+
+
# mixture cutoff
@bravo.button(28)
def mixture_cutoff(event, vjoy):
global mixture_gate
- #gremlin.util.log(f"mixture cutoff event")
+ # gremlin.util.log(f"mixture cutoff event")
mixture_gate[1] = MIX_CUTOFF if event.is_pressed else MIX_NONE
update_mixture_gate(vjoy, 1)
@@ -676,13 +694,13 @@ def condition_lever(event, vjoy):
# vjoy[1].axis(3).value = active_curve(value * rudder_factor)
-
@left_vpc.button(1)
# button - left yoke rear
def alpha_rudder(event):
# top guard = reversers
RunMacro(f1_macro)
- #unscale_rudder(event)
+ # unscale_rudder(event)
+
# @bravo.button(30)
# def vpc_rudder_b30(event,vjoy):
@@ -707,12 +725,12 @@ def alpha_rudder(event):
# unscale_rudder(event, vjoy)
-
@bravo.button(31)
def gear_up(event, vjoy):
if event.is_pressed:
fireButton(event, vjoy, 1, 31, None, True, PULSE_DURATION)
+
@bravo.button(32)
def gear_down(event, vjoy):
if event.is_pressed:
@@ -720,9 +738,10 @@ def gear_down(event, vjoy):
PERIODIC = 0.25
+
+
@gremlin.input_devices.periodic(PERIODIC)
def update():
-
# update trim axis based on the current position of the axis
global vpc_left_raw
stick_value = vpc_left_raw.axis(5).value
@@ -745,7 +764,7 @@ def update():
offset = -large_offset
else:
return
-
+
vjoy = gremlin.joystick_handling.VJoyProxy()
value = vjoy[2].axis(4).value
# reverse
@@ -754,11 +773,10 @@ def update():
value = 1
elif value < -1:
value = -1
- #gremlin.util.log(f"{offset} {value}")
+ # gremlin.util.log(f"{offset} {value}")
vjoy[2].axis(4).value = value
-
@gremlin.input_devices.gremlin_start()
def on_start():
global _rev_1_engaged, _rev_2_engaged
@@ -766,14 +784,14 @@ def on_start():
_rev_2_engaged = bravo_raw.button(48).is_pressed
# zero the throttles
- osc_callback("cmd","set_idle")
+ osc_callback("cmd", "set_idle")
# flaps UP
vjoy = gremlin.joystick_handling.VJoyProxy()
vjoy[2].axis(6).value = 1
- register_handler(osc_callback)
-
+ register_handler(osc_callback)
+
@gremlin.input_devices.gremlin_stop()
def on_stop():
- unregister_handler(osc_callback)
\ No newline at end of file
+ unregister_handler(osc_callback)
diff --git a/scripts/osc.py b/scripts/osc.py
index 8c42e6d5..7855429e 100644
--- a/scripts/osc.py
+++ b/scripts/osc.py
@@ -7,29 +7,38 @@
import logging
import re
import time
-from typing import overload, List, Union, Any, Generator, Tuple, Callable, Optional, DefaultDict, Iterator, Union, cast, Coroutine, NamedTuple
-import logging
-from typing import Any, Iterator, List, Union
+from typing import (
+ overload,
+ List,
+ Any,
+ Generator,
+ Tuple,
+ Callable,
+ Optional,
+ Iterator,
+ Union,
+ cast,
+ Coroutine,
+ NamedTuple,
+)
import asyncio
from asyncio import BaseEventLoop
import socketserver
import socket
from socket import socket as _socket
-import sys
import os
from collections.abc import Iterable
import struct
-from datetime import datetime, timedelta, date
-from typing import NamedTuple
+from datetime import datetime, timedelta
-### gremlin start -------------------------------------------------------
+### gremlin start -------------------------------------------------------
import gremlin
import threading
-from gremlin.macro import Macro, key_from_name, MacroManager, Key, PauseAction
+from gremlin.macro import Macro, key_from_name, MacroManager
from util import fire_event
@@ -45,62 +54,64 @@
osc = None
-
-
-
def GetVjoy():
- ''' get the vjoy device (virtual hardware input)'''
- return gremlin.joystick_handling.VJoyProxy()
+ """get the vjoy device (virtual hardware input)"""
+ return gremlin.joystick_handling.VJoyProxy()
+
def log(msg):
- ''' displays a log message in Gremlin and in the console '''
+ """displays a log message in Gremlin and in the console"""
gremlin.util.log(msg)
- #print(msg)
+ # print(msg)
+
# async routine to pulse a button
-def _fire_pulse(vjoy, unit, button, repeat = 1, duration = 0.2):
- if repeat < 0:
- repeat = -repeat
- for i in range(repeat):
- # gremlin.util.log("Pulsing vjoy %s button %s on" % (unit, button) )
- vjoy[unit].button(button).is_pressed = True
- time.sleep(duration)
- vjoy[unit].button(button).is_pressed = False
- time.sleep(duration)
- else:
- if repeat <= 1:
- gremlin.util.log(f"Pulsing vjoy {unit} button {button} on")
- vjoy[unit].button(button).is_pressed = True
- time.sleep(duration)
- vjoy[unit].button(button).is_pressed = False
- else:
- vjoy[unit].button(button).is_pressed = True
- time.sleep(duration*repeat)
- vjoy[unit].button(button).is_pressed = False
-
- # gremlin.util.log("Pulsing vjoy %s button %s off" % (unit, button) )
+def _fire_pulse(vjoy, unit, button, repeat=1, duration=0.2):
+ if repeat < 0:
+ repeat = -repeat
+ for i in range(repeat):
+ # gremlin.util.log("Pulsing vjoy %s button %s on" % (unit, button) )
+ vjoy[unit].button(button).is_pressed = True
+ time.sleep(duration)
+ vjoy[unit].button(button).is_pressed = False
+ time.sleep(duration)
+ else:
+ if repeat <= 1:
+ gremlin.util.log(f"Pulsing vjoy {unit} button {button} on")
+ vjoy[unit].button(button).is_pressed = True
+ time.sleep(duration)
+ vjoy[unit].button(button).is_pressed = False
+ else:
+ vjoy[unit].button(button).is_pressed = True
+ time.sleep(duration * repeat)
+ vjoy[unit].button(button).is_pressed = False
+
+ # gremlin.util.log("Pulsing vjoy %s button %s off" % (unit, button) )
+
# pulses a button - unit is the vjoy output device number, button is the number of the button on the device to pulse
-def pulse(vjoy, unit, button, duration = 0.2, repeat = 1):
- gremlin.util.log(f"pulsing: unit {unit} button {button}")
- threading.Timer(0.01, _fire_pulse, [vjoy, unit, button, repeat, duration]).start()
+def pulse(vjoy, unit, button, duration=0.2, repeat=1):
+ gremlin.util.log(f"pulsing: unit {unit} button {button}")
+ threading.Timer(0.01, _fire_pulse, [vjoy, unit, button, repeat, duration]).start()
-class Speech():
- ''' tts interface '''
- def __init__(self):
- import win32com.client
- self.speaker = win32com.client.Dispatch("SAPI.SpVoice")
+class Speech:
+ """tts interface"""
+
+ def __init__(self):
+ import win32com.client
+
+ self.speaker = win32com.client.Dispatch("SAPI.SpVoice")
- def speak(self, text):
- try:
- self.speaker.speak(text)
- except:
- pass
+ def speak(self, text):
+ try:
+ self.speaker.speak(text)
+ except Exception as e:
+ gremlin.util.log(f"Error: {e}")
def speech_handler(address, x):
- ''' handles sending text to speech '''
+ """handles sending text to speech"""
splits = address.split("/")
if len(splits) < 3:
# not enough data
@@ -109,17 +120,17 @@ def speech_handler(address, x):
Speech().speak(speech)
-
def keyboard_handler(address, x):
- ''' handles keyboard commands - address is already in lowercase '''
+ """handles keyboard commands - address is already in lowercase"""
import re
import itertools
+
log(f"KEYBOARD: received {address} {x}")
splits = address.split("/")
if len(splits) < 3:
# not enough data
return
-
+
# remove the command
splits = splits[2:]
pattern = re.compile(r"([+|-])|([^[\]\[]+)|(\[[0-9]+\])")
@@ -131,7 +142,6 @@ def keyboard_handler(address, x):
# further split each section in keyboard commands
commands = list(itertools.chain.from_iterable(pattern.findall(section)))
for token in commands:
-
if token == "+":
key_down = True
continue
@@ -145,53 +155,53 @@ def keyboard_handler(address, x):
token = token[1:][:-1]
if token.isnumeric():
delay_ms = int(token)
- macro.pause(delay_ms/1000)
+ macro.pause(delay_ms / 1000)
continue
key = None
- if token in ("ctr", "lctr", "ctrl","lctrl","leftcontrol"):
+ if token in ("ctr", "lctr", "ctrl", "lctrl", "leftcontrol"):
key = "leftcontrol"
elif token in ("rctr", "rctrl", "rightcontrol"):
key = "rightcontrol"
- elif token in ("shft","lshft","shift","lshift","leftshift"):
+ elif token in ("shft", "lshft", "shift", "lshift", "leftshift"):
key = "leftshift"
- elif token in ("rshft", "rshift","rightshift"):
+ elif token in ("rshft", "rshift", "rightshift"):
key = "rightshift2"
- elif token in ("alt", "lalt","leftalt"):
+ elif token in ("alt", "lalt", "leftalt"):
key = "leftalt"
elif token == "ralt":
- key = "rightalt2" # 0x38 TRUE
- elif token in ("win","lwin","leftwin"):
+ key = "rightalt2" # 0x38 TRUE
+ elif token in ("win", "lwin", "leftwin"):
key = "leftwin"
- elif token in ("rwin","rightwin"):
+ elif token in ("rwin", "rightwin"):
key = "rightwin"
- elif token in ("pgdn","pagedown"):
+ elif token in ("pgdn", "pagedown"):
key = "pagedown"
- elif token in ("pgup","pageup"):
+ elif token in ("pgup", "pageup"):
key = "pageup"
elif token == "slash":
key = "/"
else:
# regular key - output by character
key = token
-
+
if not key:
# don't know how to handle
continue
-
+
a_list = []
-
- action_key = key_from_name(key, validate = True)
+
+ action_key = key_from_name(key, validate=True)
if not action_key:
# check for text sequence that are press only without spacers - so wasd would pres w a s d separately
for c in key:
- action_key = key_from_name(c, validate = True)
+ action_key = key_from_name(c, validate=True)
if action_key:
a_list.append(action_key)
else:
a_list = [action_key]
-
+
if a_list:
for action_key in a_list:
if key_down:
@@ -228,15 +238,15 @@ def keyboard_handler(address, x):
# execute the macro if it contains anything
if macro.sequence:
MacroManager().queue_macro(macro)
-
+
def vjoy_handler(address, args):
- ''' handles vjoy output '''
+ """handles vjoy output"""
# format is /vjoy/command
#
# command:
# D[device_number]B[R][button_number][R]P[duration]A[axis_number]v[axis_value]
- #
+ #
# number: vjoy device number (1 based) so the first device is 1
# button_number: button 1 to 128
# axis_number: 1 to 8
@@ -248,17 +258,16 @@ def vjoy_handler(address, args):
y = 0
arg_count = len(args)
if arg_count == 2:
- (x,y) = args
+ (x, y) = args
elif arg_count == 1:
x = args[0]
- valid = False
splits = address.split("/")
if len(splits) == 3:
# get the last arg
command = splits.pop()
-
- regex = r'([d]|b[r|p]?|[a]|[v])\s*([+|-]?[0-9]*[.]?[0-9]*[r]?)'
- matches =re.findall(regex, command)
+
+ regex = r"([d]|b[r|p]?|[a]|[v])\s*([+|-]?[0-9]*[.]?[0-9]*[r]?)"
+ matches = re.findall(regex, command)
vjoy_device = 0
@@ -269,36 +278,36 @@ def vjoy_handler(address, args):
# current action
action = None
- for (item, data) in matches:
+ for item, data in matches:
# something got extracted - makes sense of i
try:
-
item = item.lower()
-
+
release = False
- if "r" in data:
- data = data.replace("r","")
+ if "r" in data:
+ data = data.replace("r", "")
release = True
if "r" in item:
- item = item.replace("r","")
+ item = item.replace("r", "")
release = True
-
+
try:
value = int(data)
- except:
- gremlin.util.log(f"Bad number format: {data} - check OSC sequence")
+ except Exception as ex:
+ gremlin.util.log(
+ f"Bad number format: {data} - check OSC sequence\n{ex}"
+ )
# ignore bad data
continue
-
-
+
if item == "d":
vjoy_device = value
- elif item in ("b","bp","br"):
+ elif item in ("b", "bp", "br"):
# press action
action = [vjoy_device, value, not release, False, 0]
actions.append(action)
- elif item in ("t","tb"):
+ elif item in ("t", "tb"):
# toggle button
state = vjoy[vjoy_device].button(value).is_pressed
action = [vjoy_device, value, not state, False, 0]
@@ -310,60 +319,60 @@ def vjoy_handler(address, args):
if value <= 0:
# make sure it's at least 200 ms to even register
value = 200
- action[4] = value/1000
+ action[4] = value / 1000
elif item == "a":
- # store the value that comes in at 0 to 1 to range -1 to +1
- axis_action = [vjoy_device, value, x*2 - 1]
+ # store the value that comes in at 0 to 1 to range -1 to +1
+ axis_action = [vjoy_device, value, x * 2 - 1]
axis_actions.append(axis_action)
elif item == "v":
# sets the axis to a known value -1000 to +1000
if axis_action:
# current axis action from setup
- v = float(data)/1000
- if v > 1.0:
+ v = float(data) / 1000
+ if v > 1.0:
v = 1.0
- elif v < -1.0:
+ elif v < -1.0:
v = -1.0
- axis_action[2]= v
+ axis_action[2] = v
+
+ except Exception as ex:
+ log(f"Unable to parse command: {command} with error: {ex}")
-
- except:
- log(f"Unable to parse command: {command}")
-
-
# process the command
- for (vjoy_device, vjoy_button, is_pressed, is_pulse, duration) in actions:
+ for vjoy_device, vjoy_button, is_pressed, is_pulse, duration in actions:
if 0 < vjoy_device <= 8 and 0 < vjoy_button <= 128:
# valid
if is_pulse:
pulse(vjoy, vjoy_device, vjoy_button, duration)
- log(f"VJOY[{vjoy_device}] button({vjoy_button}): pulse duration: {duration:0.4f})")
+ log(
+ f"VJOY[{vjoy_device}] button({vjoy_button}): pulse duration: {duration:0.4f})"
+ )
else:
vjoy[vjoy_device].button(vjoy_button).is_pressed = is_pressed
- log(f"VJOY[{vjoy_device}] button({vjoy_button}): {'press' if is_pressed else 'release'}")
+ log(
+ f"VJOY[{vjoy_device}] button({vjoy_button}): {'press' if is_pressed else 'release'}"
+ )
- for (vjoy_device, vjoy_axis, value) in axis_actions:
+ for vjoy_device, vjoy_axis, value in axis_actions:
if 0 < vjoy_device <= 8 and 0 < vjoy_axis <= 8 and -1.0 <= value <= 1.0:
vjoy[vjoy_device].axis(vjoy_axis).value = value
- log(f"VJOY[{vjoy_device}] axis({vjoy_axis} value: {value:0.4f})")
+ log(f"VJOY[{vjoy_device}] axis({vjoy_axis} value: {value:0.4f})")
def osc_message_handler(address, *args):
- #log(f"OSC: {address}: {args}")
+ # log(f"OSC: {address}: {args}")
address = address.lower()
- ESC = "!ESC!"
try:
commands = []
- keywords = ["say","key","kbd","knob","vjoy","cmd"]
-
+ keywords = ["say", "key", "kbd", "knob", "vjoy", "cmd"]
if args[0] == 0.0:
# RELEASE - ignore OSC releases
return
-
- # check for special characters
+
+ # check for special characters
splits = address.split("/")
while splits:
split = splits.pop(0)
@@ -371,10 +380,9 @@ def osc_message_handler(address, *args):
# blank
continue
if splits and split in keywords:
- commands.append("/"+split+"/"+splits.pop(0))
+ commands.append("/" + split + "/" + splits.pop(0))
for cmd in commands:
-
if cmd.startswith("/key") or cmd.startswith("/kbd"):
# send to keyboard macro handler
keyboard_handler(cmd, args[0])
@@ -382,8 +390,8 @@ def osc_message_handler(address, *args):
# send to text to speech handler
speech_handler(cmd, args[0])
elif cmd.startswith("/cmd"):
- # call the handler
- # OSC PRESS
+ # call the handler
+ # OSC PRESS
commands = address.split("/")
commands.pop(0)
for arg in commands:
@@ -395,9 +403,9 @@ def osc_message_handler(address, *args):
SLOW_C = 0.6
FAST_CC = 0.25
SLOW_CC = 0.4
-
+
if cmd == "/knob":
- (x,y) = args
+ (x, y) = args
# fast clockwise
if x > FAST_C:
@@ -406,7 +414,7 @@ def osc_message_handler(address, *args):
elif x > SLOW_C:
vjoy[3].button(1).is_pressed = True
vjoy[3].button(2).is_pressed = False
- if x < FAST_CC:
+ if x < FAST_CC:
vjoy[3].button(3).is_pressed = False
vjoy[3].button(4).is_pressed = True
elif x < SLOW_CC:
@@ -424,27 +432,24 @@ def osc_message_handler(address, *args):
elif cmd.startswith("/vjoy"):
vjoy_handler(cmd, args)
-
-
except Exception as ex:
- log(F"Command parse error: {ex}")
+ log(f"Command parse error: {ex}")
-### OSC handler start -------------------------------------------------------
-# Adapted from: Python-OSC https://github.com/attwad/python-osc
+### OSC handler start -------------------------------------------------------
+# Adapted from: Python-OSC https://github.com/attwad/python-osc
# Credits go to AttWad
# ####
-class Osc:
-
+class Osc:
def thread_loop(self):
- ''' main threading loop '''
+ """main threading loop"""
log("OSC: server starting")
self._dispatcher = Dispatcher()
self._dispatcher.set_default_handler(osc_message_handler)
self._server = BlockingOSCUDPServer((host_ip, in_port), self._dispatcher)
-
+
# this blocks until the server is shutdown
log("OSC: server running")
@@ -453,8 +458,6 @@ def thread_loop(self):
log("OSC: server shutdown")
self._server = None
-
-
def __init__(self):
log("OSC: init")
self._server = None
@@ -468,15 +471,15 @@ def __init__(self):
@property
def started(self):
- ''' true if server is started or in the process of starting '''
+ """true if server is started or in the process of starting"""
if self._lock.locked():
log("OSC: server locked")
return True
-
+
return self._running
def start(self):
- ''' starts the server '''
+ """starts the server"""
with self._lock:
# everything here is now locked until the server start is completed
@@ -484,16 +487,17 @@ def start(self):
log("OSC: start requested")
if self._running:
return
-
+
self._stop = False
if not self._server_thread.is_alive():
- self._server_thread = threading.Thread(target=self.thread_loop, daemon=True)
+ self._server_thread = threading.Thread(
+ target=self.thread_loop, daemon=True
+ )
self._server_thread.start()
self._running = True
-
def stop(self):
- ''' stops the server '''
+ """stops the server"""
if not self._running or self._start_requested:
return
log("OSC: stop requested")
@@ -509,6 +513,7 @@ def __del__(self):
log("OSC stopping...")
self.stop()
+
@gremlin.input_devices.gremlin_start()
def start():
log("Gremlin start!")
@@ -524,30 +529,28 @@ def stop():
if osc:
osc.stop()
+
@gremlin.input_devices.gremlin_mode()
def mode_changed(mode):
log(f"Gremlin mode change!: {mode}")
-
### ----------------------------------------------------------- OSC server stuff ----------------------------------------------------------
-
### PARSING ###
-
"""Parsing and conversion of NTP dates contained in datagrams."""
# 63 zero bits followed by a one in the least signifigant bit is a special
# case meaning "immediately."
-IMMEDIATELY = struct.pack('>Q', 1)
+IMMEDIATELY = struct.pack(">Q", 1)
# timetag * (1 / 2 ** 32) == l32bits + (r32bits / 1 ** 32)
-_NTP_TIMESTAMP_TO_SECONDS = 1. / 2. ** 32.
-_SECONDS_TO_NTP_TIMESTAMP = 2. ** 32.
+_NTP_TIMESTAMP_TO_SECONDS = 1.0 / 2.0**32.0
+_SECONDS_TO_NTP_TIMESTAMP = 2.0**32.0
# From NTP lib.
_SYSTEM_EPOCH = datetime(*time.gmtime(0)[0:3])
@@ -556,61 +559,57 @@ def mode_changed(mode):
_NTP_DELTA = (_SYSTEM_EPOCH - _NTP_EPOCH).days * 24 * 3600
-Timestamp = NamedTuple('Timestamp', [
- ('seconds', int),
- ('fraction', int),
-])
+Timestamp = NamedTuple(
+ "Timestamp",
+ [
+ ("seconds", int),
+ ("fraction", int),
+ ],
+)
class NtpError(Exception):
- """Base class for ntp module errors."""
+ """Base class for ntp module errors."""
def parse_timestamp(timestamp: int) -> Timestamp:
- """Parse NTP timestamp as Timetag.
- """
+ """Parse NTP timestamp as Timetag."""
seconds = timestamp >> 32
fraction = timestamp & 0xFFFFFFFF
return Timestamp(seconds, fraction)
def ntp_to_system_time(timestamp: bytes) -> float:
- """Convert a NTP timestamp to system time in seconds.
- """
+ """Convert a NTP timestamp to system time in seconds."""
try:
- timestamp = struct.unpack('>Q', timestamp)[0]
+ timestamp = struct.unpack(">Q", timestamp)[0]
except Exception as e:
raise NtpError(e)
return timestamp * _NTP_TIMESTAMP_TO_SECONDS - _NTP_DELTA
def system_time_to_ntp(seconds: float) -> bytes:
- """Convert a system time in seconds to NTP timestamp.
- """
+ """Convert a system time in seconds to NTP timestamp."""
try:
- seconds = seconds + _NTP_DELTA
+ seconds = seconds + _NTP_DELTA
except TypeError as e:
- raise NtpError(e)
- return struct.pack('>Q', int(seconds * _SECONDS_TO_NTP_TIMESTAMP))
+ raise NtpError(e)
+ return struct.pack(">Q", int(seconds * _SECONDS_TO_NTP_TIMESTAMP))
def ntp_time_to_system_epoch(seconds: float) -> float:
- """Convert a NTP time in seconds to system time in seconds.
- """
+ """Convert a NTP time in seconds to system time in seconds."""
return seconds - _NTP_DELTA
def system_time_to_ntp_epoch(seconds: float) -> float:
- """Convert a system time in seconds to NTP time in seconds.
- """
+ """Convert a system time in seconds to NTP time in seconds."""
return seconds + _NTP_DELTA
-
"""Functions to get OSC types from datagrams and vice versa"""
-
MidiPacket = Tuple[int, int, int, int]
@@ -635,7 +634,7 @@ class BuildError(Exception):
# Strings and blob dgram length is always a multiple of 4 bytes.
_STRING_DGRAM_PAD = 4
_BLOB_DGRAM_PAD = 4
-_EMPTY_STR_DGRAM = b'\x00\x00\x00\x00'
+_EMPTY_STR_DGRAM = b"\x00\x00\x00\x00"
def write_string(val: str) -> bytes:
@@ -645,11 +644,11 @@ def write_string(val: str) -> bytes:
- BuildError if the string could not be encoded.
"""
try:
- dgram = val.encode('utf-8') # Default, but better be explicit.
+ dgram = val.encode("utf-8") # Default, but better be explicit.
except (UnicodeEncodeError, AttributeError) as e:
- raise BuildError(f'Incorrect string, could not encode {e}')
+ raise BuildError(f"Incorrect string, could not encode {e}")
diff = _STRING_DGRAM_PAD - (len(dgram) % _STRING_DGRAM_PAD)
- dgram += (b'\x00' * diff)
+ dgram += b"\x00" * diff
return dgram
@@ -672,29 +671,31 @@ def get_string(dgram: bytes, start_index: int) -> Tuple[str, int]:
ParseError if the datagram could not be parsed.
"""
if start_index < 0:
- raise ParseError('start_index < 0')
+ raise ParseError("start_index < 0")
offset = 0
try:
- if (len(dgram) > start_index + _STRING_DGRAM_PAD
- and dgram[start_index + _STRING_DGRAM_PAD] == _EMPTY_STR_DGRAM):
- return '', start_index + _STRING_DGRAM_PAD
+ if (
+ len(dgram) > start_index + _STRING_DGRAM_PAD
+ and dgram[start_index + _STRING_DGRAM_PAD] == _EMPTY_STR_DGRAM
+ ):
+ return "", start_index + _STRING_DGRAM_PAD
while dgram[start_index + offset] != 0:
offset += 1
# Align to a byte word.
if (offset) % _STRING_DGRAM_PAD == 0:
offset += _STRING_DGRAM_PAD
else:
- offset += (-offset % _STRING_DGRAM_PAD)
+ offset += -offset % _STRING_DGRAM_PAD
# Python slices do not raise an IndexError past the last index,
# do it ourselves.
if offset > len(dgram[start_index:]):
- raise ParseError('Datagram is too short')
- data_str = dgram[start_index:start_index + offset]
- return data_str.replace(b'\x00', b'').decode('utf-8'), start_index + offset
+ raise ParseError("Datagram is too short")
+ data_str = dgram[start_index : start_index + offset]
+ return data_str.replace(b"\x00", b"").decode("utf-8"), start_index + offset
except IndexError as ie:
- raise ParseError(f'Could not parse datagram {ie}')
+ raise ParseError(f"Could not parse datagram {ie}")
except TypeError as te:
- raise ParseError(f'Could not parse datagram {te}')
+ raise ParseError(f"Could not parse datagram {te}")
def write_int(val: int) -> bytes:
@@ -704,9 +705,9 @@ def write_int(val: int) -> bytes:
- BuildError if the int could not be converted.
"""
try:
- return struct.pack('>i', val)
+ return struct.pack(">i", val)
except struct.error as e:
- raise BuildError(f'Wrong argument value passed: {e}')
+ raise BuildError(f"Wrong argument value passed: {e}")
def get_int(dgram: bytes, start_index: int) -> Tuple[int, int]:
@@ -724,13 +725,13 @@ def get_int(dgram: bytes, start_index: int) -> Tuple[int, int]:
"""
try:
if len(dgram[start_index:]) < _INT_DGRAM_LEN:
- raise ParseError('Datagram is too short')
+ raise ParseError("Datagram is too short")
return (
- struct.unpack('>i',
- dgram[start_index:start_index + _INT_DGRAM_LEN])[0],
- start_index + _INT_DGRAM_LEN)
+ struct.unpack(">i", dgram[start_index : start_index + _INT_DGRAM_LEN])[0],
+ start_index + _INT_DGRAM_LEN,
+ )
except (struct.error, TypeError) as e:
- raise ParseError(f'Could not parse datagram {e}')
+ raise ParseError(f"Could not parse datagram {e}")
def write_int64(val: int) -> bytes:
@@ -740,9 +741,9 @@ def write_int64(val: int) -> bytes:
- BuildError if the int64 could not be converted.
"""
try:
- return struct.pack('>q', val)
+ return struct.pack(">q", val)
except struct.error as e:
- raise BuildError(f'Wrong argument value passed: {e}')
+ raise BuildError(f"Wrong argument value passed: {e}")
def get_int64(dgram: bytes, start_index: int) -> Tuple[int, int]:
@@ -760,13 +761,13 @@ def get_int64(dgram: bytes, start_index: int) -> Tuple[int, int]:
"""
try:
if len(dgram[start_index:]) < _INT64_DGRAM_LEN:
- raise ParseError('Datagram is too short')
+ raise ParseError("Datagram is too short")
return (
- struct.unpack('>q',
- dgram[start_index:start_index + _INT64_DGRAM_LEN])[0],
- start_index + _INT64_DGRAM_LEN)
+ struct.unpack(">q", dgram[start_index : start_index + _INT64_DGRAM_LEN])[0],
+ start_index + _INT64_DGRAM_LEN,
+ )
except (struct.error, TypeError) as e:
- raise ParseError(f'Could not parse datagram {e}')
+ raise ParseError(f"Could not parse datagram {e}")
def get_uint64(dgram: bytes, start_index: int) -> Tuple[int, int]:
@@ -784,13 +785,15 @@ def get_uint64(dgram: bytes, start_index: int) -> Tuple[int, int]:
"""
try:
if len(dgram[start_index:]) < _UINT64_DGRAM_LEN:
- raise ParseError('Datagram is too short')
+ raise ParseError("Datagram is too short")
return (
- struct.unpack('>Q',
- dgram[start_index:start_index + _UINT64_DGRAM_LEN])[0],
- start_index + _UINT64_DGRAM_LEN)
+ struct.unpack(">Q", dgram[start_index : start_index + _UINT64_DGRAM_LEN])[
+ 0
+ ],
+ start_index + _UINT64_DGRAM_LEN,
+ )
except (struct.error, TypeError) as e:
- raise ParseError(f'Could not parse datagram {e}')
+ raise ParseError(f"Could not parse datagram {e}")
def get_timetag(dgram: bytes, start_index: int) -> Tuple[Tuple[datetime, int], int]:
@@ -809,7 +812,7 @@ def get_timetag(dgram: bytes, start_index: int) -> Tuple[Tuple[datetime, int], i
"""
try:
if len(dgram[start_index:]) < _TIMETAG_DGRAM_LEN:
- raise ParseError('Datagram is too short')
+ raise ParseError("Datagram is too short")
timetag, _ = get_uint64(dgram, start_index)
seconds, fraction = parse_timestamp(timetag)
@@ -817,12 +820,13 @@ def get_timetag(dgram: bytes, start_index: int) -> Tuple[Tuple[datetime, int], i
hours, seconds = seconds // 3600, seconds % 3600
minutes, seconds = seconds // 60, seconds % 60
- utc = (datetime.combine(_NTP_EPOCH, datetime.min.time()) +
- timedelta(hours=hours, minutes=minutes, seconds=seconds))
+ utc = datetime.combine(_NTP_EPOCH, datetime.min.time()) + timedelta(
+ hours=hours, minutes=minutes, seconds=seconds
+ )
return (utc, fraction), start_index + _TIMETAG_DGRAM_LEN
except (struct.error, TypeError) as e:
- raise ParseError(f'Could not parse datagram {e}')
+ raise ParseError(f"Could not parse datagram {e}")
def write_float(val: float) -> bytes:
@@ -832,9 +836,9 @@ def write_float(val: float) -> bytes:
- BuildError if the float could not be converted.
"""
try:
- return struct.pack('>f', val)
+ return struct.pack(">f", val)
except struct.error as e:
- raise BuildError(f'Wrong argument value passed: {e}')
+ raise BuildError(f"Wrong argument value passed: {e}")
def get_float(dgram: bytes, start_index: int) -> Tuple[float, int]:
@@ -855,13 +859,13 @@ def get_float(dgram: bytes, start_index: int) -> Tuple[float, int]:
# Noticed that Reaktor doesn't send the last bunch of \x00 needed to make
# the float representation complete in some cases, thus we pad here to
# account for that.
- dgram = dgram + b'\x00' * (_FLOAT_DGRAM_LEN - len(dgram[start_index:]))
+ dgram = dgram + b"\x00" * (_FLOAT_DGRAM_LEN - len(dgram[start_index:]))
return (
- struct.unpack('>f',
- dgram[start_index:start_index + _FLOAT_DGRAM_LEN])[0],
- start_index + _FLOAT_DGRAM_LEN)
+ struct.unpack(">f", dgram[start_index : start_index + _FLOAT_DGRAM_LEN])[0],
+ start_index + _FLOAT_DGRAM_LEN,
+ )
except (struct.error, TypeError) as e:
- raise ParseError(f'Could not parse datagram {e}')
+ raise ParseError(f"Could not parse datagram {e}")
def write_double(val: float) -> bytes:
@@ -871,9 +875,9 @@ def write_double(val: float) -> bytes:
- BuildError if the double could not be converted.
"""
try:
- return struct.pack('>d', val)
+ return struct.pack(">d", val)
except struct.error as e:
- raise BuildError(f'Wrong argument value passed: {e}')
+ raise BuildError(f"Wrong argument value passed: {e}")
def get_double(dgram: bytes, start_index: int) -> Tuple[float, int]:
@@ -891,17 +895,19 @@ def get_double(dgram: bytes, start_index: int) -> Tuple[float, int]:
"""
try:
if len(dgram[start_index:]) < _DOUBLE_DGRAM_LEN:
- raise ParseError('Datagram is too short')
+ raise ParseError("Datagram is too short")
return (
- struct.unpack('>d',
- dgram[start_index:start_index + _DOUBLE_DGRAM_LEN])[0],
- start_index + _DOUBLE_DGRAM_LEN)
+ struct.unpack(">d", dgram[start_index : start_index + _DOUBLE_DGRAM_LEN])[
+ 0
+ ],
+ start_index + _DOUBLE_DGRAM_LEN,
+ )
except (struct.error, TypeError) as e:
- raise ParseError(f'Could not parse datagram {e}')
+ raise ParseError(f"Could not parse datagram {e}")
def get_blob(dgram: bytes, start_index: int) -> Tuple[bytes, int]:
- """ Get a blob from the datagram.
+ """Get a blob from the datagram.
According to the specifications, a blob is made of
"an int32 size count, followed by that many 8-bit bytes of arbitrary
@@ -923,8 +929,8 @@ def get_blob(dgram: bytes, start_index: int) -> Tuple[bytes, int]:
total_size = size + (-size % _BLOB_DGRAM_PAD)
end_index = int_offset + size
if end_index - start_index > len(dgram[start_index:]):
- raise ParseError('Datagram is too short.')
- return dgram[int_offset:int_offset + size], int_offset + total_size
+ raise ParseError("Datagram is too short.")
+ return dgram[int_offset : int_offset + size], int_offset + total_size
def write_blob(val: bytes) -> bytes:
@@ -934,11 +940,11 @@ def write_blob(val: bytes) -> bytes:
- BuildError if the value was empty or if its size didn't fit an OSC int.
"""
if not val:
- raise BuildError('Blob value cannot be empty')
+ raise BuildError("Blob value cannot be empty")
dgram = write_int(len(val))
dgram += val
while len(dgram) % _BLOB_DGRAM_PAD != 0:
- dgram += b'\x00'
+ dgram += b"\x00"
return dgram
@@ -962,10 +968,10 @@ def get_date(dgram: bytes, start_index: int) -> Tuple[float, int]:
ParseError if the datagram could not be parsed.
"""
# Check for the special case first.
- if dgram[start_index:start_index + _TIMETAG_DGRAM_LEN] == IMMEDIATELY:
+ if dgram[start_index : start_index + _TIMETAG_DGRAM_LEN] == IMMEDIATELY:
return IMMEDIATELY, start_index + _TIMETAG_DGRAM_LEN
if len(dgram[start_index:]) < _TIMETAG_DGRAM_LEN:
- raise ParseError('Datagram is too short')
+ raise ParseError("Datagram is too short")
timetag, start_index = get_uint64(dgram, start_index)
seconds = timetag * _NTP_TIMESTAMP_TO_SECONDS
return ntp_time_to_system_epoch(seconds), start_index
@@ -988,9 +994,9 @@ def write_rgba(val: bytes) -> bytes:
- BuildError if the int could not be converted.
"""
try:
- return struct.pack('>I', val)
+ return struct.pack(">I", val)
except struct.error as e:
- raise BuildError(f'Wrong argument value passed: {e}')
+ raise BuildError(f"Wrong argument value passed: {e}")
def get_rgba(dgram: bytes, start_index: int) -> Tuple[bytes, int]:
@@ -1008,13 +1014,13 @@ def get_rgba(dgram: bytes, start_index: int) -> Tuple[bytes, int]:
"""
try:
if len(dgram[start_index:]) < _INT_DGRAM_LEN:
- raise ParseError('Datagram is too short')
+ raise ParseError("Datagram is too short")
return (
- struct.unpack('>I',
- dgram[start_index:start_index + _INT_DGRAM_LEN])[0],
- start_index + _INT_DGRAM_LEN)
+ struct.unpack(">I", dgram[start_index : start_index + _INT_DGRAM_LEN])[0],
+ start_index + _INT_DGRAM_LEN,
+ )
except (struct.error, TypeError) as e:
- raise ParseError(f'Could not parse datagram {e}')
+ raise ParseError(f"Could not parse datagram {e}")
def write_midi(val: MidiPacket) -> bytes:
@@ -1027,12 +1033,12 @@ def write_midi(val: MidiPacket) -> bytes:
"""
if len(val) != 4:
- raise BuildError('MIDI message length is invalid')
+ raise BuildError("MIDI message length is invalid")
try:
value = sum((value & 0xFF) << 8 * (3 - pos) for pos, value in enumerate(val))
- return struct.pack('>I', value)
+ return struct.pack(">I", value)
except struct.error as e:
- raise BuildError(f'Wrong argument value passed: {e}')
+ raise BuildError(f"Wrong argument value passed: {e}")
def get_midi(dgram: bytes, start_index: int) -> Tuple[MidiPacket, int]:
@@ -1050,18 +1056,14 @@ def get_midi(dgram: bytes, start_index: int) -> Tuple[MidiPacket, int]:
"""
try:
if len(dgram[start_index:]) < _INT_DGRAM_LEN:
- raise ParseError('Datagram is too short')
- val = struct.unpack('>I',
- dgram[start_index:start_index + _INT_DGRAM_LEN])[0]
+ raise ParseError("Datagram is too short")
+ val = struct.unpack(">I", dgram[start_index : start_index + _INT_DGRAM_LEN])[0]
midi_msg = cast(
- MidiPacket,
- tuple((val & 0xFF << 8 * i) >> 8 * i for i in range(3, -1, -1)))
+ MidiPacket, tuple((val & 0xFF << 8 * i) >> 8 * i for i in range(3, -1, -1))
+ )
return (midi_msg, start_index + _INT_DGRAM_LEN)
except (struct.error, TypeError) as e:
- raise ParseError(f'Could not parse datagram {e}')
-
-
-
+ raise ParseError(f"Could not parse datagram {e}")
### OSCE MESSAGE ###
@@ -1069,9 +1071,6 @@ def get_midi(dgram: bytes, start_index: int) -> Tuple[MidiPacket, int]:
"""Representation of an OSC message in a pythonesque way."""
-
-
-
class OscMessage(object):
"""Representation of a parsed datagram representing an OSC message.
@@ -1093,7 +1092,7 @@ def _parse_datagram(self) -> None:
# Get the parameters types.
type_tag, index = get_string(self._dgram, index)
- if type_tag.startswith(','):
+ if type_tag.startswith(","):
type_tag = type_tag[1:]
params = [] # type: List[Any]
@@ -1131,19 +1130,21 @@ def _parse_datagram(self) -> None:
param_stack.append(array)
elif param == "]": # Array stop.
if len(param_stack) < 2:
- raise ParseError(f'Unexpected closing bracket in type tag: {type_tag}')
+ raise ParseError(
+ f"Unexpected closing bracket in type tag: {type_tag}"
+ )
param_stack.pop()
# TODO: Support more exotic types as described in the specification.
else:
- logging.warning(f'Unhandled parameter type: {param}')
+ logging.warning(f"Unhandled parameter type: {param}")
continue
if param not in "[]":
param_stack[-1].append(val)
if len(param_stack) != 1:
- raise ParseError(f'Missing closing bracket in type tag: {type_tag}')
+ raise ParseError(f"Missing closing bracket in type tag: {type_tag}")
self._parameters = params
except ParseError as pe:
- raise ParseError('Found incorrect datagram, ignoring it', pe)
+ raise ParseError("Found incorrect datagram, ignoring it", pe)
@property
def address(self) -> str:
@@ -1153,7 +1154,7 @@ def address(self) -> str:
@staticmethod
def dgram_is_message(dgram: bytes) -> bool:
"""Returns whether this datagram starts as an OSC message."""
- return dgram.startswith(b'/')
+ return dgram.startswith(b"/")
@property
def size(self) -> int:
@@ -1175,7 +1176,6 @@ def __iter__(self) -> Iterator[Any]:
return iter(self._parameters)
-
### OSC PACKET ###
"""Use OSC packets to parse incoming UDP packets into messages or bundles.
@@ -1188,10 +1188,13 @@ def __iter__(self) -> Iterator[Any]:
# 1) the system time at which the message should be executed
# in seconds since the epoch.
# 2) the actual message.
-TimedMessage = NamedTuple('TimedMessage', [
- ('time', float),
- ('message', OscMessage),
-])
+TimedMessage = NamedTuple(
+ "TimedMessage",
+ [
+ ("time", float),
+ ("message", OscMessage),
+ ],
+)
def _timed_msg_of_bundle(bundle, now: float) -> List[TimedMessage]:
@@ -1199,7 +1202,7 @@ def _timed_msg_of_bundle(bundle, now: float) -> List[TimedMessage]:
msgs = []
for content in bundle:
if type(content) is OscMessage:
- if (bundle.timestamp == IMMEDIATELY or bundle.timestamp < now):
+ if bundle.timestamp == IMMEDIATELY or bundle.timestamp < now:
msgs.append(TimedMessage(now, content))
else:
msgs.append(TimedMessage(bundle.timestamp, content))
@@ -1208,7 +1211,6 @@ def _timed_msg_of_bundle(bundle, now: float) -> List[TimedMessage]:
return msgs
-
class OscPacket(object):
"""Unit of transmission of the OSC protocol.
@@ -1229,17 +1231,17 @@ def __init__(self, dgram: bytes) -> None:
try:
if OscBundle.dgram_is_bundle(dgram):
self._messages = sorted(
- _timed_msg_of_bundle(OscBundle(dgram), now),
- key=lambda x: x.time)
+ _timed_msg_of_bundle(OscBundle(dgram), now), key=lambda x: x.time
+ )
elif OscMessage.dgram_is_message(dgram):
self._messages = [TimedMessage(now, OscMessage(dgram))]
else:
# Empty packet, should not happen as per the spec but heh, UDP...
raise ParseError(
- 'OSC Packet should at least contain an OscMessage or an '
- 'OscBundle.')
+ "OSC Packet should at least contain an OscMessage or an OscBundle."
+ )
except (ParseError, ParseError) as e:
- raise ParseError(f'Could not parse datagram {e}')
+ raise ParseError(f"Could not parse datagram {e}")
@property
def messages(self) -> List[TimedMessage]:
@@ -1247,14 +1249,12 @@ def messages(self) -> List[TimedMessage]:
return self._messages
-
### OSC BUNDLE ###
_BUNDLE_PREFIX = b"#bundle\x00"
-
class OscBundle(object):
"""Bundles elements that should be triggered at the same time.
@@ -1276,11 +1276,11 @@ def __init__(self, dgram: bytes) -> None:
try:
self._timestamp, index = get_date(self._dgram, index)
except ParseError as e:
- raise ParseError(f'Could not parse datagram {e}')
+ raise ParseError(f"Could not parse datagram {e}")
# Get the contents as a list of OscBundle and OscMessage.
self._contents = self._parse_contents(index)
- def _parse_contents(self, index: int) -> List[Union['OscBundle', OscMessage]]:
+ def _parse_contents(self, index: int) -> List[Union["OscBundle", OscMessage]]:
contents = [] # type: List[Union[OscBundle, OscMessage]]
try:
@@ -1292,7 +1292,7 @@ def _parse_contents(self, index: int) -> List[Union['OscBundle', OscMessage]]:
# Get the sub content size.
content_size, index = get_int(self._dgram, index)
# Get the datagram for the sub content.
- content_dgram = self._dgram[index:index + content_size]
+ content_dgram = self._dgram[index : index + content_size]
# Increment our position index up to the next possible content.
index += content_size
# Parse the content into an OSC message or bundle.
@@ -1301,9 +1301,11 @@ def _parse_contents(self, index: int) -> List[Union['OscBundle', OscMessage]]:
elif OscMessage.dgram_is_message(content_dgram):
contents.append(OscMessage(content_dgram))
else:
- logging.warning(f"Could not identify content type of dgram {content_dgram}")
+ logging.warning(
+ f"Could not identify content type of dgram {content_dgram}"
+ )
except (ParseError, ParseError, IndexError) as e:
- raise ParseError(f'Could not parse datagram {e}')
+ raise ParseError(f"Could not parse datagram {e}")
return contents
@@ -1341,22 +1343,14 @@ def __iter__(self) -> Iterator[Any]:
return iter(self._contents)
-
-
-
### OSC BUNDLE BUILDER ###
"""Build OSC bundles for client applications."""
-
# Shortcut to specify an immediate execution of messages in the bundle.
-class BuildError(Exception):
- """Error raised when an error occurs building the bundle."""
-
-
class OscBundleBuilder(object):
"""Builds arbitrary OscBundle instances."""
@@ -1384,24 +1378,22 @@ def build(self) -> OscBundle:
Raises:
- BuildError: if we could not build the bundle.
"""
- dgram = b'#bundle\x00'
+ dgram = b"#bundle\x00"
try:
dgram += write_date(self._timestamp)
for content in self._contents:
- if (type(content) == OscMessage
- or type(content) == OscBundle):
+ if isinstance(content, (OscMessage, OscBundle)):
size = content.size
dgram += write_int(size)
dgram += content.dgram
else:
raise BuildError(
"Content must be either OscBundle or OscMessage"
- f"found {type(content)}")
+ f"found {type(content)}"
+ )
return OscBundle(dgram)
except BuildError as be:
- raise BuildError(f'Could not build the bundle {be}')
-
-
+ raise BuildError(f"Could not build the bundle {be}")
### OSC MESSAGE BUILDER ###
@@ -1411,8 +1403,6 @@ def build(self) -> OscBundle:
ArgValue = Union[str, bytes, bool, int, float, MidiPacket, list]
-class BuildError(Exception):
- """Error raised when an incomplete message is trying to be built."""
class OscMessageBuilder(object):
"""Builds arbitrary OscMessage instances."""
@@ -1433,8 +1423,18 @@ class OscMessageBuilder(object):
ARG_TYPE_ARRAY_STOP = "]"
_SUPPORTED_ARG_TYPES = (
- ARG_TYPE_FLOAT, ARG_TYPE_DOUBLE, ARG_TYPE_INT, ARG_TYPE_INT64, ARG_TYPE_BLOB, ARG_TYPE_STRING,
- ARG_TYPE_RGBA, ARG_TYPE_MIDI, ARG_TYPE_TRUE, ARG_TYPE_FALSE, ARG_TYPE_NIL)
+ ARG_TYPE_FLOAT,
+ ARG_TYPE_DOUBLE,
+ ARG_TYPE_INT,
+ ARG_TYPE_INT64,
+ ARG_TYPE_BLOB,
+ ARG_TYPE_STRING,
+ ARG_TYPE_RGBA,
+ ARG_TYPE_MIDI,
+ ARG_TYPE_TRUE,
+ ARG_TYPE_FALSE,
+ ARG_TYPE_NIL,
+ )
def __init__(self, address: Optional[str] = None) -> None:
"""Initialize a new builder for a message.
@@ -1482,8 +1482,8 @@ def add_arg(self, arg_value: ArgValue, arg_type: Optional[str] = None) -> None:
"""
if arg_type and not self._valid_type(arg_type):
raise ValueError(
- f'arg_type must be one of {self._SUPPORTED_ARG_TYPES}, or an array of valid types'
- )
+ f"arg_type must be one of {self._SUPPORTED_ARG_TYPES}, or an array of valid types"
+ )
if not arg_type:
arg_type = self._get_arg_type(arg_value)
if isinstance(arg_type, list):
@@ -1526,7 +1526,7 @@ def _get_arg_type(self, arg_value: ArgValue) -> Union[str, Any]:
elif arg_value is None:
arg_type = self.ARG_TYPE_NIL
else:
- raise ValueError('Infered arg_value type is not supported')
+ raise ValueError("Infered arg_value type is not supported")
return arg_type
def build(self) -> OscMessage:
@@ -1540,18 +1540,18 @@ def build(self) -> OscMessage:
- an OscMessage instance.
"""
if not self._address:
- raise BuildError('OSC addresses cannot be empty')
- dgram = b''
+ raise BuildError("OSC addresses cannot be empty")
+ dgram = b""
try:
# Write the address.
dgram += write_string(self._address)
if not self._args:
- dgram += write_string(',')
+ dgram += write_string(",")
return OscMessage(dgram)
# Write the parameters.
arg_types = "".join([arg[0] for arg in self._args])
- dgram += write_string(',' + arg_types)
+ dgram += write_string("," + arg_types)
for arg_type, value in self._args:
if arg_type == self.ARG_TYPE_STRING:
dgram += write_string(value) # type: ignore[arg-type]
@@ -1569,21 +1569,20 @@ def build(self) -> OscMessage:
dgram += write_rgba(value) # type: ignore[arg-type]
elif arg_type == self.ARG_TYPE_MIDI:
dgram += write_midi(value) # type: ignore[arg-type]
- elif arg_type in (self.ARG_TYPE_TRUE,
- self.ARG_TYPE_FALSE,
- self.ARG_TYPE_ARRAY_START,
- self.ARG_TYPE_ARRAY_STOP,
- self.ARG_TYPE_NIL):
+ elif arg_type in (
+ self.ARG_TYPE_TRUE,
+ self.ARG_TYPE_FALSE,
+ self.ARG_TYPE_ARRAY_START,
+ self.ARG_TYPE_ARRAY_STOP,
+ self.ARG_TYPE_NIL,
+ ):
continue
else:
- raise BuildError(f'Incorrect parameter type found {arg_type}')
+ raise BuildError(f"Incorrect parameter type found {arg_type}")
return OscMessage(dgram)
except BuildError as be:
- raise BuildError(f'Could not build the message: {be}')
-
-
-
+ raise BuildError(f"Could not build the message: {be}")
### DISPATCHER ###
@@ -1591,8 +1590,6 @@ def build(self) -> OscMessage:
"""
-
-
class Handler(object):
"""Wrapper for a callback function that will be called when an OSC message is sent to the right address.
@@ -1601,24 +1598,30 @@ class Handler(object):
message if any were passed.
"""
- def __init__(self, _callback: Callable, _args: Union[Any, List[Any]],
- _needs_reply_address: bool = False) -> None:
+ def __init__(
+ self,
+ _callback: Callable,
+ _args: Union[Any, List[Any]],
+ _needs_reply_address: bool = False,
+ ) -> None:
"""
Args:
_callback Function that is called when handler is invoked
_args: Message causing invocation
_needs_reply_address Whether the client's ip address shall be passed as an argument or not
- """
+ """
self.callback = _callback
self.args = _args
self.needs_reply_address = _needs_reply_address
# needed for test module
def __eq__(self, other: Any) -> bool:
- return (type(self) == type(other) and
- self.callback == other.callback and
- self.args == other.args and
- self.needs_reply_address == other.needs_reply_address)
+ return (
+ type(self) is type(other)
+ and self.callback == other.callback
+ and self.args == other.args
+ and self.needs_reply_address == other.needs_reply_address
+ )
def invoke(self, client_address: Tuple[str, int], message: OscMessage) -> None:
"""Invokes the associated callback function
@@ -1626,7 +1629,7 @@ def invoke(self, client_address: Tuple[str, int], message: OscMessage) -> None:
Args:
client_address: Address match that causes the invocation
message: Message causing invocation
- """
+ """
if self.needs_reply_address:
if self.args:
self.callback(client_address, message.address, self.args, *message)
@@ -1649,8 +1652,13 @@ def __init__(self) -> None:
self._map = collections.defaultdict(list) # type: DefaultDict[str, List[Handler]]
self._default_handler = None # type: Optional[Handler]
- def map(self, address: str, handler: Callable, *args: Union[Any, List[Any]],
- needs_reply_address: bool = False) -> Handler:
+ def map(
+ self,
+ address: str,
+ handler: Callable,
+ *args: Union[Any, List[Any]],
+ needs_reply_address: bool = False,
+ ) -> Handler:
"""Map an address to a handler
The callback function must have one of the following signatures:
@@ -1690,8 +1698,13 @@ def unmap(self, address: str, handler: Handler) -> None:
pass
@overload
- def unmap(self, address: str, handler: Callable, *args: Union[Any, List[Any]],
- needs_reply_address: bool = False) -> None:
+ def unmap(
+ self,
+ address: str,
+ handler: Callable,
+ *args: Union[Any, List[Any]],
+ needs_reply_address: bool = False,
+ ) -> None:
"""Remove an already mapped handler from an address
Args:
@@ -1710,12 +1723,19 @@ def unmap(self, address, handler, *args, needs_reply_address=False):
if isinstance(handler, Handler):
self._map[address].remove(handler)
else:
- self._map[address].remove(Handler(handler, list(args), needs_reply_address))
+ self._map[address].remove(
+ Handler(handler, list(args), needs_reply_address)
+ )
except ValueError as e:
if str(e) == "list.remove(x): x not in list":
- raise ValueError("Address '%s' doesn't have handler '%s' mapped to it" % (address, handler)) from e
-
- def handlers_for_address(self, address_pattern: str) -> Generator[Handler, None, None]:
+ raise ValueError(
+ "Address '%s' doesn't have handler '%s' mapped to it"
+ % (address, handler)
+ ) from e
+
+ def handlers_for_address(
+ self, address_pattern: str
+ ) -> Generator[Handler, None, None]:
"""Yields handlers matching an address
@@ -1730,27 +1750,31 @@ def handlers_for_address(self, address_pattern: str) -> Generator[Handler, None,
# Let's consider numbers and _ "characters" too here, it's not said
# explicitly in the specification but it sounds good.
escaped_address_pattern = re.escape(address_pattern)
- pattern = escaped_address_pattern.replace('\\?', '\\w?')
+ pattern = escaped_address_pattern.replace("\\?", "\\w?")
# '*' in the OSC Address Pattern matches any sequence of zero or more
# characters.
- pattern = pattern.replace('\\*', '[\w|\+]*')
+ pattern = pattern.replace("\\*", "[\w|\+]*")
# The rest of the syntax in the specification is like the re module so
# we're fine.
- pattern = pattern + '$'
+ pattern = pattern + "$"
patterncompiled = re.compile(pattern)
matched = False
for addr, handlers in self._map.items():
- if (patterncompiled.match(addr)
- or (('*' in addr) and re.match(addr.replace('*', '[^/]*?/*'), address_pattern))):
+ if patterncompiled.match(addr) or (
+ ("*" in addr)
+ and re.match(addr.replace("*", "[^/]*?/*"), address_pattern)
+ ):
yield from handlers
matched = True
if not matched and self._default_handler:
- logging.debug('No handler matched but default handler present, added it.')
+ logging.debug("No handler matched but default handler present, added it.")
yield self._default_handler
- def call_handlers_for_packet(self, data: bytes, client_address: Tuple[str, int]) -> None:
+ def call_handlers_for_packet(
+ self, data: bytes, client_address: Tuple[str, int]
+ ) -> None:
"""Invoke handlers for all messages in OSC packet
The incoming OSC Packet is decoded and the handlers for each included message is found and invoked.
@@ -1765,8 +1789,7 @@ def call_handlers_for_packet(self, data: bytes, client_address: Tuple[str, int])
packet = OscPacket(data)
for timed_msg in packet.messages:
now = time.time()
- handlers = self.handlers_for_address(
- timed_msg.message.address)
+ handlers = self.handlers_for_address(timed_msg.message.address)
if not handlers:
continue
# If the message is to be handled later, then so be it.
@@ -1777,7 +1800,9 @@ def call_handlers_for_packet(self, data: bytes, client_address: Tuple[str, int])
except ParseError:
pass
- def set_default_handler(self, handler: Callable, needs_reply_address: bool = False) -> None:
+ def set_default_handler(
+ self, handler: Callable, needs_reply_address: bool = False
+ ) -> None:
"""Sets the default handler
The default handler is invoked every time no other handler is mapped to an address.
@@ -1786,10 +1811,9 @@ def set_default_handler(self, handler: Callable, needs_reply_address: bool = Fal
handler: Callback function to handle unmapped requests
needs_reply_address: Whether the callback shall be passed the client address
"""
- self._default_handler = None if (handler is None) else Handler(handler, [], needs_reply_address)
-
-
-
+ self._default_handler = (
+ None if (handler is None) else Handler(handler, [], needs_reply_address)
+ )
### OSC SERVER ###
@@ -1798,7 +1822,6 @@ def set_default_handler(self, handler: Callable, needs_reply_address: bool = Fal
"""
-
_RequestType = Union[_socket, Tuple[bytes, _socket]]
_AddressType = Union[Tuple[str, int], str]
@@ -1824,28 +1847,35 @@ def _is_valid_request(request: _RequestType) -> bool:
Returns:
True if request is OSC bundle or OSC message
"""
- assert isinstance(request, tuple) # TODO: handle requests which are passed just as a socket?
+ assert isinstance(
+ request, tuple
+ ) # TODO: handle requests which are passed just as a socket?
data = request[0]
- return (
- OscBundle.dgram_is_bundle(data)
- or OscMessage.dgram_is_message(data))
+ return OscBundle.dgram_is_bundle(data) or OscMessage.dgram_is_message(data)
class OSCUDPServer(socketserver.UDPServer):
"""Superclass for different flavors of OSC UDP servers"""
- def __init__(self, server_address: Tuple[str, int], dispatcher: Dispatcher, bind_and_activate: bool = True) -> None:
+ def __init__(
+ self,
+ server_address: Tuple[str, int],
+ dispatcher: Dispatcher,
+ bind_and_activate: bool = True,
+ ) -> None:
"""Initialize
Args:
server_address: IP and port of server
dispatcher: Dispatcher this server will use
- (optional) bind_and_activate: default=True defines if the server has to start on call of constructor
+ (optional) bind_and_activate: default=True defines if the server has to start on call of constructor
"""
super().__init__(server_address, _UDPHandler, bind_and_activate)
self._dispatcher = dispatcher
- def verify_request(self, request: _RequestType, client_address: _AddressType) -> bool:
+ def verify_request(
+ self, request: _RequestType, client_address: _AddressType
+ ) -> bool:
"""Returns true if the data looks like a valid OSC UDP datagram
Args:
@@ -1880,6 +1910,7 @@ class ThreadingOSCUDPServer(socketserver.ThreadingMixIn, OSCUDPServer):
if hasattr(os, "fork"):
+
class ForkingOSCUDPServer(socketserver.ForkingMixIn, OSCUDPServer):
"""Forking version of the OSC UDP server.
@@ -1889,13 +1920,18 @@ class ForkingOSCUDPServer(socketserver.ForkingMixIn, OSCUDPServer):
"""
-class AsyncIOOSCUDPServer():
+class AsyncIOOSCUDPServer:
"""Asynchronous OSC Server
An asynchronous OSC Server using UDP. It creates a datagram endpoint that runs in an event loop.
"""
- def __init__(self, server_address: Tuple[str, int], dispatcher: Dispatcher, loop: BaseEventLoop) -> None:
+ def __init__(
+ self,
+ server_address: Tuple[str, int],
+ dispatcher: Dispatcher,
+ loop: BaseEventLoop,
+ ) -> None:
"""Initialize
Args:
@@ -1915,7 +1951,9 @@ class _OSCProtocolFactory(asyncio.DatagramProtocol):
def __init__(self, dispatcher: Dispatcher) -> None:
self.dispatcher = dispatcher
- def datagram_received(self, data: bytes, client_address: Tuple[str, int]) -> None:
+ def datagram_received(
+ self, data: bytes, client_address: Tuple[str, int]
+ ) -> None:
self.dispatcher.call_handlers_for_packet(data, client_address)
def serve(self) -> None:
@@ -1926,7 +1964,11 @@ def serve(self) -> None:
"""
self._loop.run_until_complete(self.create_serve_endpoint())
- def create_serve_endpoint(self) -> Coroutine[Any, Any, Tuple[asyncio.transports.BaseTransport, asyncio.DatagramProtocol]]:
+ def create_serve_endpoint(
+ self,
+ ) -> Coroutine[
+ Any, Any, Tuple[asyncio.transports.BaseTransport, asyncio.DatagramProtocol]
+ ]:
"""Creates a datagram endpoint and registers it with event loop as coroutine.
Returns:
@@ -1934,7 +1976,8 @@ def create_serve_endpoint(self) -> Coroutine[Any, Any, Tuple[asyncio.transports.
"""
return self._loop.create_datagram_endpoint(
lambda: self._OSCProtocolFactory(self.dispatcher),
- local_addr=self._server_address)
+ local_addr=self._server_address,
+ )
@property
def dispatcher(self) -> Dispatcher:
@@ -1946,12 +1989,16 @@ def dispatcher(self) -> Dispatcher:
"""UDP Clients for sending OSC messages to an OSC server"""
-from typing import Union
-
class UDPClient(object):
"""OSC client to send :class:`OscMessage` or :class:`OscBundle` via UDP"""
- def __init__(self, address: str, port: int, allow_broadcast: bool = False, family: socket.AddressFamily = socket.AF_UNSPEC) -> None:
+ def __init__(
+ self,
+ address: str,
+ port: int,
+ allow_broadcast: bool = False,
+ family: socket.AddressFamily = socket.AF_UNSPEC,
+ ) -> None:
"""Initialize client
As this is UDP it will not actually make any attempt to connect to the
@@ -1964,7 +2011,9 @@ def __init__(self, address: str, port: int, allow_broadcast: bool = False, famil
family: address family parameter (passed to socket.getaddrinfo)
"""
- for addr in socket.getaddrinfo(address, port, type=socket.SOCK_DGRAM, family=family):
+ for addr in socket.getaddrinfo(
+ address, port, type=socket.SOCK_DGRAM, family=family
+ ):
af, socktype, protocol, canonname, sa = addr
try:
diff --git a/scripts/sc.py b/scripts/sc.py
index d4941636..15de4b13 100644
--- a/scripts/sc.py
+++ b/scripts/sc.py
@@ -1,19 +1,24 @@
+import logging
import gremlin
import time
-import uuid
import atexit
-from gremlin.spline import CubicSpline
-from gremlin.input_devices import keyboard
from gremlin.macro import Macro, MacroManager
-from vjoy.vjoy import AxisName
-from configuration import * # load constants defining the devices connected to this PC
-from util import *
-from hardware import *
-import time
-import random
import threading
import gremlin.input_devices
+from configuration import (
+ RIGHT_VPC_Stick_WarBRD_mining,
+ LEFT_VPC_Stick_WarBRD_mining,
+ RIGHT_VPC_Stick_WarBRD_scan,
+ LEFT_VPC_Stick_WarBRD_scan,
+ RIGHT_VPC_Stick_WarBRD_Default,
+ LEFT_VPC_Stick_WarBRD_Default,
+ Bravo_Throttle_Quadrant_Default,
+ Throttle_HOTAS_Warthog_Default,
+ VPC_RIGHT_GUID,
+ MODE_DEFAULT,
+)
+from scripts.util import repeat_fire_cancel_all, ActiveMode, say, SetMode, RunMacro
# class RemoteState():
@@ -22,12 +27,11 @@
# is_broadcast = False
-class ProfileState():
- ''' holds state information for the profile '''
- is_running = False
+class ProfileState:
+ """holds state information for the profile"""
+ is_running = False
-import logging
syslog = logging.getLogger("system")
@@ -41,7 +45,7 @@ class ProfileState():
MOUSE_MOVE_DELTA_MIN = 1
MOUSE_MOVE_DELTA_SPAN = MOUSE_MOVE_DELTA_MAX - MOUSE_MOVE_DELTA_MIN
-''' axis names to ID
+""" axis names to ID
X # 1
Y # 2
@@ -52,7 +56,7 @@ class ProfileState():
SL0 # 7
SL1 # 8
-'''
+"""
# joystick button for scan mode functions
SCAN_JOY = 1
@@ -73,17 +77,35 @@ class ProfileState():
throttle = Throttle_HOTAS_Warthog_Default
-bump_a_index = 4 # should match the initial value on the axis
+bump_a_index = 4 # should match the initial value on the axis
# index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
-bump_a_table = [-1,-0.98,-0.96, -0.94, -0.9,-0.8,-0.75, -0.7, -0.6, -0.5, -0.3, 0.0, 0.5, 0.75,1.0]
+bump_a_table = [
+ -1,
+ -0.98,
+ -0.96,
+ -0.94,
+ -0.9,
+ -0.8,
+ -0.75,
+ -0.7,
+ -0.6,
+ -0.5,
+ -0.3,
+ 0.0,
+ 0.5,
+ 0.75,
+ 1.0,
+]
bump_a_max = len(bump_a_table)
gremlin.util.log("Custom SC module enabled")
-brake_threshold = 0.95 # value of deviation (+ or -) to reach to trigger brake action on twist
+brake_threshold = (
+ 0.95 # value of deviation (+ or -) to reach to trigger brake action on twist
+)
-''' MACRO DEFINITIONS '''
+""" MACRO DEFINITIONS """
# macro functions are: press(key), release(key), tap(key), pause(seconds)
brake_on_macro = Macro()
@@ -93,91 +115,90 @@ class ProfileState():
gear_toggle_macro = Macro()
-gear_toggle_macro.tap('N')
+gear_toggle_macro.tap("N")
mining_mode_toggle_macro = Macro()
-mining_mode_toggle_macro.tap('M')
+mining_mode_toggle_macro.tap("M")
scanning_mode_toggle_macro = Macro()
-scanning_mode_toggle_macro.tap('S')
+scanning_mode_toggle_macro.tap("S")
cruise_toggle_macro = Macro()
cruise_toggle_macro.tap("C")
-
-
-
flip_trigger = False
repeat_trigger_a = False
repeat_trigger_b = False
trigger_a_pressed = False
trigger_b_pressed = False
+
def exit_handler():
gremlin.util.log("sc exit!")
repeat_fire_cancel_all()
+
atexit.register(exit_handler)
# gets a bracket value for a given joystick value - will be the low or high bracket depending on direction
def get_value(direction, current):
- old_value = bump_a_table[1] # smallest value possible
+ old_value = bump_a_table[1] # smallest value possible
# gremlin.util.log(f"current joystick value: {current}")
for value in bump_a_table:
# gremlin.util.log(f"current bracket: [{old_value}, {value}]")
- if current >= old_value and ((direction > 0 and current < value) or (direction < 0 and current <= value)):
+ if current >= old_value and (
+ (direction > 0 and current < value) or (direction < 0 and current <= value)
+ ):
if direction > 0:
- # gremlin.util.log(f"return value: {value}")
+ # gremlin.util.log(f"return value: {value}")
return value
else:
# gremlin.util.log(f"return value: {old_value}")
return old_value
old_value = value
- # gremlin.util.log(f"no bracket found: return value: {value}")
+ # gremlin.util.log(f"no bracket found: return value: {value}")
return value
-
-
+
+
# cruise control
last_gear_status = False
+
def toggle_cruise():
- ''' toggles the cruise mode'''
- MacroManager().queue_macro(cruise_toggle_macro)
+ """toggles the cruise mode"""
+ MacroManager().queue_macro(cruise_toggle_macro)
+
def toggle_gear():
global last_gear_status
- MacroManager().queue_macro(gear_toggle_macro)
+ MacroManager().queue_macro(gear_toggle_macro)
if last_gear_status == -1 or last_gear_status == 0:
last_gear_status = 1
else:
last_gear_status = -1
-
-
@left_vpc.button(1)
def cruise_toggle(event, vjoy):
- MacroManager().queue_macro(cruise_toggle_macro)
-
+ MacroManager().queue_macro(cruise_toggle_macro)
-
-# handle acceleration presets
+
+# handle acceleration presets
def speed_limit_increase(event, vjoy):
-
if event.is_pressed:
(is_local, is_remote) = gremlin.input_devices.remote_state.state
global bump_a_table, bump_a_index
current = vjoy[1].axis(6).value
new_value = get_value(+1, current)
- #gremlin.util.log(f"increase speed: Current: {current} New {new_value}")
+ # gremlin.util.log(f"increase speed: Current: {current} New {new_value}")
vjoy[1].axis(6).value = new_value
if is_remote:
gremlin.input_devices.remote_client.send_axis(1, 6, new_value)
-
+
def speed_limit_decrease(event, vjoy):
if event.is_pressed:
@@ -185,12 +206,13 @@ def speed_limit_decrease(event, vjoy):
global bump_a_table, bump_a_index
current = vjoy[1].axis(6).value
new_value = get_value(-1, current)
- #gremlin.util.log(f"descrease speed: Current: {current} New {new_value}")
+ # gremlin.util.log(f"descrease speed: Current: {current} New {new_value}")
vjoy[1].axis(6).value = new_value
if is_remote:
gremlin.input_devices.remote_client.send_axis(1, 6, new_value)
+
def init_speed_limiter(vjoy):
(is_local, is_remote) = gremlin.input_devices.remote_state.state
global bump_a_table, bump_a_index
@@ -198,23 +220,27 @@ def init_speed_limiter(vjoy):
vjoy[1].axis(6).value = current
gremlin.input_devices.remote_client.send_axis(1, 6, current)
+
@left_vpc.button(24)
def speed_inc_a(event, vjoy):
speed_limit_increase(event, vjoy)
-
+
+
# @left_vpc.button(26)
# def speed_inc_b(event, vjoy):
- # speed_limit_increase(event, vjoy)
+# speed_limit_increase(event, vjoy)
+
@left_vpc.button(23)
def speed_dec_a(event, vjoy):
speed_limit_decrease(event, vjoy)
+
# @left_vpc.button(28)
# def speed_dec_b(event, vjoy):
- # speed_limit_decrease(event, vjoy)
+# speed_limit_decrease(event, vjoy)
-# acceleration presets
+# acceleration presets
def acc_limit_increase(event, vjoy):
@@ -223,14 +249,12 @@ def acc_limit_increase(event, vjoy):
global bump_a_table, bump_a_index
current = vjoy[1].axis(7).value
new_value = get_value(+1, current)
- #gremlin.util.log(f"increase acc: Current: {current} New {new_value}")
+ # gremlin.util.log(f"increase acc: Current: {current} New {new_value}")
vjoy[1].axis(7).value = new_value
if is_remote:
gremlin.input_devices.remote_client.send_axis(1, 7, new_value)
-
-
-
+
def acc_limit_decrease(event, vjoy):
if event.is_pressed:
@@ -238,84 +262,90 @@ def acc_limit_decrease(event, vjoy):
global bump_a_table, bump_a_index
current = vjoy[1].axis(7).value
new_value = get_value(-1, current)
- #gremlin.util.log(f"decrease acc: Current: {current} New {new_value}")
+ # gremlin.util.log(f"decrease acc: Current: {current} New {new_value}")
vjoy[1].axis(7).value = new_value
if is_remote:
gremlin.input_devices.remote_client.send_axis(1, 7, new_value)
-
-
# @right_vpc.button(24)
# def acc_inc_a(event, vjoy):
- # acc_limit_increase(event, vjoy)
+# acc_limit_increase(event, vjoy)
+
@left_vpc.button(27)
def acc_inc_b(event, vjoy):
- acc_limit_increase(event, vjoy)
+ acc_limit_increase(event, vjoy)
+
# @right_vpc.button(23)
# def acc_dec_a(event, vjoy):
- # acc_limit_decrease(event, vjoy)
+# acc_limit_decrease(event, vjoy)
+
@left_vpc.button(29)
def acc_dec_b(event, vjoy):
- acc_limit_decrease(event, vjoy)
+ acc_limit_decrease(event, vjoy)
@left_vpc_scan.button(27)
def acc_inc_b1(event, vjoy):
- acc_limit_increase(event, vjoy)
+ acc_limit_increase(event, vjoy)
+
# @right_vpc.button(23)
# def acc_dec_a(event, vjoy):
- # acc_limit_decrease(event, vjoy)
+# acc_limit_decrease(event, vjoy)
+
@left_vpc_scan.button(29)
def acc_dec_b1(event, vjoy):
- acc_limit_decrease(event, vjoy)
+ acc_limit_decrease(event, vjoy)
+
-last_rot_value = -2 # invalid value
+last_rot_value = -2 # invalid value
-brake_on = False # not braking by default
+brake_on = False # not braking by default
+
@left_vpc.axis(3)
def space_brake(event, vjoy):
global brake_on
brake_threshold = 0.8
- value = abs(event.value)
- if not brake_on and value >= brake_threshold:
+ value = abs(event.value)
+ if not brake_on and value >= brake_threshold:
brake_on = True
- MacroManager().queue_macro(brake_on_macro)
-
+ MacroManager().queue_macro(brake_on_macro)
+
elif brake_on and value < brake_threshold:
MacroManager().queue_macro(brake_off_macro)
-
+
brake_on = False
# stick z rotation (#6 is the rudder for some reason) = brake
-#@t16k.axis(6)
+# @t16k.axis(6)
def speed_brake(event, vjoy):
global last_rot_value, brake_on, brake_threshold
if event.value != last_rot_value:
last_rot_value = event.value
- value = abs(event.value)
- if not brake_on and value >= brake_threshold:
+ value = abs(event.value)
+ if not brake_on and value >= brake_threshold:
brake_on = True
- MacroManager().queue_macro(brake_on_macro)
-
+ MacroManager().queue_macro(brake_on_macro)
+
elif brake_on and value < brake_threshold:
- MacroManager().queue_macro(brake_off_macro)
+ MacroManager().queue_macro(brake_off_macro)
brake_on = False
def update_mouse(vjoy):
global mouse_dx, mouse_dy, is_mouse_mode
- if is_mouse_mode: # and (mouse_dx != 0 or mouse_dy != 0):
- gremlin.sendinput.mouse_relative_motion(mouse_dx,mouse_dy)
+ if is_mouse_mode: # and (mouse_dx != 0 or mouse_dy != 0):
+ gremlin.sendinput.mouse_relative_motion(mouse_dx, mouse_dy)
+
@gremlin.input_devices.periodic(0.05)
def periodic_function(vjoy):
@@ -323,11 +353,9 @@ def periodic_function(vjoy):
return
-
-
-''' scan mode axis options for mouse thumb '''
-KEY_SCAN_DECREASE = ','
-KEY_SCAN_INCREASE = '.'
+""" scan mode axis options for mouse thumb """
+KEY_SCAN_DECREASE = ","
+KEY_SCAN_INCREASE = "."
scan_increase_macro = Macro()
scan_increase_macro.press(KEY_SCAN_INCREASE)
@@ -347,24 +375,22 @@ def periodic_function(vjoy):
last_thumb_y = 0
# current mouse delta to send
-mouse_dx = 0
+mouse_dx = 0
mouse_dy = 0
+
# initial mouse setup
def sync_initial_state():
- # get a reference to vjoy devices
- vjoy = gremlin.joystick_handling.VJoyProxy()
# get a reference to actual raw hardware devices
joy = gremlin.input_devices.JoystickProxy()
try:
-
r_vpc = joy[gremlin.profile.parse_guid(VPC_RIGHT_GUID)]
global is_mouse_mode
is_mouse_mode = r_vpc.button(1).is_pressed
- except:
- pass
+ except Exception as e:
+ print("Error getting joystick state: ", e)
# mouse modes
@@ -378,21 +404,21 @@ def mouse_mode(event, vjoy):
say("mouse mode off")
-
-
STICK_THRESHOLD = 0.1
STICK_THRESHOLD_SPAN = 1 - STICK_THRESHOLD
-@right_vpc.axis(4) # x axis thumb
+
+@right_vpc.axis(4) # x axis thumb
def thumb_x(event, vjoy):
global is_mouse_mode, last_thumb_x, mouse_dx, mouse_dy
if is_mouse_mode:
value = event.value
else:
- value = 0
+ value = 0
vjoy[3].axis(1).value = value
-
-@right_vpc.axis(5) # y axis thumb
+
+
+@right_vpc.axis(5) # y axis thumb
def thumb_y(event, vjoy):
global is_mouse_mode, last_thumb_x, mouse_dx, mouse_dy
if is_mouse_mode:
@@ -403,7 +429,7 @@ def thumb_y(event, vjoy):
vjoy[3].axis(2).value = value
-''' MODE CHANGES '''
+""" MODE CHANGES """
MODE_CHANGE_DELAY_SECONDS = 0.5
mining_mode_timer = None
@@ -412,49 +438,55 @@ def thumb_y(event, vjoy):
last_mode = MODE_DEFAULT
-def set_mining_mode(quiet = False):
- ''' enter mining mode '''
+def set_mining_mode(quiet=False):
+ """enter mining mode"""
global mining_throttle_value, last_mode
mode = ActiveMode()
-
+
if mode == MODE_MINING:
- if not quiet: say ("mining mode off")
+ if not quiet:
+ say("mining mode off")
SetMode(MODE_DEFAULT)
last_mode = MODE_DEFAULT
else:
- if not quiet: say("mining mode")
+ if not quiet:
+ say("mining mode")
SetMode(MODE_MINING)
last_mode = MODE_MINING
RunMacro(mining_mode_toggle_macro)
mining_throttle_value = -1.0
-def set_scan_mode(quiet = False):
- ''' enter scanning mode '''
+
+def set_scan_mode(quiet=False):
+ """enter scanning mode"""
global last_mode
mode = ActiveMode()
if mode == MODE_SCAN:
- if not quiet: say ("scan mode off")
+ if not quiet:
+ say("scan mode off")
SetMode(MODE_DEFAULT)
last_mode = MODE_DEFAULT
else:
- if not quiet: say("scan mode")
+ if not quiet:
+ say("scan mode")
SetMode(MODE_SCAN)
last_mode = MODE_SCAN
RunMacro(scanning_mode_toggle_macro)
-
+
def set_default_mode(quiet=False):
- ''' enter default mode '''
+ """enter default mode"""
global last_mode
SetMode(MODE_DEFAULT)
- if not quiet: say("default mode")
+ if not quiet:
+ say("default mode")
last_mode = MODE_DEFAULT
def set_mode(mode):
- ''' general set mode '''
+ """general set mode"""
global last_mode
active_mode = ActiveMode()
# if mode == active_mode or mode == last_mode:
@@ -481,9 +513,6 @@ def set_mode(mode):
elif mode == MODE_SCAN:
set_scan_mode()
-
-
-
# left stick, right hat down = toggle mining mode
@left_vpc.button(16)
@@ -493,10 +522,13 @@ def mining_mode(event, vjoy):
mining_mode_timer.cancel()
mining_mode_timer = None
if event.is_pressed:
- # button down, kickoff delay timer
- mining_mode_timer = threading.Timer(MODE_CHANGE_DELAY_SECONDS, set_mode, [MODE_MINING])
+ # button down, kickoff delay timer
+ mining_mode_timer = threading.Timer(
+ MODE_CHANGE_DELAY_SECONDS, set_mode, [MODE_MINING]
+ )
mining_mode_timer.start()
+
# left stick, right hat up = toggle scanning mode
@left_vpc.button(14)
def scanning_mode(event, vjoy):
@@ -505,8 +537,10 @@ def scanning_mode(event, vjoy):
scan_mode_timer.cancel()
scan_mode_timer = None
if event.is_pressed:
- # button down, kickoff delay timer
- scan_mode_timer = threading.Timer(MODE_CHANGE_DELAY_SECONDS, set_mode, [MODE_SCAN])
+ # button down, kickoff delay timer
+ scan_mode_timer = threading.Timer(
+ MODE_CHANGE_DELAY_SECONDS, set_mode, [MODE_SCAN]
+ )
scan_mode_timer.start()
@@ -518,23 +552,27 @@ def default_mode(event, vjoy):
default_mode_timer.cancel()
default_mode_timer = None
if event.is_pressed:
- # button down, kickoff delay timer
- default_mode_timer = threading.Timer(MODE_CHANGE_DELAY_SECONDS, set_mode, [MODE_DEFAULT])
+ # button down, kickoff delay timer
+ default_mode_timer = threading.Timer(
+ MODE_CHANGE_DELAY_SECONDS, set_mode, [MODE_DEFAULT]
+ )
default_mode_timer.start()
-
LASER_BUMP_SLOW = 0.008
LASER_BUMP_FAST = 0.02
LASER_BUMP_THUMB_THRESHOLD = 0.1 # trigger
-LASER_BUMP_THUMB_TURBO = 0.6 # fast mode
+LASER_BUMP_THUMB_TURBO = 0.6 # fast mode
laser_bump = 0.01
last_thumstick_position = 0
-class laser_data():
+
+
+class laser_data:
go_up = False
go_down = False
-
+
+
def update_laser(vjoy):
if laser_data.go_up or laser_data.go_down:
global laser_bump
@@ -551,25 +589,25 @@ def update_laser(vjoy):
elif current < -1:
current = -1
vjoy[2].axis(7).value = current
-
-
-@left_vpc.button(26)
+
+
+@left_vpc.button(26)
def scc_laser_increase(event, vjoy):
- ''' button to decrease laser '''
+ """button to decrease laser"""
mode = ActiveMode()
if mode != MODE_MINING:
laser_data.go_up = False
laser_data.go_down = False
- return
+ return
laser_data.go_up = event.is_pressed
laser_data.go_down = False
global laser_bump
laser_bump = LASER_BUMP_SLOW
-
-
-@left_vpc.button(28)
+
+
+@left_vpc.button(28)
def scc_laser_decrease(event, vjoy):
- ''' button in increase laser'''
+ """button in increase laser"""
mode = ActiveMode()
if mode != MODE_MINING:
laser_data.go_down = False
@@ -579,16 +617,14 @@ def scc_laser_decrease(event, vjoy):
laser_data.go_down = event.is_pressed
laser_data.go_up = False
global laser_bump
- laser_bump = LASER_BUMP_SLOW
-
-
+ laser_bump = LASER_BUMP_SLOW
@left_vpc.axis(5)
def mining_throttle(event, vjoy):
- ''' sets up a mining throttle on the left thumbstick up/down '''
+ """sets up a mining throttle on the left thumbstick up/down"""
mode = ActiveMode()
- if mode == MODE_MINING:
+ if mode == MODE_MINING:
global last_thumstick_position
value = event.value
# if value == last_thumstick_position:
@@ -600,7 +636,7 @@ def mining_throttle(event, vjoy):
elif value <= -LASER_BUMP_THUMB_THRESHOLD:
laser_data.go_down = False
laser_data.go_up = True
- else:
+ else:
laser_data.go_down = False
laser_data.go_up = False
@@ -613,23 +649,21 @@ def mining_throttle(event, vjoy):
else:
if value < 0:
value = -value
- laser_bump = LASER_BUMP_SLOW if value < LASER_BUMP_THUMB_TURBO else LASER_BUMP_FAST
-
-
+ laser_bump = (
+ LASER_BUMP_SLOW if value < LASER_BUMP_THUMB_TURBO else LASER_BUMP_FAST
+ )
-''' custom wiggle modes for SC '''
+""" custom wiggle modes for SC """
-class Wiggle():
- """Implements more advanced wiggle functionality using macro functionality """
+class Wiggle:
+ """Implements more advanced wiggle functionality using macro functionality"""
-
def __init__(self):
# setup wiggle steps
Wiggle._mouse_controller = gremlin.sendinput.MouseController()
-
self._wiggle_local_thread = None
self._wiggle_remote_thread = None
self._wiggle_local_stop_requested = None
@@ -639,23 +673,22 @@ def __init__(self):
self._current_local_step = 0
self._current_remote_step = 0
-
# define the wiggle macro for Star Citizen
# wiggle the mouse
- self._steps.append(self.get_mouse_move_macro(100,0))
- self._steps.append(self.get_mouse_move_macro(-100,0))
- self._steps.append(self.get_mouse_move_macro(0,0))
- # send the D key - press between 1 to 3 seconds
+ self._steps.append(self.get_mouse_move_macro(100, 0))
+ self._steps.append(self.get_mouse_move_macro(-100, 0))
+ self._steps.append(self.get_mouse_move_macro(0, 0))
+ # send the D key - press between 1 to 3 seconds
self._steps.append(self.get_key_macro("d"))
- # pause between 2 and 10 seconds
+ # pause between 2 and 10 seconds
self._steps.append(self.get_pause_macro(2, 10))
- # send the A key
+ # send the A key
self._steps.append(self.get_key_macro("a"))
# pause between 2 and 10 seconds
self._steps.append(self.get_pause_macro(2, 10))
# press F1
- self._steps.append(self.get_key_macro("f1",0.5))
+ self._steps.append(self.get_key_macro("f1", 0.5))
# pause between 2 and 10 seconds
self._steps.append(self.get_pause_macro(2, 10))
# press F1 again
@@ -680,39 +713,38 @@ def __del__(self):
self.stop_local()
self.stop_remote()
-
@property
def force_remote(self):
- ''' flag that forces remote mode on even if mode is local '''
+ """flag that forces remote mode on even if mode is local"""
return self._force_remote
-
+
@force_remote.setter
def force_remote(self, value):
self._force_remote = value
-
-
- def get_key_macro(self, name, duration_min = 0.3, duration_max = 0):
- ''' gets a keyboard macro '''
+ def get_key_macro(self, name, duration_min=0.3, duration_max=0):
+ """gets a keyboard macro"""
m = Macro()
k = gremlin.macro.Key.from_key(gremlin.keyboard.key_from_name(name))
if k:
- a = gremlin.macro.KeyAction(k,True)
+ a = gremlin.macro.KeyAction(k, True)
m.add_action(a)
- a = gremlin.macro.PauseAction(duration=duration_min, duration_max = duration_max)
+ a = gremlin.macro.PauseAction(
+ duration=duration_min, duration_max=duration_max
+ )
m.add_action(a)
- a = gremlin.macro.KeyAction(k,False)
+ a = gremlin.macro.KeyAction(k, False)
m.add_action(a)
return m
-
- def get_pause_macro(self, duration_min = 2, duration_max = 0):
+
+ def get_pause_macro(self, duration_min=2, duration_max=0):
m = Macro()
- a = gremlin.macro.PauseAction(duration=duration_min, duration_max = duration_max)
+ a = gremlin.macro.PauseAction(duration=duration_min, duration_max=duration_max)
m.add_action(a)
- return m
+ return m
- def get_mouse_move_macro(self, x = 0, y = 0):
- ''' returns a mouse move macro '''
+ def get_mouse_move_macro(self, x=0, y=0):
+ """returns a mouse move macro"""
m = Macro()
a = gremlin.macro.MouseMotionAction(x, y)
m.add_action(a)
@@ -720,9 +752,8 @@ def get_mouse_move_macro(self, x = 0, y = 0):
m.add_action(a)
return m
-
def start_local(self):
- # starts local wiggle
+ # starts local wiggle
self._wiggle_start(is_local=True)
def start_remote(self):
@@ -732,30 +763,31 @@ def start_remote(self):
def stop_local(self):
# stops remote wiggle
self._wiggle_stop(is_local=True)
-
+
def stop_remote(self):
# stops remote wiggle
self._wiggle_stop(is_remote=True)
@property
def local_running(self):
- ''' true if the local thread is running '''
+ """true if the local thread is running"""
return self._wiggle_local_stop_requested is not None
-
@property
def remote_running(self):
- ''' true if the remote thread is running '''
+ """true if the remote thread is running"""
return self._wiggle_remote_stop_requested is not None
- def _wiggle_start(self, is_local = False, is_remote = False):
- ''' starts the wiggle thread, local or remote '''
+ def _wiggle_start(self, is_local=False, is_remote=False):
+ """starts the wiggle thread, local or remote"""
if is_local and not self.local_running:
syslog.debug("Wiggle start local requested...")
self._current_local_step = 0
with self._lock:
self._wiggle_local_stop_requested = threading.Event()
- self._wiggle_local_thread = threading.Thread(target=self._wiggle_local, daemon=True)
+ self._wiggle_local_thread = threading.Thread(
+ target=self._wiggle_local, daemon=True
+ )
self._wiggle_local_thread.start()
if is_remote and not self.remote_running:
@@ -763,11 +795,13 @@ def _wiggle_start(self, is_local = False, is_remote = False):
self._current_remote_step = 0
with self._lock:
self._wiggle_remote_stop_requested = threading.Event()
- self._wiggle_remote_thread = threading.Thread(target=self._wiggle_remote, daemon=True)
+ self._wiggle_remote_thread = threading.Thread(
+ target=self._wiggle_remote, daemon=True
+ )
self._wiggle_remote_thread.start()
- def _wiggle_stop(self, is_local = False, is_remote = False):
- ''' stops the wiggle thread, local or remote '''
+ def _wiggle_stop(self, is_local=False, is_remote=False):
+ """stops the wiggle thread, local or remote"""
if is_local and self.local_running:
syslog.debug("Wiggle stop local requested...")
with self._lock:
@@ -785,15 +819,14 @@ def _wiggle_stop(self, is_local = False, is_remote = False):
if self._wiggle_remote_thread.is_alive():
self._wiggle_remote_thread.join()
syslog.debug("Wiggle thread remote exited...")
- self._wiggle_remote_thread = None
+ self._wiggle_remote_thread = None
self._wiggle_remote_stop_requested = None
def _wiggle_local(self):
- ''' wiggles the mouse '''
+ """wiggles the mouse"""
syslog.debug("Wiggle local start...")
-
msg = "local wiggle mode on"
gremlin.input_devices.remote_state.say(msg)
@@ -810,13 +843,12 @@ def _wiggle_local(self):
action(True, False, self.force_remote)
time.sleep(0.1)
time.sleep(0.1)
-
+
syslog.debug("Wiggle local stop...")
gremlin.input_devices.remote_state.say("local wiggle mode off")
-
def _wiggle_remote(self):
- ''' wiggles the mouse - remote clients'''
+ """wiggles the mouse - remote clients"""
syslog.debug("Wiggle remote start...")
msg = "remote wiggle mode on"
@@ -835,13 +867,14 @@ def _wiggle_remote(self):
action(False, True, self.force_remote)
time.sleep(0.1)
time.sleep(0.1)
-
+
syslog.debug("Wiggle remote stop...")
gremlin.input_devices.remote_state.say("remote wiggle mode off")
wiggle = Wiggle()
-wiggle.force_remote = True # force remote mode wiggle
+wiggle.force_remote = True # force remote mode wiggle
+
# external wiggle
@throttle.button(24)
@@ -851,6 +884,7 @@ def ext_wiggle(event):
else:
wiggle.stop_remote()
+
# external wiggle
@throttle.button(25)
def local_wiggle(event):
@@ -862,11 +896,12 @@ def local_wiggle(event):
@gremlin.input_devices.gremlin_state()
def state_changed(event):
- ''' called when Gremlin's state changes '''
+ """called when Gremlin's state changes"""
# remote_state.is_remote = event.is_remote
# remote_state.is_local = event.is_local
# remote_state.is_broadcast = event.is_broadcast_enabled
+
# init function when profile is starting
@gremlin.input_devices.gremlin_start()
def profile_start():
diff --git a/scripts/util.py b/scripts/util.py
index 99006dad..3c21ad1e 100644
--- a/scripts/util.py
+++ b/scripts/util.py
@@ -1,153 +1,166 @@
-
-''' MUCHIMI'S Joystick Gremlin Python utilitites '''
+"""MUCHIMI'S Joystick Gremlin Python utilitites"""
import gremlin
-
import time
import threading
from threading import Timer
-
-from gremlin.spline import CubicSpline
-from vjoy.vjoy import AxisName
-from configuration import * # load constants from the configuration.py file
import gremlin.input_devices
+from configuration import LONG_PULSE
-''' event handlers '''
+""" event handlers """
custom_handlers = []
+
def register_handler(callback):
- if not callback in custom_handlers:
- custom_handlers.append(callback)
+ if callback not in custom_handlers:
+ custom_handlers.append(callback)
+
def unregister_handler(callback):
- if callback in custom_handlers:
- custom_handlers.remove(callback)
+ if callback in custom_handlers:
+ custom_handlers.remove(callback)
+
def fire_event(cmd, value):
- ''' calls the registered handlers '''
- for callback in custom_handlers:
- callback(cmd, value)
+ """calls the registered handlers"""
+ for callback in custom_handlers:
+ callback(cmd, value)
+
timer_threads = {}
-class Speech():
- ''' tts interface '''
- def __init__(self):
- import win32com.client
- self.speaker = win32com.client.Dispatch("SAPI.SpVoice")
- def speak(self, text):
- try:
- self.speaker.speak(text)
- except:
- pass
+class Speech:
+ """tts interface"""
+
+ def __init__(self):
+ import win32com.client
+
+ self.speaker = win32com.client.Dispatch("SAPI.SpVoice")
+
+ def speak(self, text):
+ try:
+ self.speaker.speak(text)
+ except Exception as e:
+ print(f"Error: {e}")
+
def say(message):
- ''' say something using text to speech'''
- speech = Speech()
- speech.speak(message)
+ """say something using text to speech"""
+ speech = Speech()
+ speech.speak(message)
class RepeatedTimer(object):
- def __init__(self, interval, function, *args, **kwargs):
- self._timer = None
- self.interval = interval
- self.function = function
- self.args = args
- self.kwargs = kwargs
- self.is_running = False
-
- self.start()
-
- def _run(self):
- self.is_running = False
- #gremlin.util.log("run 1")
- self.function(*self.args, **self.kwargs)
- #gremlin.util.log("run 2")
- self.start()
-
-
- def start(self):
- if not self.is_running:
- self._timer = Timer(self.interval, self._run)
- self._timer.start()
- self.is_running = True
-
- def stop(self):
- self._timer.cancel()
- self._timer.join()
- self.is_running = False
- gremlin.util.log("stop")
-
-
-
+ def __init__(self, interval, function, *args, **kwargs):
+ self._timer = None
+ self.interval = interval
+ self.function = function
+ self.args = args
+ self.kwargs = kwargs
+ self.is_running = False
+
+ self.start()
+
+ def _run(self):
+ self.is_running = False
+ # gremlin.util.log("run 1")
+ self.function(*self.args, **self.kwargs)
+ # gremlin.util.log("run 2")
+ self.start()
+
+ def start(self):
+ if not self.is_running:
+ self._timer = Timer(self.interval, self._run)
+ self._timer.start()
+ self.is_running = True
+
+ def stop(self):
+ self._timer.cancel()
+ self._timer.join()
+ self.is_running = False
+ gremlin.util.log("stop")
+
+
# async routine to pulse a button
-def _fire_pulse(vjoy, unit, button, repeat = 1, duration = 0.2):
- if repeat < 0:
- repeat = -repeat
- for i in range(repeat):
- # gremlin.util.log("Pulsing vjoy %s button %s on" % (unit, button) )
- vjoy[unit].button(button).is_pressed = True
- time.sleep(duration)
- vjoy[unit].button(button).is_pressed = False
- time.sleep(duration)
- else:
- if repeat <= 1:
- gremlin.util.log(f"Pulsing vjoy {unit} button {button} on" )
- vjoy[unit].button(button).is_pressed = True
- time.sleep(duration)
- vjoy[unit].button(button).is_pressed = False
- else:
- vjoy[unit].button(button).is_pressed = True
- time.sleep(duration*repeat)
- vjoy[unit].button(button).is_pressed = False
-
- # gremlin.util.log("Pulsing vjoy %s button %s off" % (unit, button) )
+def _fire_pulse(vjoy, unit, button, repeat=1, duration=0.2):
+ if repeat < 0:
+ repeat = -repeat
+ for i in range(repeat):
+ # gremlin.util.log("Pulsing vjoy %s button %s on" % (unit, button) )
+ vjoy[unit].button(button).is_pressed = True
+ time.sleep(duration)
+ vjoy[unit].button(button).is_pressed = False
+ time.sleep(duration)
+ else:
+ if repeat <= 1:
+ gremlin.util.log(f"Pulsing vjoy {unit} button {button} on")
+ vjoy[unit].button(button).is_pressed = True
+ time.sleep(duration)
+ vjoy[unit].button(button).is_pressed = False
+ else:
+ vjoy[unit].button(button).is_pressed = True
+ time.sleep(duration * repeat)
+ vjoy[unit].button(button).is_pressed = False
+
+ # gremlin.util.log("Pulsing vjoy %s button %s off" % (unit, button) )
+
# pulses a button - unit is the vjoy output device number, button is the number of the button on the device to pulse
-def pulse(vjoy, unit, button, duration = 0.2, repeat = 1):
- gremlin.util.log(f"pulsing: unit {unit} button {button}")
- threading.Timer(0.01, _fire_pulse, [vjoy, unit, button, repeat, duration]).start()
+def pulse(vjoy, unit, button, duration=0.2, repeat=1):
+ gremlin.util.log(f"pulsing: unit {unit} button {button}")
+ threading.Timer(0.01, _fire_pulse, [vjoy, unit, button, repeat, duration]).start()
+
-
# gets the last timer tick for a unit/button - creates entries if needed
last_click = {}
-def get_tick(unit,button):
- global last_click
- if not unit in last_click.keys():
- last_click[unit] = {}
-
- if not button in last_click[unit]:
- last_click[unit][button] = 0
-
- return last_click[unit][button]
-
-
-
-
-# performs a slow or fast click depending on the last time the button fired
+
+
+def get_tick(unit, button):
+ global last_click
+ if unit not in last_click.keys():
+ last_click[unit] = {}
+
+ if button not in last_click[unit]:
+ last_click[unit][button] = 0
+
+ return last_click[unit][button]
+
+
+# performs a slow or fast click depending on the last time the button fired
# ref_unit = source unit clicked
# ref_button = source button on unit
# unit = vjoy device to pulse
# slow_button = vjoy button to pulse for slow rotation
# fast_button = vjoy button to pulse for fast rotation
-def speed_click(vjoy, ref_unit, ref_button, unit, slow_button, fast_button, use_fast, duration = 0.1, repeat = 1):
- if use_fast:
- t1 = time.clock()
- t0 = get_tick(ref_unit,ref_button)
- gremlin.util.log("delta %s repeat %s " % (t1-t0, repeat) )
- last_click[ref_unit][ref_button] = t1
- if t1 - t0 < LONG_PULSE:
- pulse(vjoy, unit, fast_button, duration, repeat)
- gremlin.util.log("fast")
- else:
- pulse(vjoy, unit, slow_button, duration, repeat)
- gremlin.util.log("slow")
- else:
- pulse(vjoy, unit, slow_button, duration, repeat)
- gremlin.util.log("use slow button")
+def speed_click(
+ vjoy,
+ ref_unit,
+ ref_button,
+ unit,
+ slow_button,
+ fast_button,
+ use_fast,
+ duration=0.1,
+ repeat=1,
+):
+ if use_fast:
+ t1 = time.clock()
+ t0 = get_tick(ref_unit, ref_button)
+ gremlin.util.log("delta %s repeat %s " % (t1 - t0, repeat))
+ last_click[ref_unit][ref_button] = t1
+ if t1 - t0 < LONG_PULSE:
+ pulse(vjoy, unit, fast_button, duration, repeat)
+ gremlin.util.log("fast")
+ else:
+ pulse(vjoy, unit, slow_button, duration, repeat)
+ gremlin.util.log("slow")
+ else:
+ pulse(vjoy, unit, slow_button, duration, repeat)
+ gremlin.util.log("use slow button")
+
# fires specified button - either stead or pulse
# event = gremlin event
@@ -156,110 +169,124 @@ def speed_click(vjoy, ref_unit, ref_button, unit, slow_button, fast_button, use_
# on_button = button to fire when button is in the ON position (steady or pulse)
# off_button = button to fire when button is in the OFF position (steady or pulse)
# pulse = flag, when true, pulses, when false, steady output
-def fireButton(event, vjoy, unit, on_button, off_button = None, momentary = False, pulse_duration = 0.2):
- if not on_button:
- return
-
- if momentary:
- if event.is_pressed:
- pulse(vjoy, unit, on_button, pulse_duration)
- if off_button:
- vjoy[unit].button(off_button).is_pressed = False
- #gremlin.util.log("device %s button %s pulse" % (unit, on_button))
- elif off_button:
- pulse(vjoy, unit, off_button, pulse_duration)
- vjoy[unit].button(on_button).is_pressed = False
- #gremlin.util.log("device %s button %s pulse" % (unit, off_button))
- else:
- if event.is_pressed:
- vjoy[unit].button(on_button).is_pressed = True
- if off_button:
- vjoy[unit].button(off_button).is_pressed = False
- else:
- vjoy[unit].button(on_button).is_pressed = False
- if off_button:
- vjoy[unit].button(off_button).is_pressed = True
-
-
-def pulseButton(event, vjoy, unit, on_button, off_button = None, pulse_duration = 0.2):
- fireButton(event, vjoy, unit, on_button, off_button, True, pulse_duration)
+def fireButton(
+ event, vjoy, unit, on_button, off_button=None, momentary=False, pulse_duration=0.2
+):
+ if not on_button:
+ return
+
+ if momentary:
+ if event.is_pressed:
+ pulse(vjoy, unit, on_button, pulse_duration)
+ if off_button:
+ vjoy[unit].button(off_button).is_pressed = False
+ # gremlin.util.log("device %s button %s pulse" % (unit, on_button))
+ elif off_button:
+ pulse(vjoy, unit, off_button, pulse_duration)
+ vjoy[unit].button(on_button).is_pressed = False
+ # gremlin.util.log("device %s button %s pulse" % (unit, off_button))
+ else:
+ if event.is_pressed:
+ vjoy[unit].button(on_button).is_pressed = True
+ if off_button:
+ vjoy[unit].button(off_button).is_pressed = False
+ else:
+ vjoy[unit].button(on_button).is_pressed = False
+ if off_button:
+ vjoy[unit].button(off_button).is_pressed = True
+
+
+def pulseButton(event, vjoy, unit, on_button, off_button=None, pulse_duration=0.2):
+ fireButton(event, vjoy, unit, on_button, off_button, True, pulse_duration)
def repeat_pulse(vjoy, unit, button, repeat, duration):
- gremlin.util.log(f"pulsing: unit {unit} button {button}")
-
- #threading.Timer(0.01, fire_pulse, [vjoy, unit, button, repeat, duration]).start()
-
-def repeat_fire(name, vjoy, unit, button, repeat=1, interval = 0.5, duration = 0.2):
- global timer_threads
- repeat_fire_cancel(name)
- gremlin.util.log(f"repeat {name} unit {unit} button {button} repeat {repeat} interval {interval} duration {duration}")
- #args = (vjoy, unit, button, repeat, duration)
- rt = RepeatedTimer(interval, pulse, vjoy, unit, button, repeat, duration )
- timer_threads[name] = rt
-
+ gremlin.util.log(f"pulsing: unit {unit} button {button}")
+
+ # threading.Timer(0.01, fire_pulse, [vjoy, unit, button, repeat, duration]).start()
+
+
+def repeat_fire(name, vjoy, unit, button, repeat=1, interval=0.5, duration=0.2):
+ global timer_threads
+ repeat_fire_cancel(name)
+ gremlin.util.log(
+ f"repeat {name} unit {unit} button {button} repeat {repeat} interval {interval} duration {duration}"
+ )
+ # args = (vjoy, unit, button, repeat, duration)
+ rt = RepeatedTimer(interval, pulse, vjoy, unit, button, repeat, duration)
+ timer_threads[name] = rt
+
+
def repeat_fire_cancel(name):
- global timer_threads
- gremlin.util.log(f"cancel {name}")
- if name in timer_threads:
- rt = timer_threads[name]
- rt.stop()
- del timer_threads[name]
-
+ global timer_threads
+ gremlin.util.log(f"cancel {name}")
+ if name in timer_threads:
+ rt = timer_threads[name]
+ rt.stop()
+ del timer_threads[name]
+
+
def repeat_fire_cancel_all():
- global timer_threads
- for name in timer_threads:
- rt = timer_threads[name]
- rt.stop()
+ global timer_threads
+ for name in timer_threads:
+ rt = timer_threads[name]
+ rt.stop()
# gets the current Gremlin active mode (will be a text value)
def ActiveMode() -> str:
- ''' gets the current active mode'''
- eh = gremlin.event_handler.EventHandler()
- return eh.active_mode
+ """gets the current active mode"""
+ eh = gremlin.event_handler.EventHandler()
+ return eh.active_mode
+
def SetMode(mode_name: str):
- ''' sets the active mode '''
- eh = gremlin.event_handler.EventHandler()
- eh.change_mode(mode_name)
+ """sets the active mode"""
+ eh = gremlin.event_handler.EventHandler()
+ eh.change_mode(mode_name)
+
def SetLastMode():
- ''' returns to the last mode '''
- eh = gremlin.event_handler.EventHandler()
- eh.change_mode(eh.previous_mode)
+ """returns to the last mode"""
+ eh = gremlin.event_handler.EventHandler()
+ eh.change_mode(eh.previous_mode)
+
def RunMacro(macro):
- ''' runs a macro'''
- gremlin.macro.MacroManager().queue_macro(macro)
+ """runs a macro"""
+ gremlin.macro.MacroManager().queue_macro(macro)
-def GetTimeMs(future = 0) -> int:
- ''' returns the current time in milliseconds '''
- return int(time.time() * 1000) + future
+def GetTimeMs(future=0) -> int:
+ """returns the current time in milliseconds"""
+ return int(time.time() * 1000) + future
+
def SendMouseWheel(direction):
- ''' sends a mouse wheel output direction is 1 or -1'''
- if direction in (1, -1):
- gremlin.sendinput.mouse_wheel(direction)
+ """sends a mouse wheel output direction is 1 or -1"""
+ if direction in (1, -1):
+ gremlin.sendinput.mouse_wheel(direction)
+
def GetVjoy():
- ''' get the vjoy device (virtual hardware input)'''
- return gremlin.joystick_handling.VJoyProxy()
+ """get the vjoy device (virtual hardware input)"""
+ return gremlin.joystick_handling.VJoyProxy()
+
def GetJoy():
- ''' gets the joy device (raw hardware input)'''
+ """gets the joy device (raw hardware input)"""
return gremlin.input_devices.JoystickProxy()
cubic_control_points = [(-1.0, -1.0), (1.0, 1.0)]
+
def cubic_curve(value):
- ''' curves the data using a simple cubic curve '''
- points = cubic_control_points
- for cp in sorted(points, key=lambda e: e.center.x):
- points.append((cp.center.x, cp.center.y))
- if len(points) < 2:
- return None
- else:
- return gremlin.spline.CubicSpline(points)
+ """curves the data using a simple cubic curve"""
+ points = cubic_control_points
+ for cp in sorted(points, key=lambda e: e.center.x):
+ points.append((cp.center.x, cp.center.y))
+ if len(points) < 2:
+ return None
+ else:
+ return gremlin.spline.CubicSpline(points)
diff --git a/test/conftest.py b/test/conftest.py
index 15a6ac0a..e6ea73de 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,4 +1,5 @@
import sys
+
sys.path.append(".")
import pytest
@@ -17,6 +18,4 @@ def joystick_init():
@pytest.fixture(scope="session", autouse=True)
def terminate_event_listener(request):
- request.addfinalizer(
- lambda: gremlin.event_handler.EventListener().terminate()
- )
\ No newline at end of file
+ request.addfinalizer(lambda: gremlin.event_handler.EventListener().terminate())
diff --git a/test/test_action_condition.py b/test/test_action_condition.py
index 898ab4c7..6fdd0d41 100644
--- a/test/test_action_condition.py
+++ b/test/test_action_condition.py
@@ -18,13 +18,11 @@
import sys
from gremlin import event_handler
+
sys.path.append(".")
-import pytest
-import uuid
from lxml import etree as ElementTree
-import gremlin.error as error
import gremlin.types as types
import gremlin.profile_library as profile_library
import gremlin.util as util
@@ -34,8 +32,7 @@
def test_from_xml():
c = condition.ConditionModel(
- profile_library.ActionTree(),
- types.InputType.JoystickButton
+ profile_library.ActionTree(), types.InputType.JoystickButton
)
c.from_xml(ElementTree.fromstring(open("test/action_condition_simple.xml").read()))
@@ -55,8 +52,7 @@ def test_from_xml():
def test_from_xml_complex():
c = condition.ConditionModel(
- profile_library.ActionTree(),
- types.InputType.JoystickButton
+ profile_library.ActionTree(), types.InputType.JoystickButton
)
c.from_xml(ElementTree.fromstring(open("test/action_condition_complex.xml").read()))
@@ -117,41 +113,40 @@ def test_from_xml_complex():
def test_to_xml():
c = condition.ConditionModel(
- profile_library.ActionTree(),
- types.InputType.JoystickButton
+ profile_library.ActionTree(), types.InputType.JoystickButton
)
cond = condition.JoystickCondition()
input_dev = event_handler.Event(
types.InputType.JoystickButton,
37,
- util.parse_guid("4DCB3090-97EC-11EB-8003-444553540000")
+ util.parse_guid("4DCB3090-97EC-11EB-8003-444553540000"),
)
cond._inputs.append(input_dev)
cond._comparator = condition.comparator.PressedComparator(True)
c._conditions.append(cond)
node = c.to_xml()
- assert node.find(
- "./property/name[.='logical-operator']/../value"
- ).text == "all"
- assert node.find(
- "./condition/property/name[.='condition-type']/../value"
- ).text == "joystick"
- assert node.find(
- "./condition/input/property/name[.='input-type']/../value"
- ).text == "button"
- assert node.find(
- "./condition/comparator/property/name[.='is-pressed']/../value"
- ).text == "True"
+ assert node.find("./property/name[.='logical-operator']/../value").text == "all"
+ assert (
+ node.find("./condition/property/name[.='condition-type']/../value").text
+ == "joystick"
+ )
+ assert (
+ node.find("./condition/input/property/name[.='input-type']/../value").text
+ == "button"
+ )
+ assert (
+ node.find("./condition/comparator/property/name[.='is-pressed']/../value").text
+ == "True"
+ )
def test_ctor():
c = condition.ConditionModel(
- profile_library.ActionTree(),
- types.InputType.JoystickButton
+ profile_library.ActionTree(), types.InputType.JoystickButton
)
assert len(c._conditions) == 0
assert c._logical_operator == condition.LogicalOperator.All
- assert c.is_valid() == True
\ No newline at end of file
+ assert c.is_valid() == True
diff --git a/test/test_action_description.py b/test/test_action_description.py
index d06753c2..5a746542 100644
--- a/test/test_action_description.py
+++ b/test/test_action_description.py
@@ -16,13 +16,11 @@
# along with this program. If not, see
import sys
+
sys.path.append(".")
import pytest
-import uuid
-from lxml import etree as ElementTree
-import gremlin.error
# import gremlin.plugin_manager
# from gremlin.profile import Profile
# import gremlin.types
@@ -44,9 +42,8 @@
@pytest.fixture(scope="session", autouse=True)
def terminate_event_listener(request):
import gremlin.event_handler
- request.addfinalizer(
- lambda: gremlin.event_handler.EventListener().terminate()
- )
+
+ request.addfinalizer(lambda: gremlin.event_handler.EventListener().terminate())
def test_model_ctor():
diff --git a/test/test_action_remap.py b/test/test_action_remap.py
index 0f25567c..66e80f0e 100644
--- a/test/test_action_remap.py
+++ b/test/test_action_remap.py
@@ -16,15 +16,12 @@
# along with this program. If not, see
import sys
+
sys.path.append(".")
-import pytest
import uuid
from lxml import etree as ElementTree
-import dinput
-import gremlin.error as error
-import gremlin.joystick_handling
import gremlin.types as types
import gremlin.profile_library as profile_library
@@ -80,8 +77,7 @@
def test_ctor(joystick_init):
r = map_to_vjoy.MapToVjoyModel(
- profile_library.ActionTree(),
- types.InputType.JoystickButton
+ profile_library.ActionTree(), types.InputType.JoystickButton
)
assert r.vjoy_device_id == 1
@@ -93,8 +89,7 @@ def test_ctor(joystick_init):
def test_from_xml():
r = map_to_vjoy.MapToVjoyModel(
- profile_library.ActionTree(),
- types.InputType.JoystickButton
+ profile_library.ActionTree(), types.InputType.JoystickButton
)
r.from_xml(ElementTree.fromstring(xml_button))
assert r.vjoy_device_id == 1
@@ -114,8 +109,7 @@ def test_from_xml():
def test_to_xml():
r = map_to_vjoy.MapToVjoyModel(
- profile_library.ActionTree(),
- types.InputType.JoystickButton
+ profile_library.ActionTree(), types.InputType.JoystickButton
)
r._id = uuid.UUID("ac905a47-9ad3-4b65-b702-fbae1d133609")
@@ -124,15 +118,9 @@ def test_to_xml():
r.vjoy_input_type = types.InputType.JoystickButton
node = r.to_xml()
- assert node.find(
- "./property/name[.='vjoy-device-id']/../value"
- ).text == "2"
- assert node.find(
- "./property/name[.='vjoy-input-id']/../value"
- ).text == "14"
- assert node.find(
- "./property/name[.='vjoy-input-type']/../value"
- ).text == "button"
+ assert node.find("./property/name[.='vjoy-device-id']/../value").text == "2"
+ assert node.find("./property/name[.='vjoy-input-id']/../value").text == "14"
+ assert node.find("./property/name[.='vjoy-input-type']/../value").text == "button"
assert node.find("./property/name[.='axis-mode']") == None
assert node.find("./property/name[.='axis-scaling']") == None
@@ -141,18 +129,8 @@ def test_to_xml():
r.axis_scaling = 0.75
node = r.to_xml()
- assert node.find(
- "./property/name[.='vjoy-device-id']/../value"
- ).text == "2"
- assert node.find(
- "./property/name[.='vjoy-input-id']/../value"
- ).text == "14"
- assert node.find(
- "./property/name[.='vjoy-input-type']/../value"
- ).text == "axis"
- assert node.find(
- "./property/name[.='axis-mode']/../value"
- ).text == "absolute"
- assert node.find(
- "./property/name[.='axis-scaling']/../value"
- ).text == "0.75"
+ assert node.find("./property/name[.='vjoy-device-id']/../value").text == "2"
+ assert node.find("./property/name[.='vjoy-input-id']/../value").text == "14"
+ assert node.find("./property/name[.='vjoy-input-type']/../value").text == "axis"
+ assert node.find("./property/name[.='axis-mode']/../value").text == "absolute"
+ assert node.find("./property/name[.='axis-scaling']/../value").text == "0.75"
diff --git a/test/test_action_tempo.py b/test/test_action_tempo.py
index afc68f31..90df3040 100644
--- a/test/test_action_tempo.py
+++ b/test/test_action_tempo.py
@@ -16,13 +16,12 @@
# along with this program. If not, see
import sys
+
sys.path.append(".")
-import pytest
import uuid
from lxml import etree as ElementTree
-import gremlin.error as error
import gremlin.types as types
import gremlin.profile_library as profile_library
@@ -51,10 +50,7 @@
def test_from_xml():
- a = tempo.TempoModel(
- profile_library.ActionTree(),
- types.InputType.JoystickButton
- )
+ a = tempo.TempoModel(profile_library.ActionTree(), types.InputType.JoystickButton)
a.from_xml(ElementTree.fromstring(xml_simple))
assert len(a._short_action_ids) == 2
@@ -64,34 +60,25 @@ def test_from_xml():
def test_to_xml():
- a = tempo.TempoModel(
- profile_library.ActionTree(),
- types.InputType.JoystickButton
- )
+ a = tempo.TempoModel(profile_library.ActionTree(), types.InputType.JoystickButton)
a._short_action_ids.append(uuid.UUID("fbe6be7b-07c9-4400-94f2-caa245ebcc7e"))
a._threshold = 0.42
node = a.to_xml()
- assert node.find(
- "./property/name[.='activate-on']/../value"
- ).text == "release"
- assert node.find(
- "./property/name[.='threshold']/../value"
- ).text == "0.42"
- assert node.find(
- "./short-actions/action-id"
- ).text == "fbe6be7b-07c9-4400-94f2-caa245ebcc7e"
+ assert node.find("./property/name[.='activate-on']/../value").text == "release"
+ assert node.find("./property/name[.='threshold']/../value").text == "0.42"
+ assert (
+ node.find("./short-actions/action-id").text
+ == "fbe6be7b-07c9-4400-94f2-caa245ebcc7e"
+ )
def test_ctor():
- a = tempo.TempoModel(
- profile_library.ActionTree(),
- types.InputType.JoystickButton
- )
+ a = tempo.TempoModel(profile_library.ActionTree(), types.InputType.JoystickButton)
assert len(a._short_action_ids) == 0
assert len(a._long_action_ids) == 0
assert a._threshold == 0.5
assert a._activate_on == "release"
- assert a.is_valid() == True
\ No newline at end of file
+ assert a.is_valid() == True
diff --git a/test/test_config.py b/test/test_config.py
index 0fccb5f7..bb9fb277 100644
--- a/test/test_config.py
+++ b/test/test_config.py
@@ -16,9 +16,9 @@
# along with this program. If not, see
import sys
+
sys.path.append(".")
-import os
import tempfile
import pytest
@@ -43,7 +43,14 @@ def test_simple(modify_config):
c.register("test", "case", "1", PropertyType.Int, 42, "")
c.register("test", "case", "2", PropertyType.Bool, False, "")
- c.register("test", "case", "3", PropertyType.HatDirection, gremlin.types.HatDirection.NorthEast, "")
+ c.register(
+ "test",
+ "case",
+ "3",
+ PropertyType.HatDirection,
+ gremlin.types.HatDirection.NorthEast,
+ "",
+ )
assert c.value("test", "case", "1") == 42
assert c.value("test", "case", "2") == False
assert c.value("test", "case", "3") == gremlin.types.HatDirection.NorthEast
@@ -55,13 +62,21 @@ def test_simple(modify_config):
assert c.value("test", "case", "2") == True
assert c.value("test", "case", "3") == gremlin.types.HatDirection.SouthWest
+
def test_load_save(modify_config):
c = gremlin.config.Configuration()
c.register("test", "case", "1", PropertyType.Int, 42, "one")
c.register("test", "case", "2", PropertyType.Bool, False, "two", True)
- c.register("test", "case", "3", PropertyType.HatDirection, gremlin.types.HatDirection.NorthEast, "")
- c.register("test", "case", "4", PropertyType.List, [1,2,3,4,5], "")
+ c.register(
+ "test",
+ "case",
+ "3",
+ PropertyType.HatDirection,
+ gremlin.types.HatDirection.NorthEast,
+ "",
+ )
+ c.register("test", "case", "4", PropertyType.List, [1, 2, 3, 4, 5], "")
assert c.value("test", "case", "1") == 42
assert c.description("test", "case", "1") == "one"
assert c.expose("test", "case", "1") == False
@@ -74,7 +89,7 @@ def test_load_save(modify_config):
assert c.description("test", "case", "3") == ""
assert c.expose("test", "case", "3") == False
- assert c.value("test", "case", "4") == [1,2,3,4,5]
+ assert c.value("test", "case", "4") == [1, 2, 3, 4, 5]
assert c.description("test", "case", "4") == ""
assert c.expose("test", "case", "4") == False
@@ -93,10 +108,11 @@ def test_load_save(modify_config):
assert c.description("test", "case", "3") == ""
assert c.expose("test", "case", "3") == False
- assert c.value("test", "case", "4") == [1,2,3,4,5]
+ assert c.value("test", "case", "4") == [1, 2, 3, 4, 5]
assert c.description("test", "case", "4") == ""
assert c.expose("test", "case", "4") == False
+
def test_exceptions():
c = gremlin.config.Configuration()
@@ -108,4 +124,4 @@ def test_exceptions():
c.set("test", "some", "other", "test")
with pytest.raises(gremlin.error.GremlinError):
- c.value("does", "not", "exist")
\ No newline at end of file
+ c.value("does", "not", "exist")
diff --git a/test/test_profile.py b/test/test_profile.py
index cf79d82c..b13fe436 100644
--- a/test/test_profile.py
+++ b/test/test_profile.py
@@ -16,6 +16,7 @@
# along with this program. If not, see
import sys
+
sys.path.append(".")
import os
@@ -186,7 +187,6 @@ def _store_in_tmpfile(text: str) -> str:
return fpath
-
def test_constructor_invalid():
fpath = _store_in_tmpfile(xml_invalid)
@@ -221,7 +221,9 @@ def test_simple_action():
assert actions[2].value.tag == "map-to-vjoy"
assert actions[2].value.vjoy_device_id == 2
assert actions[2].value.vjoy_input_id == 6
- assert actions[2].value.vjoy_input_type == gremlin.input_types.InputType.JoystickAxis
+ assert (
+ actions[2].value.vjoy_input_type == gremlin.input_types.InputType.JoystickAxis
+ )
assert actions[2].value.axis_mode == gremlin.types.AxisMode.Relative
assert actions[2].value.axis_scaling == 1.5
assert actions[2].value.id == uuid.UUID("d67cbad2-da3f-4b59-b434-2d493e7e6185")
@@ -275,9 +277,8 @@ def test_mode_hierarchy():
tree = ElementTree.parse(fpath)
root = tree.getroot()
-
mh = gremlin.profile.ModeHierarchy()
mh.from_xml(root)
assert mh.mode_list() == ["Child", "Deep", "Default", "Levels", "Separate", "Three"]
- assert mh.first_mode == "Default"
\ No newline at end of file
+ assert mh.first_mode == "Default"
diff --git a/test/test_tree.py b/test/test_tree.py
index 7e9a8fad..ccc980c1 100644
--- a/test/test_tree.py
+++ b/test/test_tree.py
@@ -17,6 +17,7 @@
import sys
+
sys.path.append(".")
import pytest
@@ -75,7 +76,7 @@ def test_add_child():
assert n1.children == [n2, n3, n4]
assert n4.parent == n1
assert n4.depth == 1
- assert n2.node_count ==4
+ assert n2.node_count == 4
def test_add_sibling():
@@ -287,4 +288,4 @@ def test_sibling_reordering():
n4.detach()
n2.insert_sibling_after(n4)
- assert n1.children == [n2, n4, n3]
\ No newline at end of file
+ assert n1.children == [n2, n4, n3]
diff --git a/test/test_util.py b/test/test_util.py
index 376548f5..8fc65d09 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -16,9 +16,9 @@
# along with this program. If not, see
import sys
+
sys.path.append(".")
-import os
import pytest
import uuid
from lxml import etree as ElementTree
@@ -58,53 +58,58 @@
"""
+
def test_read_action_id():
doc = ElementTree.fromstring(xml_doc)
- assert gremlin.util.read_action_id(doc) == \
- uuid.UUID("ac905a47-9ad3-4b65-b702-fbae1d133609")
+ assert gremlin.util.read_action_id(doc) == uuid.UUID(
+ "ac905a47-9ad3-4b65-b702-fbae1d133609"
+ )
xml_bad = """
"""
doc = ElementTree.fromstring(xml_bad)
- with pytest.raises(gremlin.error.ProfileError,
- match=r"Failed parsing id from"):
+ with pytest.raises(gremlin.error.ProfileError, match=r"Failed parsing id from"):
gremlin.util.read_action_id(doc)
xml_bad = """
"""
doc = ElementTree.fromstring(xml_bad)
- with pytest.raises(gremlin.error.ProfileError,
- match=r"Reading id entry failed due"):
+ with pytest.raises(
+ gremlin.error.ProfileError, match=r"Reading id entry failed due"
+ ):
gremlin.util.read_action_id(doc)
def test_read_property():
doc = ElementTree.fromstring(xml_doc)
- assert gremlin.util.read_property(
- doc, "description", gremlin.types.PropertyType.String
- ) == "This is a test"
- assert gremlin.util.read_property(
- doc, "answer-to-life-and-everything", gremlin.types.PropertyType.Int
- ) == 42
- assert gremlin.util.read_property(
- doc, "pi", gremlin.types.PropertyType.Float
- ) == 3.14
- assert gremlin.util.read_property(
- doc, "lies", gremlin.types.PropertyType.Bool
- ) == True
-
+ assert (
+ gremlin.util.read_property(
+ doc, "description", gremlin.types.PropertyType.String
+ )
+ == "This is a test"
+ )
+ assert (
+ gremlin.util.read_property(
+ doc, "answer-to-life-and-everything", gremlin.types.PropertyType.Int
+ )
+ == 42
+ )
+ assert (
+ gremlin.util.read_property(doc, "pi", gremlin.types.PropertyType.Float) == 3.14
+ )
+ assert (
+ gremlin.util.read_property(doc, "lies", gremlin.types.PropertyType.Bool) is True
+ )
with pytest.raises(gremlin.error.ProfileError, match=r"No property name"):
gremlin.util.read_property(
doc, "does not exist", gremlin.types.PropertyType.Bool
)
with pytest.raises(gremlin.error.ProfileError, match=r"Property type mismatch"):
- gremlin.util.read_property(
- doc, "lies", gremlin.types.PropertyType.Float
- )
+ gremlin.util.read_property(doc, "lies", gremlin.types.PropertyType.Float)
xml_bad = """
@@ -116,9 +121,7 @@ def test_read_property():
"""
doc = ElementTree.fromstring(xml_bad)
with pytest.raises(gremlin.error.ProfileError, match=r"Failed parsing property"):
- gremlin.util.read_property(
- doc, "value", gremlin.types.PropertyType.Int
- )
+ gremlin.util.read_property(doc, "value", gremlin.types.PropertyType.Int)
xml_bad = """
@@ -129,9 +132,7 @@ def test_read_property():
"""
doc = ElementTree.fromstring(xml_bad)
with pytest.raises(gremlin.error.ProfileError, match=r"Value element of property"):
- gremlin.util.read_property(
- doc, "value", gremlin.types.PropertyType.Int
- )
+ gremlin.util.read_property(doc, "value", gremlin.types.PropertyType.Int)
xml_bad = """
@@ -142,7 +143,7 @@ def test_read_property():
"""
doc = ElementTree.fromstring(xml_bad)
- with pytest.raises(gremlin.error.ProfileError, match=r"Property element is missing"):
- gremlin.util.read_property(
- doc, "value", gremlin.types.PropertyType.Int
- )
\ No newline at end of file
+ with pytest.raises(
+ gremlin.error.ProfileError, match=r"Property element is missing"
+ ):
+ gremlin.util.read_property(doc, "value", gremlin.types.PropertyType.Int)
diff --git a/vigem/vigem_client.py b/vigem/vigem_client.py
index 1e2c0c5c..23f6347d 100644
--- a/vigem/vigem_client.py
+++ b/vigem/vigem_client.py
@@ -2,42 +2,41 @@
Adapted from ViGEm source
"""
-import platform
from pathlib import Path
import ctypes
-from ctypes import CDLL, POINTER, CFUNCTYPE, c_void_p, c_uint, c_ushort, c_ulong, c_bool, c_ubyte
-
-from .vigem_commons import XUSB_REPORT, DS4_REPORT, DS4_REPORT_EX, VIGEM_TARGET_TYPE
+from ctypes import (
+ c_void_p,
+ c_uint,
+ c_ushort,
+ c_ulong,
+ c_bool,
+)
+
+from .vigem_commons import XUSB_REPORT, DS4_REPORT, VIGEM_TARGET_TYPE
import os
import logging
from gremlin.util import display_error, get_dll_version
+
syslog = logging.getLogger("system")
-class VigemClient():
-
+class VigemClient:
_dll = None
_version = None
initalized = False
@staticmethod
def init():
-
-
"""Initializes the DILL library.
This has to be called before any other DILL interactions can take place.
"""
- from pathlib import Path
-
if VigemClient._dll is None:
-
dll_folder = os.path.dirname(__file__)
dll_file = "ViGEmClient.dll"
- _dll_path = os.path.join(dll_folder, dll_file )
+ _dll_path = os.path.join(dll_folder, dll_file)
if not os.path.isfile(_dll_path):
-
# look one level up for packaging in 3.12
parent = Path(dll_folder).parent
_dll_path = os.path.join(parent, dll_file)
@@ -46,7 +45,7 @@ def init():
display_error(msg)
syslog.critical(msg)
return
-
+
dll_version = get_dll_version(_dll_path)
VigemClient._version = dll_version
@@ -58,7 +57,7 @@ def init():
syslog.warning(msg)
try:
VigemClient._dll = _di_listener_dll
-
+
except Exception as error:
msg = f"Unable to initialize Vigem: {_dll_path}\n{error}"
display_error(msg)
@@ -66,6 +65,7 @@ def init():
VigemClient.initalized = True
+
VigemClient.init()
@@ -74,7 +74,7 @@ def init():
# vigem_connect = None
# vigem_disconnect = None
-#vigemClient = CDLL(_dll_path)
+# vigemClient = CDLL(_dll_path)
vigemClient = VigemClient._dll
@@ -93,7 +93,7 @@ def init():
@param vigem The PVIGEM_CLIENT object.
"""
vigem_free = vigemClient.vigem_free
- vigem_free.argtypes = (c_void_p, )
+ vigem_free.argtypes = (c_void_p,)
vigem_free.restype = None
"""
@@ -103,7 +103,7 @@ def init():
@returns A VIGEM_ERROR.
"""
vigem_connect = vigemClient.vigem_connect
- vigem_connect.argtypes = (c_void_p, )
+ vigem_connect.argtypes = (c_void_p,)
vigem_connect.restype = c_uint
"""
@@ -114,7 +114,7 @@ def init():
@param vigem The PVIGEM_CLIENT object.
"""
vigem_disconnect = vigemClient.vigem_disconnect
- vigem_disconnect.argtypes = (c_void_p, )
+ vigem_disconnect.argtypes = (c_void_p,)
vigem_disconnect.restype = None
"""
@@ -141,7 +141,7 @@ def init():
@param target The target device object.
"""
vigem_target_free = vigemClient.vigem_target_free
- vigem_target_free.argtypes = (c_void_p, )
+ vigem_target_free.argtypes = (c_void_p,)
vigem_target_free.restype = None
"""
@@ -193,7 +193,7 @@ def init():
@returns The Vendor ID.
"""
vigem_target_get_vid = vigemClient.vigem_target_get_vid
- vigem_target_get_vid.argtypes = (c_void_p, )
+ vigem_target_get_vid.argtypes = (c_void_p,)
vigem_target_get_vid.restype = c_ushort
"""
@@ -202,7 +202,7 @@ def init():
@returns The Product ID.
"""
vigem_target_get_pid = vigemClient.vigem_target_get_pid
- vigem_target_get_pid.argtypes = (c_void_p, )
+ vigem_target_get_pid.argtypes = (c_void_p,)
vigem_target_get_pid.restype = c_ushort
"""
@@ -254,7 +254,7 @@ def init():
@returns The internally used index of the target device.
"""
vigem_target_get_index = vigemClient.vigem_target_get_index
- vigem_target_get_index.argtypes = (c_void_p, )
+ vigem_target_get_index.argtypes = (c_void_p,)
vigem_target_get_index.restype = c_ulong
"""
@@ -263,7 +263,7 @@ def init():
@returns A VIGEM_TARGET_TYPE.
"""
vigem_target_get_type = vigemClient.vigem_target_get_type
- vigem_target_get_type.argtypes = (c_void_p, )
+ vigem_target_get_type.argtypes = (c_void_p,)
vigem_target_get_type.restype = VIGEM_TARGET_TYPE
"""
@@ -273,7 +273,7 @@ def init():
@returns TRUE if device is attached to the bus, FALSE otherwise.
"""
vigem_target_is_attached = vigemClient.vigem_target_is_attached
- vigem_target_is_attached.argtypes = (c_void_p, )
+ vigem_target_is_attached.argtypes = (c_void_p,)
vigem_target_is_attached.restype = c_bool
"""
@@ -300,16 +300,25 @@ def init():
@param userData The user data passed to the notification callback.
@returns A VIGEM_ERROR.
"""
- vigem_target_x360_register_notification = vigemClient.vigem_target_x360_register_notification
- vigem_target_x360_register_notification.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p)
+ vigem_target_x360_register_notification = (
+ vigemClient.vigem_target_x360_register_notification
+ )
+ vigem_target_x360_register_notification.argtypes = (
+ c_void_p,
+ c_void_p,
+ c_void_p,
+ c_void_p,
+ )
vigem_target_x360_register_notification.restype = c_uint
"""
Removes a previously registered callback function from the provided target object.
@param target The target device object.
"""
- vigem_target_x360_unregister_notification = vigemClient.vigem_target_x360_unregister_notification
- vigem_target_x360_unregister_notification.argtypes = (c_void_p, )
+ vigem_target_x360_unregister_notification = (
+ vigemClient.vigem_target_x360_unregister_notification
+ )
+ vigem_target_x360_unregister_notification.argtypes = (c_void_p,)
vigem_target_x360_unregister_notification.restype = None
"""
@@ -322,14 +331,23 @@ def init():
@param userData The user data passed to the notification callback.
@returns A VIGEM_ERROR.
"""
- vigem_target_ds4_register_notification = vigemClient.vigem_target_ds4_register_notification
- vigem_target_ds4_register_notification.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p)
+ vigem_target_ds4_register_notification = (
+ vigemClient.vigem_target_ds4_register_notification
+ )
+ vigem_target_ds4_register_notification.argtypes = (
+ c_void_p,
+ c_void_p,
+ c_void_p,
+ c_void_p,
+ )
vigem_target_ds4_register_notification.restype = c_uint
"""
Removes a previously registered callback function from the provided target object.
@param target The target device object.
"""
- vigem_target_ds4_unregister_notification = vigemClient.vigem_target_ds4_unregister_notification
- vigem_target_ds4_unregister_notification.argtypes = (c_void_p, )
- vigem_target_ds4_unregister_notification.restype = None
\ No newline at end of file
+ vigem_target_ds4_unregister_notification = (
+ vigemClient.vigem_target_ds4_unregister_notification
+ )
+ vigem_target_ds4_unregister_notification.argtypes = (c_void_p,)
+ vigem_target_ds4_unregister_notification.restype = None
diff --git a/vigem/vigem_commons.py b/vigem/vigem_commons.py
index bc62abfd..67ae12c5 100644
--- a/vigem/vigem_commons.py
+++ b/vigem/vigem_commons.py
@@ -14,6 +14,7 @@ class VIGEM_TARGET_TYPE(IntFlag):
Represents the desired target type for the emulated device.
NOTE: 1 skipped on purpose to maintain compatibility
"""
+
Xbox360Wired = 0 # Microsoft Xbox 360 Controller (wired)
DualShock4Wired = 2 # Sony DualShock 4 (wired)
@@ -22,6 +23,7 @@ class XUSB_BUTTON(IntFlag):
"""
Possible XUSB report buttons.
"""
+
XUSB_GAMEPAD_DPAD_UP = 0x0001
XUSB_GAMEPAD_DPAD_DOWN = 0x0002
XUSB_GAMEPAD_DPAD_LEFT = 0x0004
@@ -43,28 +45,35 @@ class XUSB_REPORT(Structure):
"""
Represents an XINPUT_GAMEPAD-compatible report structure.
"""
- _fields_ = [("wButtons", c_ushort),
- ("bLeftTrigger", c_byte),
- ("bRightTrigger", c_byte),
- ("sThumbLX", c_short),
- ("sThumbLY", c_short),
- ("sThumbRX", c_short),
- ("sThumbRY", c_short)]
+
+ _fields_ = [
+ ("wButtons", c_ushort),
+ ("bLeftTrigger", c_byte),
+ ("bRightTrigger", c_byte),
+ ("sThumbLX", c_short),
+ ("sThumbLY", c_short),
+ ("sThumbRX", c_short),
+ ("sThumbRY", c_short),
+ ]
class DS4_LIGHTBAR_COLOR(Structure):
"""
The color value (RGB) of a DualShock 4 Lightbar
"""
- _fields_ = [("Red", c_ubyte), # Red part of the Lightbar (0-255).
- ("Green", c_ubyte), # Green part of the Lightbar (0-255).
- ("Blue", c_ubyte)] # Blue part of the Lightbar (0-255).
+
+ _fields_ = [
+ ("Red", c_ubyte), # Red part of the Lightbar (0-255).
+ ("Green", c_ubyte), # Green part of the Lightbar (0-255).
+ ("Blue", c_ubyte),
+ ] # Blue part of the Lightbar (0-255).
class DS4_BUTTONS(IntFlag):
"""
DualShock 4 digital buttons
"""
+
DS4_BUTTON_THUMB_RIGHT = 1 << 15
DS4_BUTTON_THUMB_LEFT = 1 << 14
DS4_BUTTON_OPTIONS = 1 << 13
@@ -83,6 +92,7 @@ class DS4_SPECIAL_BUTTONS(IntFlag):
"""
DualShock 4 special buttons
"""
+
DS4_SPECIAL_BUTTON_PS = 1 << 0
DS4_SPECIAL_BUTTON_TOUCHPAD = 1 << 1
@@ -91,6 +101,7 @@ class DS4_DPAD_DIRECTIONS(IntEnum):
"""
DualShock 4 directional pad (HAT) values
"""
+
DS4_BUTTON_DPAD_NONE = 0x8
DS4_BUTTON_DPAD_NORTHWEST = 0x7
DS4_BUTTON_DPAD_WEST = 0x6
@@ -106,14 +117,17 @@ class DS4_REPORT(Structure):
"""
DualShock 4 HID Input report
"""
- _fields_ = [("bThumbLX", c_byte),
- ("bThumbLY", c_byte),
- ("bThumbRX", c_byte),
- ("bThumbRY", c_byte),
- ("wButtons", c_ushort),
- ("bSpecial", c_byte),
- ("bTriggerL", c_byte),
- ("bTriggerR", c_byte)]
+
+ _fields_ = [
+ ("bThumbLX", c_byte),
+ ("bThumbLY", c_byte),
+ ("bThumbRX", c_byte),
+ ("bThumbRY", c_byte),
+ ("wButtons", c_ushort),
+ ("bSpecial", c_byte),
+ ("bTriggerL", c_byte),
+ ("bTriggerR", c_byte),
+ ]
def DS4_SET_DPAD(report, dpad):
@@ -133,53 +147,65 @@ class DS4_TOUCH(Structure):
"""
DualShock 4 HID Touchpad structure
"""
- _fields_ = [("bPacketCounter", c_byte), # timestamp / packet counter associated with touch event
- ("bIsUpTrackingNum1", c_byte), # 0 means down; active low
- # unique to each finger down, so for a lift and repress the value is incremented
- ("bTouchData1", c_byte * 3), # Two 12 bits values (for X and Y)
- # middle byte holds last 4 bits of X and the starting
- ("bIsUpTrackingNum2", c_byte), # second touch data immediately follows data of first
- ("bTouchData2", c_ushort * 3)] # resolution is 1920x943
+
+ _fields_ = [
+ (
+ "bPacketCounter",
+ c_byte,
+ ), # timestamp / packet counter associated with touch event
+ ("bIsUpTrackingNum1", c_byte), # 0 means down; active low
+ # unique to each finger down, so for a lift and repress the value is incremented
+ ("bTouchData1", c_byte * 3), # Two 12 bits values (for X and Y)
+ # middle byte holds last 4 bits of X and the starting
+ (
+ "bIsUpTrackingNum2",
+ c_byte,
+ ), # second touch data immediately follows data of first
+ ("bTouchData2", c_ushort * 3),
+ ] # resolution is 1920x943
class DS4_SUB_REPORT_EX(Structure):
- _fields_ = [("bThumbLX", c_byte),
- ("bThumbLY", c_byte),
- ("bThumbRX", c_byte),
- ("bThumbRY", c_byte),
- ("wButtons", c_ushort),
- ("bSpecial", c_byte),
- ("bTriggerL", c_byte),
- ("bTriggerR", c_byte),
- ("wTimestamp", c_ushort),
- ("bBatteryLvl", c_byte),
- ("wGyroX", c_short),
- ("wGyroY", c_short),
- ("wGyroZ", c_short),
- ("wAccelX", c_short),
- ("wAccelY", c_short),
- ("wAccelZ", c_short),
- ("_bUnknown1", c_byte * 5),
- ("bBatteryLvlSpecial", c_byte),
- # really should have a enum to show everything that this can represent (USB charging, battery level; EXT, headset, microphone connected)
- ("_bUnknown2", c_byte * 2),
- ("bTouchPacketsN", c_byte), # 0x00 to 0x03 (USB max)
- ("sCurrentTouch", DS4_TOUCH),
- ("sPreviousTouch", DS4_TOUCH * 2)]
+ _fields_ = [
+ ("bThumbLX", c_byte),
+ ("bThumbLY", c_byte),
+ ("bThumbRX", c_byte),
+ ("bThumbRY", c_byte),
+ ("wButtons", c_ushort),
+ ("bSpecial", c_byte),
+ ("bTriggerL", c_byte),
+ ("bTriggerR", c_byte),
+ ("wTimestamp", c_ushort),
+ ("bBatteryLvl", c_byte),
+ ("wGyroX", c_short),
+ ("wGyroY", c_short),
+ ("wGyroZ", c_short),
+ ("wAccelX", c_short),
+ ("wAccelY", c_short),
+ ("wAccelZ", c_short),
+ ("_bUnknown1", c_byte * 5),
+ ("bBatteryLvlSpecial", c_byte),
+ # really should have a enum to show everything that this can represent (USB charging, battery level; EXT, headset, microphone connected)
+ ("_bUnknown2", c_byte * 2),
+ ("bTouchPacketsN", c_byte), # 0x00 to 0x03 (USB max)
+ ("sCurrentTouch", DS4_TOUCH),
+ ("sPreviousTouch", DS4_TOUCH * 2),
+ ]
class DS4_REPORT_EX(Union):
"""
DualShock 4 v1 complete HID Input report
"""
- _fields_ = [("Report", DS4_SUB_REPORT_EX),
- ("ReportBuffer", c_ubyte * 63)]
+
+ _fields_ = [("Report", DS4_SUB_REPORT_EX), ("ReportBuffer", c_ubyte * 63)]
class VIGEM_ERRORS(IntEnum):
"""
Values that represent ViGEm errors
"""
+
VIGEM_ERROR_NONE = 0x20000000
VIGEM_ERROR_BUS_NOT_FOUND = 0xE0000001
VIGEM_ERROR_NO_FREE_SLOT = 0xE0000002
@@ -199,4 +225,4 @@ class VIGEM_ERRORS(IntEnum):
VIGEM_ERROR_NOT_SUPPORTED = 0xE0000016
-# TODO: add the missing types (C callback functions)
\ No newline at end of file
+# TODO: add the missing types (C callback functions)
diff --git a/vigem/vigem_gamepad.py b/vigem/vigem_gamepad.py
index f655734c..1c6a1a46 100644
--- a/vigem/vigem_gamepad.py
+++ b/vigem/vigem_gamepad.py
@@ -6,13 +6,14 @@
from . import vigem_commons as vcom
from . import vigem_client as vcli
-import ctypes
from ctypes import CFUNCTYPE, c_void_p, c_ubyte
from abc import ABC, abstractmethod
from inspect import signature # Check if user defined callback function is legal
import logging
+
syslog = logging.getLogger("system")
+
def check_err(err):
if err != vcom.VIGEM_ERRORS.VIGEM_ERROR_NONE:
syslog.warning(f"VIGEM error: {vcom.VIGEM_ERRORS(err).name}")
@@ -38,6 +39,7 @@ class VBus:
"""
Virtual USB bus (ViGEmBus)
"""
+
def __init__(self):
# keep internal references so GC does not remove the dll before the objects are terminated properly
self.vigem_disconnect = vcli.vigem_disconnect
@@ -53,7 +55,6 @@ def __del__(self):
self.vigem_free(self._busp)
-
# We instantiate a single global VBus for all controllers
VBUS = VBus()
@@ -69,10 +70,14 @@ def __init__(self):
self.vigem_target_free = vcli.vigem_target_free
self._busp = self.vbus.get_busp()
self._devicep = self.target_alloc()
- self.CMPFUNC = CFUNCTYPE(None, c_void_p, c_void_p, c_ubyte, c_ubyte, c_ubyte, c_void_p)
+ self.CMPFUNC = CFUNCTYPE(
+ None, c_void_p, c_void_p, c_ubyte, c_ubyte, c_ubyte, c_void_p
+ )
self.cmp_func = None
vcli.vigem_target_add(self._busp, self._devicep)
- assert vcli.vigem_target_is_attached(self._devicep), "The virtual device could not connect to ViGEmBus."
+ assert vcli.vigem_target_is_attached(
+ self._devicep
+ ), "The virtual device could not connect to ViGEmBus."
def __del__(self):
if not self.vbus.valid:
@@ -128,6 +133,7 @@ class VX360Gamepad(VGamepad):
"""
Virtual XBox360 gamepad
"""
+
def __init__(self):
super().__init__()
if not self.valid:
@@ -143,7 +149,8 @@ def get_default_report(self):
sThumbLX=0,
sThumbLY=0,
sThumbRX=0,
- sThumbRY=0)
+ sThumbRY=0,
+ )
def reset(self):
"""
@@ -219,14 +226,6 @@ def right_joystick(self, x_value, y_value):
self.report.sThumbRX = x_value
self.report.sThumbRY = y_value
- def left_joystick_float(self, x_value_float, y_value_float):
- """
- Sets the values of the X and Y axis for the left joystick
-
- :param: float between -1.0 and 1.0 (0 = neutral position)
- """
- self.left_joystick(round(x_value_float * 32767), round(y_value_float * 32767))
-
def left_joystick_x_float(self, value_float):
"""
Sets the values of the X and Y axis for the left joystick
@@ -262,7 +261,6 @@ def right_joystick_y_float(self, value_float):
"""
value = round(value_float * 32767)
self.report.sThumbRY = value
-
def right_joystick_float(self, x_value_float, y_value_float):
"""
@@ -272,7 +270,6 @@ def right_joystick_float(self, x_value_float, y_value_float):
"""
self.right_joystick(round(x_value_float * 32767), round(y_value_float * 32767))
-
def left_joystick_float(self, x_value_float, y_value_float):
"""
Sets the values of the X and Y axis for the left joystick
@@ -298,7 +295,6 @@ def left_joystick_float_y(self, value_float):
"""
value = round(value_float * 32767)
self.report.sThumbLY = value
-
def right_joystick_float_x(self, value_float):
"""
@@ -317,7 +313,6 @@ def right_joystick_float_y(self, value_float):
"""
value = round(value_float * 32767)
self.report.sThumbRY = value
-
def update(self):
"""
@@ -332,9 +327,19 @@ def register_notification(self, callback_function):
:param: a function of the form: my_func(client, target, large_motor, small_motor, led_number, user_data)
"""
if not signature(callback_function) == signature(dummy_callback):
- raise TypeError("Needed callback function signature: {}, but got: {}".format(signature(dummy_callback), signature(callback_function)))
- self.cmp_func = self.CMPFUNC(callback_function) # keep its reference, otherwise the program will crash when a callback is made.
- check_err(vcli.vigem_target_x360_register_notification(self._busp, self._devicep, self.cmp_func, None))
+ raise TypeError(
+ "Needed callback function signature: {}, but got: {}".format(
+ signature(dummy_callback), signature(callback_function)
+ )
+ )
+ self.cmp_func = self.CMPFUNC(
+ callback_function
+ ) # keep its reference, otherwise the program will crash when a callback is made.
+ check_err(
+ vcli.vigem_target_x360_register_notification(
+ self._busp, self._devicep, self.cmp_func, None
+ )
+ )
def unregister_notification(self):
"""
@@ -365,7 +370,8 @@ def get_default_report(self):
wButtons=0,
bSpecial=0,
bTriggerL=0,
- bTriggerR=0)
+ bTriggerR=0,
+ )
vcom.DS4_REPORT_INIT(rep)
return rep
@@ -467,7 +473,9 @@ def left_joystick_float(self, x_value_float, y_value_float):
:param: float between -1.0 and 1.0 (0 = neutral position)
"""
- self.left_joystick(128 + round(x_value_float * 127), 128 + round(y_value_float * 127))
+ self.left_joystick(
+ 128 + round(x_value_float * 127), 128 + round(y_value_float * 127)
+ )
def right_joystick_float(self, x_value_float, y_value_float):
"""
@@ -475,7 +483,9 @@ def right_joystick_float(self, x_value_float, y_value_float):
:param: float between -1.0 and 1.0 (0 = neutral position)
"""
- self.right_joystick(128 + round(x_value_float * 127), 128 + round(y_value_float * 127))
+ self.right_joystick(
+ 128 + round(x_value_float * 127), 128 + round(y_value_float * 127)
+ )
def directional_pad(self, direction):
"""
@@ -499,7 +509,7 @@ def update_extended_report(self, extended_report):
:param: a DS4_REPORT_EX
"""
- #check_err(vcli.vigem_target_ds4_update_ex_ptr(self._busp, self._devicep, ctypes.byref(extended_report)))
+ # check_err(vcli.vigem_target_ds4_update_ex_ptr(self._busp, self._devicep, ctypes.byref(extended_report)))
pass
def register_notification(self, callback_function):
@@ -509,9 +519,17 @@ def register_notification(self, callback_function):
:param: a function of the form: my_func(client, target, large_motor, small_motor, led_number, user_data)
"""
if not signature(callback_function) == signature(dummy_callback):
- raise TypeError("Needed callback function signature: {}, but got: {}".format(signature(dummy_callback), signature(callback_function)))
+ raise TypeError(
+ "Needed callback function signature: {}, but got: {}".format(
+ signature(dummy_callback), signature(callback_function)
+ )
+ )
self.cmp_func = self.CMPFUNC(callback_function)
- check_err(vcli.vigem_target_ds4_register_notification(self._busp, self._devicep, self.cmp_func, None))
+ check_err(
+ vcli.vigem_target_ds4_register_notification(
+ self._busp, self._devicep, self.cmp_func, None
+ )
+ )
def unregister_notification(self):
"""
@@ -520,4 +538,4 @@ def unregister_notification(self):
vcli.vigem_target_ds4_unregister_notification(self._devicep)
def target_alloc(self):
- return vcli.vigem_target_ds4_alloc()
\ No newline at end of file
+ return vcli.vigem_target_ds4_alloc()
diff --git a/vjoy/__init__.py b/vjoy/__init__.py
index 0468910b..6b42754a 100644
--- a/vjoy/__init__.py
+++ b/vjoy/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/vjoy/vjoy.py b/vjoy/vjoy.py
index 07a9db25..7604e657 100644
--- a/vjoy/vjoy.py
+++ b/vjoy/vjoy.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@
syslog = logging.getLogger("system")
+
def _error_string(vid, iid, value):
"""Creates an error string for the given inputs.
@@ -44,21 +45,19 @@ def _error_string(vid, iid, value):
class AxisName(enum.Enum):
-
"""Enumeration of the valid axis names."""
X = 0x30 # 1
Y = 0x31 # 2
Z = 0x32 # 3
- RX = 0x33 # 4
- RY = 0x34 # 5
- RZ = 0x35 # 6
- SL0 = 0x36 # 7
- SL1 = 0x37 # 8
+ RX = 0x33 # 4
+ RY = 0x34 # 5
+ RZ = 0x35 # 6
+ SL0 = 0x36 # 7
+ SL1 = 0x37 # 8
class HatType(enum.Enum):
-
"""Valid hat types."""
Discrete = 0
@@ -144,10 +143,7 @@ def hat_configuration_valid(vjoy_id):
return continuous_count >= discrete_count
-
-
class Axis:
-
"""Represents an analog axis in vJoy, allows setting the value
of the axis."""
@@ -164,17 +160,9 @@ def __init__(self, vjoy_dev, axis_id):
# Retrieve axis minimum and maximum values
tmp = ctypes.c_ulong()
- VJoyInterface.GetVJDAxisMin(
- self.vjoy_id,
- self.axis_id,
- ctypes.byref(tmp)
- )
+ VJoyInterface.GetVJDAxisMin(self.vjoy_id, self.axis_id, ctypes.byref(tmp))
self._min_value = tmp.value
- VJoyInterface.GetVJDAxisMax(
- self.vjoy_id,
- self.axis_id,
- ctypes.byref(tmp)
- )
+ VJoyInterface.GetVJDAxisMax(self.vjoy_id, self.axis_id, ctypes.byref(tmp))
self._max_value = tmp.value
self._half_range = int(self._max_value / 2)
@@ -184,7 +172,8 @@ def __init__(self, vjoy_dev, axis_id):
# If this is not the case our value setter needs to change
if self._min_value != 0:
raise VJoyError(
- f"vJoy axis minimum value is not 0 - {_error_string(self.vjoy_id, self.axis_id, self._min_value)}")
+ f"vJoy axis minimum value is not 0 - {_error_string(self.vjoy_id, self.axis_id, self._min_value)}"
+ )
def set_response_curve(self, spline_type, control_points):
"""Sets the response curve to use for the axis.
@@ -195,8 +184,7 @@ def set_response_curve(self, spline_type, control_points):
if spline_type == "cubic-spline":
self._response_curve_fn = gremlin.spline.CubicSpline(control_points)
elif spline_type == "cubic-bezier-spline":
- self._response_curve_fn = \
- gremlin.spline.CubicBezierSpline(control_points)
+ self._response_curve_fn = gremlin.spline.CubicBezierSpline(control_points)
else:
syslog.error("Invalid spline type specified")
self._response_curve_fn = lambda x: x
@@ -209,9 +197,7 @@ def set_deadzone(self, low, center_low, center_high, high):
:param center_high upper center deadzone limit
:param high high deadzone limit
"""
- self._deadzone_fn = lambda x: deadzone(
- x, low, center_low, center_high, high
- )
+ self._deadzone_fn = lambda x: deadzone(x, low, center_low, center_high, high)
@property
def value(self):
@@ -228,7 +214,7 @@ def value(self, p_value):
:param value the position of the axis in the range [-1, 1]
"""
-
+
import gremlin.event_handler
from gremlin.input_types import InputType
@@ -240,7 +226,9 @@ def value(self, p_value):
return
eh = gremlin.event_handler.EventListener()
- event = gremlin.event_handler.VjoyEvent(self.vjoy_id, InputType.JoystickAxis, self.axis_id - 0x30 + 1, p_value)
+ event = gremlin.event_handler.VjoyEvent(
+ self.vjoy_id, InputType.JoystickAxis, self.axis_id - 0x30 + 1, p_value
+ )
eh.vjoy_event.emit(event)
self.vjoy_dev.ensure_ownership()
@@ -260,14 +248,16 @@ def value(self, p_value):
)
if not VJoyInterface.SetAxis(
- int(self._half_range + self._half_range * self._value),
- self.vjoy_id,
- self.axis_id
+ int(self._half_range + self._half_range * self._value),
+ self.vjoy_id,
+ self.axis_id,
):
- from gremlin.ui import backend
from gremlin.util import log_sys_warn
- log_sys_warn(f"Failed setting axis value - {_error_string(self.vjoy_id, self.axis_id, self._value)}")
-
+
+ log_sys_warn(
+ f"Failed setting axis value - {_error_string(self.vjoy_id, self.axis_id, self._value)}"
+ )
+
self.vjoy_dev.used()
def set_absolute_value(self, value):
@@ -291,9 +281,9 @@ def set_absolute_value(self, value):
self._value = value
if not VJoyInterface.SetAxis(
- int(self._half_range + self._half_range * self._value),
- self.vjoy_id,
- self.axis_id
+ int(self._half_range + self._half_range * self._value),
+ self.vjoy_id,
+ self.axis_id,
):
raise VJoyError(
f"Failed setting axis value - { _error_string(self.vjoy_id, self.axis_id, self._value)}"
@@ -302,7 +292,6 @@ def set_absolute_value(self, value):
class Button:
-
"""Represents a button in vJoy, allows pressing and releasing it."""
def __init__(self, vjoy_dev, button_id):
@@ -331,42 +320,32 @@ def is_pressed(self, is_pressed):
:param is_pressed True if the button is pressed, False otherwise
"""
- assert(isinstance(is_pressed, bool))
+ assert isinstance(is_pressed, bool)
import gremlin.event_handler
from gremlin.input_types import InputType
eh = gremlin.event_handler.EventListener()
- event = gremlin.event_handler.VjoyEvent(self.vjoy_id, InputType.JoystickButton, self.button_id, is_pressed)
+ event = gremlin.event_handler.VjoyEvent(
+ self.vjoy_id, InputType.JoystickButton, self.button_id, is_pressed
+ )
eh.vjoy_event.emit(event)
-
-
-
self.vjoy_dev.ensure_ownership()
self._is_pressed = is_pressed
- if not VJoyInterface.SetBtn(
- self._is_pressed,
- self.vjoy_id,
- self.button_id
- ):
- syslog.error(f"Failed setting button value - {_error_string(self.vjoy_id, self.button_id, self._is_pressed)}")
+ if not VJoyInterface.SetBtn(self._is_pressed, self.vjoy_id, self.button_id):
+ syslog.error(
+ f"Failed setting button value - {_error_string(self.vjoy_id, self.button_id, self._is_pressed)}"
+ )
self.vjoy_dev.used()
class Hat:
-
"""Represents a discrete hat in vJoy, allows setting the direction
of the hat."""
# Discrete directions, mapping (x, y) coordinates to vJoy values
- to_discrete_direction = {
- (0, 1): 0,
- (1, 0): 1,
- (0, -1): 2,
- (-1, 0): 3,
- (0, 0): -1
- }
+ to_discrete_direction = {(0, 1): 0, (1, 0): 1, (0, -1): 2, (-1, 0): 3, (0, 0): -1}
# Continuous directions, mapping 8-way *(x, y) coordinates to vJoy values
to_continuous_direction = {
@@ -378,33 +357,32 @@ class Hat:
(0, -1): 18000,
(-1, -1): 22500,
(-1, 0): 27000,
- (-1, 1): 31500
+ (-1, 1): 31500,
}
to_continuous_position = {
- -1 : (0, 0),
+ -1: (0, 0),
0: (0, 1),
4500: (1, 1),
9000: (1, 0),
- 13500 : (1, -1),
- 18000 : (0, -1),
+ 13500: (1, -1),
+ 18000: (0, -1),
22500: (-1, -1),
27000: (-1, 0),
- 31500: (-1, 1),
+ 31500: (-1, 1),
}
-
# Mapping from event directions to names
direction_to_name = {
- ( 0, 0): "Center",
- ( 0, 1): "North",
- ( 1, 1): "North-east",
- ( 1, 0): "East",
- ( 1, -1): "South-east",
- ( 0, -1): "South",
+ (0, 0): "Center",
+ (0, 1): "North",
+ (1, 1): "North-east",
+ (1, 0): "East",
+ (1, -1): "South-east",
+ (0, -1): "South",
(-1, -1): "South-west",
- (-1, 0): "West",
- (-1, 1): "North-west"
+ (-1, 0): "West",
+ (-1, 1): "North-west",
}
# Mapping from names to event directions
@@ -417,26 +395,21 @@ class Hat:
"South": (0, -1),
"South-west": (-1, -1),
"West": (-1, 0),
- "North-west": (-1, 1)
+ "North-west": (-1, 1),
}
direction_to_icon = {
- ( 0, 0): "mdi.image-filter-center-focus-strong", #"Center",
- ( 0, 1): "mdi.arrow-up-thin-circle-outline", #"North",
- ( 1, 1): "mdi.arrow-top-right-thin-circle-outline", #"North-east",
- ( 1, 0): "mdi.arrow-right-thin-circle-outline", #"East",
- ( 1, -1): "mdi.arrow-bottom-right-thin-circle-outline", #"South-east",
- ( 0, -1): "mdi.arrow-down-thin-circle-outline", #"South",
- (-1, -1): "mdi.arrow-bottom-left-thin-circle-outline", #"South-west",
- (-1, 0): "mdi.arrow-left-thin-circle-outline", # "West",
- (-1, 1): "mdi.arrow-top-left-thin-circle-outline", #"North-west"
+ (0, 0): "mdi.image-filter-center-focus-strong", # "Center",
+ (0, 1): "mdi.arrow-up-thin-circle-outline", # "North",
+ (1, 1): "mdi.arrow-top-right-thin-circle-outline", # "North-east",
+ (1, 0): "mdi.arrow-right-thin-circle-outline", # "East",
+ (1, -1): "mdi.arrow-bottom-right-thin-circle-outline", # "South-east",
+ (0, -1): "mdi.arrow-down-thin-circle-outline", # "South",
+ (-1, -1): "mdi.arrow-bottom-left-thin-circle-outline", # "South-west",
+ (-1, 0): "mdi.arrow-left-thin-circle-outline", # "West",
+ (-1, 1): "mdi.arrow-top-left-thin-circle-outline", # "North-west"
}
-
-
-
-
-
def __init__(self, vjoy_dev, hat_id, hat_type):
"""Creates a new object.
@@ -470,11 +443,11 @@ def direction(self, direction):
from gremlin.input_types import InputType
eh = gremlin.event_handler.EventListener()
- event = gremlin.event_handler.VjoyEvent(self.vjoy_id, InputType.JoystickHat, self.hat_id, direction)
+ event = gremlin.event_handler.VjoyEvent(
+ self.vjoy_id, InputType.JoystickHat, self.hat_id, direction
+ )
eh.vjoy_event.emit(event)
-
-
self.vjoy_dev.ensure_ownership()
if self.hat_type == HatType.Discrete:
@@ -483,7 +456,8 @@ def direction(self, direction):
self._set_continuous_direction(direction)
else:
raise VJoyError(
- f"Invalid hat type specified - {_error_string(self.vjoy_id, self.hat_id, self.direction)}")
+ f"Invalid hat type specified - {_error_string(self.vjoy_id, self.hat_id, self.direction)}"
+ )
self.vjoy_dev.used()
def _set_discrete_direction(self, direction):
@@ -498,12 +472,10 @@ def _set_discrete_direction(self, direction):
self._direction = direction
if not VJoyInterface.SetDiscPov(
- Hat.to_discrete_direction[direction],
- self.vjoy_id,
- self.hat_id
+ Hat.to_discrete_direction[direction], self.vjoy_id, self.hat_id
):
raise VJoyError(
- f"Failed to set hat direction - {_error_string(self.vjoy_id, self.hat_id, self._direction)}"
+ f"Failed to set hat direction - {_error_string(self.vjoy_id, self.hat_id, self._direction)}"
)
def _set_continuous_direction(self, direction):
@@ -518,9 +490,7 @@ def _set_continuous_direction(self, direction):
self._direction = direction
if not VJoyInterface.SetContPov(
- Hat.to_continuous_direction[direction],
- self.vjoy_id,
- self.hat_id
+ Hat.to_continuous_direction[direction], self.vjoy_id, self.hat_id
):
raise VJoyError(
f"Failed to set hat direction - {_error_string(self.vjoy_id, self.hat_id, self._direction)}"
@@ -528,7 +498,6 @@ def _set_continuous_direction(self, direction):
class VJoy:
-
"""Represents a vJoy device present in the system."""
# Duration of inactivity after which the keep alive routine is run
@@ -543,7 +512,7 @@ class VJoy:
AxisName.RY: 5,
AxisName.RZ: 6,
AxisName.SL0: 7,
- AxisName.SL1: 8
+ AxisName.SL1: 8,
}
def __init__(self, vjoy_id):
@@ -556,7 +525,7 @@ def __init__(self, vjoy_id):
if not VJoyInterface.vJoyEnabled():
syslog.error("vJoy is not currently running")
raise VJoyError("vJoy is not currently running")
- #vjoy_version = VJoyInterface.GetvJoyVersion()
+ # vjoy_version = VJoyInterface.GetvJoyVersion()
# if vjoy_version < 0x218:
# syslog.error(
# f"Running incompatible vJoy version, 2.1.8+ is required - found {vjoy_version:x}"
@@ -586,8 +555,7 @@ def __init__(self, vjoy_id):
# Timestamp of the last time the device was used
self._last_active = time.time()
self._keep_alive_timer = threading.Timer(
- VJoy.keep_alive_timeout,
- self._keep_alive
+ VJoy.keep_alive_timeout, self._keep_alive
)
self._keep_alive_timer.start()
@@ -610,7 +578,8 @@ def ensure_ownership(self):
if self.pid != VJoyInterface.GetOwnerPid(self.vjoy_id):
if not VJoyInterface.AcquireVJD(self.vjoy_id):
syslog.error(
- f"Failed to re-acquire the vJoy device - vid: {self.vjoy_id}")
+ f"Failed to re-acquire the vJoy device - vid: {self.vjoy_id}"
+ )
raise VJoyError(
f"Failed to re-acquire the vJoy device - vid: {self.vjoy_id}"
)
@@ -783,9 +752,7 @@ def reset(self):
for i in self._hat:
self._hat[i].direction = hat_states[i]
else:
- syslog.info(
- "Could not reset vJoy device, are we using it?"
- )
+ syslog.info("Could not reset vJoy device, are we using it?")
def used(self):
"""Updates the timestamp of the last time the device has been used."""
@@ -812,8 +779,7 @@ def _keep_alive(self):
if self._last_active + VJoy.keep_alive_timeout < time.time():
self.reset()
self._keep_alive_timer = threading.Timer(
- VJoy.keep_alive_timeout,
- self._keep_alive
+ VJoy.keep_alive_timeout, self._keep_alive
)
self._keep_alive_timer.start()
@@ -826,12 +792,12 @@ def _init_axes(self):
axes = {}
for i, axis in enumerate(AxisName):
if VJoyInterface.GetVJDAxisExist(self.vjoy_id, axis.value) > 0:
- axes[i+1] = Axis(self, axis.value)
- self._axis_names[i+1] = gremlin.types.AxisNames.to_string(
- gremlin.types.AxisNames(i+1)
+ axes[i + 1] = Axis(self, axis.value)
+ self._axis_names[i + 1] = gremlin.types.AxisNames.to_string(
+ gremlin.types.AxisNames(i + 1)
)
- self._axis_lookup[len(self._axis_names)] = i+1
- self._axis_lookup[axis] = i+1
+ self._axis_lookup[len(self._axis_names)] = i + 1
+ self._axis_lookup[axis] = i + 1
return axes
def _init_buttons(self):
@@ -841,7 +807,7 @@ def _init_buttons(self):
:returns list of Button objects
"""
buttons = {}
- for btn_id in range(1, VJoyInterface.GetVJDButtonNumber(self.vjoy_id)+1):
+ for btn_id in range(1, VJoyInterface.GetVJDButtonNumber(self.vjoy_id) + 1):
buttons[btn_id] = Button(self, btn_id)
return buttons
@@ -858,14 +824,16 @@ def _init_hats(self):
# We can't use discrete hats as such their existence is considered
# an error
if VJoyInterface.GetVJDDiscPovNumber(self.vjoy_id) > 0:
- error_msg = "vJoy is configured incorrectly. \n\n" \
- "Please ensure hats are configured as 'Continuous' " \
- "rather then '4 Directions'."
+ error_msg = (
+ "vJoy is configured incorrectly. \n\n"
+ "Please ensure hats are configured as 'Continuous' "
+ "rather then '4 Directions'."
+ )
syslog.error(error_msg)
raise VJoyError(error_msg)
# for hat_id in range(1, VJoyInterface.GetVJDDiscPovNumber(self.vjoy_id)+1):
# hats[hat_id] = Hat(self, hat_id, HatType.Discrete)
- for hat_id in range(1, VJoyInterface.GetVJDContPovNumber(self.vjoy_id)+1):
+ for hat_id in range(1, VJoyInterface.GetVJDContPovNumber(self.vjoy_id) + 1):
hats[hat_id] = Hat(self, hat_id, HatType.Continuous)
return hats
diff --git a/vjoy/vjoy_interface.py b/vjoy/vjoy_interface.py
index 6b9fadd7..aa37304c 100644
--- a/vjoy/vjoy_interface.py
+++ b/vjoy/vjoy_interface.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
-# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
+# Based on original Joystick Gremlin work by Lionel Ott and other contributors - Joystick Gremlin Ex is (C) EMCS 2025
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,156 +19,105 @@
import ctypes
import enum
import os
-from gremlin.error import GremlinError
import logging
syslog = logging.getLogger("system")
-class VJoyState(enum.Enum):
+class VJoyState(enum.Enum):
"""Enumeration of the possible VJoy device states."""
- Owned = 0 # The device is owned by the current application
- Free = 1 # The device is not owned by any application
- Bust = 2 # The device is owned by another application
- Missing = 3 # The device is not present
- Unknown = 4 # Unknown type of error
+ Owned = 0 # The device is owned by the current application
+ Free = 1 # The device is not owned by any application
+ Bust = 2 # The device is owned by another application
+ Missing = 3 # The device is not present
+ Unknown = 4 # Unknown type of error
class VJoyInterface:
-
"""Allows low level interaction with VJoy devices via ctypes."""
vjoy_dll = None
-
# Declare argument and return types for all the functions
# exposed by the dll
api_functions = {
# General vJoy information
- "GetvJoyVersion": {
- "arguments": [],
- "returns": ctypes.c_short
- },
- "vJoyEnabled": {
- "arguments": [],
- "returns": ctypes.c_bool
- },
- "GetvJoyProductString": {
- "arguments": [],
- "returns": ctypes.c_wchar_p
- },
- "GetvJoyManufacturerString": {
- "arguments": [],
- "returns": ctypes.c_wchar_p
- },
- "GetvJoySerialNumberString": {
- "arguments": [],
- "returns": ctypes.c_wchar_p
- },
-
+ "GetvJoyVersion": {"arguments": [], "returns": ctypes.c_short},
+ "vJoyEnabled": {"arguments": [], "returns": ctypes.c_bool},
+ "GetvJoyProductString": {"arguments": [], "returns": ctypes.c_wchar_p},
+ "GetvJoyManufacturerString": {"arguments": [], "returns": ctypes.c_wchar_p},
+ "GetvJoySerialNumberString": {"arguments": [], "returns": ctypes.c_wchar_p},
# Device properties
- "GetVJDButtonNumber": {
- "arguments": [ctypes.c_uint],
- "returns": ctypes.c_int
- },
- "GetVJDDiscPovNumber": {
- "arguments": [ctypes.c_uint],
- "returns": ctypes.c_int
- },
- "GetVJDContPovNumber": {
- "arguments": [ctypes.c_uint],
- "returns": ctypes.c_int
- },
+ "GetVJDButtonNumber": {"arguments": [ctypes.c_uint], "returns": ctypes.c_int},
+ "GetVJDDiscPovNumber": {"arguments": [ctypes.c_uint], "returns": ctypes.c_int},
+ "GetVJDContPovNumber": {"arguments": [ctypes.c_uint], "returns": ctypes.c_int},
# API claims this should return a bool, however, this is untrue and
# is an int, see:
# http://vjoystick.sourceforge.net/site/index.php/forum/5-Discussion/1026-bug-with-getvjdaxisexist
"GetVJDAxisExist": {
"arguments": [ctypes.c_uint, ctypes.c_uint],
- "returns": ctypes.c_int
+ "returns": ctypes.c_int,
},
"GetVJDAxisMax": {
"arguments": [ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p],
- "returns": ctypes.c_bool
+ "returns": ctypes.c_bool,
},
"GetVJDAxisMin": {
"arguments": [ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p],
- "returns": ctypes.c_bool
+ "returns": ctypes.c_bool,
},
-
# Device management
- "GetOwnerPid": {
- "arguments": [ctypes.c_uint],
- "returns": ctypes.c_int
- },
- "AcquireVJD": {
- "arguments": [ctypes.c_uint],
- "returns": ctypes.c_bool
- },
+ "GetOwnerPid": {"arguments": [ctypes.c_uint], "returns": ctypes.c_int},
+ "AcquireVJD": {"arguments": [ctypes.c_uint], "returns": ctypes.c_bool},
"RelinquishVJD": {
"arguments": [ctypes.c_uint],
"returns": None,
},
"UpdateVJD": {
"arguments": [ctypes.c_uint, ctypes.c_void_p],
- "returns": ctypes.c_bool
+ "returns": ctypes.c_bool,
},
- "GetVJDStatus": {
- "arguments": [ctypes.c_uint],
- "returns": ctypes.c_int
- },
-
+ "GetVJDStatus": {"arguments": [ctypes.c_uint], "returns": ctypes.c_int},
# Reset functions
- "ResetVJD": {
- "arguments": [ctypes.c_uint],
- "returns": ctypes.c_bool
- },
- "ResetAll": {
- "arguments": [],
- "returns": None
- },
- "ResetButtons": {
- "arguments": [ctypes.c_uint],
- "returns": ctypes.c_bool
- },
- "ResetPovs": {
- "arguments": [ctypes.c_uint],
- "returns": ctypes.c_bool
- },
-
+ "ResetVJD": {"arguments": [ctypes.c_uint], "returns": ctypes.c_bool},
+ "ResetAll": {"arguments": [], "returns": None},
+ "ResetButtons": {"arguments": [ctypes.c_uint], "returns": ctypes.c_bool},
+ "ResetPovs": {"arguments": [ctypes.c_uint], "returns": ctypes.c_bool},
# Set values
"SetAxis": {
"arguments": [ctypes.c_long, ctypes.c_uint, ctypes.c_uint],
- "returns": ctypes.c_bool
+ "returns": ctypes.c_bool,
},
"SetBtn": {
"arguments": [ctypes.c_bool, ctypes.c_uint, ctypes.c_ubyte],
- "returns": ctypes.c_bool
+ "returns": ctypes.c_bool,
},
"SetDiscPov": {
"arguments": [ctypes.c_int, ctypes.c_uint, ctypes.c_ubyte],
- "returns": ctypes.c_bool
+ "returns": ctypes.c_bool,
},
"SetContPov": {
"arguments": [ctypes.c_ulong, ctypes.c_uint, ctypes.c_ubyte],
- "returns": ctypes.c_bool
+ "returns": ctypes.c_bool,
},
-
}
@classmethod
def initialize(self):
"""Initializes the functions as class methods."""
from pathlib import Path
- from gremlin.util import display_error, get_dll_version, version_valid #, get_vjoy_driver_version
+ from gremlin.util import (
+ display_error,
+ get_dll_version,
+ version_valid,
+ ) # , get_vjoy_driver_version
if VJoyInterface.vjoy_dll is None:
-
dll_folder = os.path.dirname(__file__)
dll_file = "vJoyInterface.dll"
- _dll_path = os.path.join(dll_folder, dll_file )
+ _dll_path = os.path.join(dll_folder, dll_file)
if not os.path.isfile(_dll_path):
-
# look one level up for packaging in 3.12
parent = Path(dll_folder).parent
_dll_path = os.path.join(parent, dll_file)
@@ -176,22 +125,20 @@ def initialize(self):
msg = f"Unable to continue - missing dll: {_dll_path}"
display_error(msg)
syslog.critical(msg)
- os._exit(1)
+ os._exit(1)
-
- # check DLL version
+ # check DLL version
min_version = "2.1.9.1"
try:
-
dll_version = get_dll_version(_dll_path)
-
+
if not version_valid(dll_version, min_version):
msg = f"Invalid version dll: {_dll_path}\nVersion {min_version} required, found {dll_version}"
display_error(msg)
syslog.critical(msg)
os._exit(1)
- except:
- msg = "Error: VJOY is not installed or not configured."
+ except Exception as e:
+ msg = f"Error: VJOY is not installed or not configured. Details: {e}"
display_error(msg)
syslog.critical(msg)
os._exit(1)
@@ -206,7 +153,6 @@ def initialize(self):
syslog.critical(msg)
os._exit(1)
-
for fn_name, params in self.api_functions.items():
dll_fn = getattr(self.vjoy_dll, fn_name)
if "arguments" in params:
@@ -214,5 +160,3 @@ def initialize(self):
if "returns" in params:
dll_fn.restype = params["returns"]
setattr(self, fn_name, dll_fn)
-
-