fix: stabilize handheld controller mapping
This commit is contained in:
parent
6fac52dd96
commit
26c9308fcf
3 changed files with 121 additions and 43 deletions
|
|
@ -13,19 +13,19 @@ const MAX_PROFILE_BYTES: int = 1024 * 1024
|
||||||
const CAPTURE_AXIS_THRESHOLD: float = 0.55
|
const CAPTURE_AXIS_THRESHOLD: float = 0.55
|
||||||
const CAPTURE_AXIS_RELEASE_THRESHOLD: float = 0.30
|
const CAPTURE_AXIS_RELEASE_THRESHOLD: float = 0.30
|
||||||
const ACTIVE_DEVICE_AXIS_THRESHOLD: float = 0.35
|
const ACTIVE_DEVICE_AXIS_THRESHOLD: float = 0.35
|
||||||
const MUOS_MAPPING_REVISION: String = "muos-v3"
|
const MUOS_MAPPING_REVISION: String = "muos-v4"
|
||||||
const MUOS_CONTROLLER_NAMES: Array[String] = [
|
const MUOS_CONTROLLER_NAMES: Array[String] = [
|
||||||
"muOS-Keys",
|
"muOS-Keys",
|
||||||
"Deeplay-keys",
|
"Deeplay-keys",
|
||||||
]
|
]
|
||||||
const MUOS_MAPPING_BINDINGS: String = (
|
const MUOS_MAPPING_BINDINGS: String = (
|
||||||
"a:b4,b:b3,x:b5,y:b6,"
|
"a:b0,b:b1,x:b3,y:b2,"
|
||||||
+ "leftshoulder:b7,rightshoulder:b8,"
|
+ "leftshoulder:b4,rightshoulder:b5,"
|
||||||
+ "lefttrigger:b13,righttrigger:b14,"
|
+ "lefttrigger:b10,righttrigger:b11,"
|
||||||
+ "guide:b11,start:b10,back:b9,"
|
+ "guide:b8,start:b7,back:b6,"
|
||||||
+ "dpup:h0.1,dpleft:h0.8,dpright:h0.2,dpdown:h0.4,"
|
+ "dpup:h0.1,dpleft:h0.8,dpright:h0.2,dpdown:h0.4,"
|
||||||
+ "leftx:a0,lefty:a1,leftstick:b12,"
|
+ "leftx:a0,lefty:a1,leftstick:b9,"
|
||||||
+ "rightx:a2,righty:a3,rightstick:b15,platform:Linux,"
|
+ "rightx:a2,righty:a3,rightstick:b12,platform:Linux,"
|
||||||
)
|
)
|
||||||
const ROLE_A: StringName = &"a"
|
const ROLE_A: StringName = &"a"
|
||||||
const ROLE_B: StringName = &"b"
|
const ROLE_B: StringName = &"b"
|
||||||
|
|
@ -739,8 +739,8 @@ static func should_install_muos_compatibility_mapping(
|
||||||
controller_name: String,
|
controller_name: String,
|
||||||
is_known: bool,
|
is_known: bool,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
# Current SDL databases should remain authoritative. This fallback exists
|
# Never update an already-recognized virtual controller at runtime. Godot
|
||||||
# only for legacy muOS virtual controllers that SDL cannot identify.
|
# can stop delivering its standardized input events after that replacement.
|
||||||
return is_muos_controller_name(controller_name) and not is_known
|
return is_muos_controller_name(controller_name) and not is_known
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ func _run() -> void:
|
||||||
root.add_child(manager)
|
root.add_child(manager)
|
||||||
await process_frame
|
await process_frame
|
||||||
var failure: String = _validate_manager(manager)
|
var failure: String = _validate_manager(manager)
|
||||||
|
if failure.is_empty():
|
||||||
|
failure = _validate_portmaster_launcher()
|
||||||
if failure.is_empty():
|
if failure.is_empty():
|
||||||
failure = _validate_auto_map(manager)
|
failure = _validate_auto_map(manager)
|
||||||
if failure.is_empty():
|
if failure.is_empty():
|
||||||
|
|
@ -40,19 +42,19 @@ func _validate_manager(manager: ControllerMappingManagerType) -> String:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
for expected_binding: String in [
|
for expected_binding: String in [
|
||||||
"a:b4",
|
"a:b0",
|
||||||
"b:b3",
|
"b:b1",
|
||||||
"x:b5",
|
"x:b3",
|
||||||
"y:b6",
|
"y:b2",
|
||||||
"leftshoulder:b7",
|
"leftshoulder:b4",
|
||||||
"rightshoulder:b8",
|
"rightshoulder:b5",
|
||||||
"lefttrigger:b13",
|
"lefttrigger:b10",
|
||||||
"righttrigger:b14",
|
"righttrigger:b11",
|
||||||
"back:b9",
|
"back:b6",
|
||||||
"start:b10",
|
"start:b7",
|
||||||
"guide:b11",
|
"guide:b8",
|
||||||
"leftstick:b12",
|
"leftstick:b9",
|
||||||
"rightstick:b15",
|
"rightstick:b12",
|
||||||
"leftx:a0",
|
"leftx:a0",
|
||||||
"righty:a3",
|
"righty:a3",
|
||||||
]:
|
]:
|
||||||
|
|
@ -79,7 +81,7 @@ func _validate_manager(manager: ControllerMappingManagerType) -> String:
|
||||||
"muOS-Keys",
|
"muOS-Keys",
|
||||||
true,
|
true,
|
||||||
):
|
):
|
||||||
return "recognized muOS controller would override its SDL mapping"
|
return "recognized muOS controller would be replaced at runtime"
|
||||||
if ControllerMappingManagerType.should_install_muos_compatibility_mapping(
|
if ControllerMappingManagerType.should_install_muos_compatibility_mapping(
|
||||||
"ordinary controller",
|
"ordinary controller",
|
||||||
false,
|
false,
|
||||||
|
|
@ -194,6 +196,30 @@ func _validate_manager(manager: ControllerMappingManagerType) -> String:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_portmaster_launcher() -> String:
|
||||||
|
const launcher_path: String = "res://scripts/portmaster/NETfishing.sh"
|
||||||
|
if not FileAccess.file_exists(launcher_path):
|
||||||
|
return "PortMaster launcher template is missing"
|
||||||
|
var launcher: String = FileAccess.get_file_as_string(launcher_path)
|
||||||
|
for expected_fragment: String in [
|
||||||
|
"19004ca6010000000100000000010000",
|
||||||
|
"19000000010000000100000000010000",
|
||||||
|
"a:b0,b:b1,x:b3,y:b2",
|
||||||
|
"leftshoulder:b4,rightshoulder:b5",
|
||||||
|
"lefttrigger:b10,righttrigger:b11",
|
||||||
|
"guide:b8,start:b7,back:b6",
|
||||||
|
"leftstick:b9",
|
||||||
|
"rightstick:b12",
|
||||||
|
"netfishing_controllerconfig=\"$GODOT_MUOS_MAPPING\"",
|
||||||
|
"SDL_GAMECONTROLLERCONFIG=\"$netfishing_controllerconfig\" \\",
|
||||||
|
]:
|
||||||
|
if expected_fragment not in launcher:
|
||||||
|
return "PortMaster launcher omitted " + expected_fragment
|
||||||
|
if "$'\\n'" in launcher:
|
||||||
|
return "PortMaster launcher appends mappings across a Weston-unsafe newline"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
func _validate_auto_map(manager: ControllerMappingManagerType) -> String:
|
func _validate_auto_map(manager: ControllerMappingManagerType) -> String:
|
||||||
var panel := ControllerMappingPanelType.new()
|
var panel := ControllerMappingPanelType.new()
|
||||||
root.add_child(panel)
|
root.add_child(panel)
|
||||||
|
|
@ -258,13 +284,35 @@ func _validate_auto_map(manager: ControllerMappingManagerType) -> String:
|
||||||
return "manual remapping still dictates a specific physical input"
|
return "manual remapping still dictates a specific physical input"
|
||||||
if "any button" not in panel._progress_label.text.to_lower():
|
if "any button" not in panel._progress_label.text.to_lower():
|
||||||
return "manual remapping does not request a generic controller input"
|
return "manual remapping does not request a generic controller input"
|
||||||
var cancel_button := InputEventJoypadButton.new()
|
panel._cancel_capture()
|
||||||
cancel_button.device = manager.get_active_device_id()
|
panel._begin_auto_map()
|
||||||
cancel_button.button_index = JOY_BUTTON_B
|
panel._process(ControllerMappingPanelType.CAPTURE_NEUTRAL_SECONDS)
|
||||||
cancel_button.pressed = true
|
var lone_button := InputEventJoypadButton.new()
|
||||||
manager.controller_input_observed.emit(cancel_button)
|
lone_button.device = manager.get_active_device_id()
|
||||||
if panel.is_capturing():
|
lone_button.button_index = JOY_BUTTON_B
|
||||||
return "mapped controller back input did not cancel capture"
|
lone_button.pressed = true
|
||||||
|
manager.controller_input_observed.emit(lone_button)
|
||||||
|
if not panel._auto_map_active or panel._auto_map_index != 1:
|
||||||
|
return "a single controller button cancelled auto-map"
|
||||||
|
panel._cancel_capture()
|
||||||
|
panel._begin_auto_map()
|
||||||
|
panel._process(ControllerMappingPanelType.CAPTURE_NEUTRAL_SECONDS)
|
||||||
|
var left_bumper := InputEventJoypadButton.new()
|
||||||
|
left_bumper.device = manager.get_active_device_id()
|
||||||
|
left_bumper.button_index = JOY_BUTTON_LEFT_SHOULDER
|
||||||
|
left_bumper.pressed = true
|
||||||
|
manager.controller_input_observed.emit(left_bumper)
|
||||||
|
var right_bumper := InputEventJoypadButton.new()
|
||||||
|
right_bumper.device = manager.get_active_device_id()
|
||||||
|
right_bumper.button_index = JOY_BUTTON_RIGHT_SHOULDER
|
||||||
|
right_bumper.pressed = true
|
||||||
|
manager.controller_input_observed.emit(right_bumper)
|
||||||
|
panel._process(ControllerMappingPanelType.CANCEL_COMBO_HOLD_SECONDS - 0.01)
|
||||||
|
if not panel._auto_map_active:
|
||||||
|
return "bumper combo cancelled auto-map before the hold threshold"
|
||||||
|
panel._process(0.02)
|
||||||
|
if panel._auto_map_active or panel.is_capturing():
|
||||||
|
return "sustained bumper combo did not cancel auto-map"
|
||||||
panel._begin_auto_map()
|
panel._begin_auto_map()
|
||||||
panel._process(ControllerMappingPanelType.CAPTURE_NEUTRAL_SECONDS)
|
panel._process(ControllerMappingPanelType.CAPTURE_NEUTRAL_SECONDS)
|
||||||
var held_left := InputEventJoypadMotion.new()
|
var held_left := InputEventJoypadMotion.new()
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const ControllerMappingManagerType = preload(
|
||||||
)
|
)
|
||||||
const UtilityPageStyleType = preload("res://ui/utility_page_style.gd")
|
const UtilityPageStyleType = preload("res://ui/utility_page_style.gd")
|
||||||
const CAPTURE_NEUTRAL_SECONDS: float = 0.22
|
const CAPTURE_NEUTRAL_SECONDS: float = 0.22
|
||||||
|
const CANCEL_COMBO_HOLD_SECONDS: float = 1.25
|
||||||
|
|
||||||
var _mapping_manager: ControllerMappingManagerType
|
var _mapping_manager: ControllerMappingManagerType
|
||||||
var _binding_buttons: Dictionary = {}
|
var _binding_buttons: Dictionary = {}
|
||||||
|
|
@ -24,6 +25,9 @@ var _auto_map_draft: Dictionary = {}
|
||||||
var _capture_device_id: int = 0
|
var _capture_device_id: int = 0
|
||||||
var _waiting_for_neutral: bool = false
|
var _waiting_for_neutral: bool = false
|
||||||
var _neutral_elapsed: float = 0.0
|
var _neutral_elapsed: float = 0.0
|
||||||
|
var _cancel_left_bumper_pressed: bool = false
|
||||||
|
var _cancel_right_bumper_pressed: bool = false
|
||||||
|
var _cancel_combo_elapsed: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
@ -112,6 +116,14 @@ func _process(delta: float) -> void:
|
||||||
or _capturing_role.is_empty()
|
or _capturing_role.is_empty()
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
if _cancel_left_bumper_pressed and _cancel_right_bumper_pressed:
|
||||||
|
_cancel_combo_elapsed += delta
|
||||||
|
_progress_label.text = "keep holding both bumpers to cancel"
|
||||||
|
if _cancel_combo_elapsed >= CANCEL_COMBO_HOLD_SECONDS:
|
||||||
|
_cancel_capture()
|
||||||
|
_progress_label.text = "controller mapping cancelled"
|
||||||
|
return
|
||||||
|
_cancel_combo_elapsed = 0.0
|
||||||
if _waiting_for_neutral:
|
if _waiting_for_neutral:
|
||||||
if not _mapping_manager.are_capture_inputs_neutral(_capture_device_id):
|
if not _mapping_manager.are_capture_inputs_neutral(_capture_device_id):
|
||||||
_neutral_elapsed = 0.0
|
_neutral_elapsed = 0.0
|
||||||
|
|
@ -152,18 +164,7 @@ func _on_controller_input_observed(event: InputEvent) -> void:
|
||||||
return
|
return
|
||||||
if event_device != _capture_device_id:
|
if event_device != _capture_device_id:
|
||||||
return
|
return
|
||||||
if (
|
_track_cancel_combo_button(button_event)
|
||||||
button_event != null
|
|
||||||
and button_event.pressed
|
|
||||||
and _capturing_role != ControllerMappingManagerType.ROLE_B
|
|
||||||
and _mapping_manager.event_matches_role(
|
|
||||||
event,
|
|
||||||
ControllerMappingManagerType.ROLE_B,
|
|
||||||
)
|
|
||||||
):
|
|
||||||
_cancel_capture()
|
|
||||||
_progress_label.text = "controller mapping cancelled"
|
|
||||||
return
|
|
||||||
if _waiting_for_neutral:
|
if _waiting_for_neutral:
|
||||||
return
|
return
|
||||||
_try_capture_event(event)
|
_try_capture_event(event)
|
||||||
|
|
@ -332,6 +333,7 @@ func _begin_auto_map() -> void:
|
||||||
_auto_map_active = true
|
_auto_map_active = true
|
||||||
_auto_map_index = 0
|
_auto_map_index = 0
|
||||||
_auto_map_draft = {}
|
_auto_map_draft = {}
|
||||||
|
_reset_cancel_combo()
|
||||||
_capture_device_id = _mapping_manager.get_active_device_id()
|
_capture_device_id = _mapping_manager.get_active_device_id()
|
||||||
_set_capture_role(ControllerMappingManagerType.ROLE_ORDER[_auto_map_index])
|
_set_capture_role(ControllerMappingManagerType.ROLE_ORDER[_auto_map_index])
|
||||||
|
|
||||||
|
|
@ -340,6 +342,7 @@ func _begin_manual_capture(role: StringName) -> void:
|
||||||
_auto_map_active = false
|
_auto_map_active = false
|
||||||
_auto_map_index = -1
|
_auto_map_index = -1
|
||||||
_auto_map_draft.clear()
|
_auto_map_draft.clear()
|
||||||
|
_reset_cancel_combo()
|
||||||
_capture_device_id = _mapping_manager.get_active_device_id()
|
_capture_device_id = _mapping_manager.get_active_device_id()
|
||||||
_set_capture_role(role)
|
_set_capture_role(role)
|
||||||
|
|
||||||
|
|
@ -356,10 +359,14 @@ func _refresh_capture_prompt() -> void:
|
||||||
if _capturing_role.is_empty():
|
if _capturing_role.is_empty():
|
||||||
return
|
return
|
||||||
if _auto_map_active:
|
if _auto_map_active:
|
||||||
_progress_label.text = "step %d of %d: %s" % [
|
_progress_label.text = (
|
||||||
|
"step %d of %d: %s\n"
|
||||||
|
+ "hold both bumpers for %.2f seconds to cancel"
|
||||||
|
) % [
|
||||||
_auto_map_index + 1,
|
_auto_map_index + 1,
|
||||||
ControllerMappingManagerType.ROLE_ORDER.size(),
|
ControllerMappingManagerType.ROLE_ORDER.size(),
|
||||||
_mapping_manager.get_role_prompt(_capturing_role),
|
_mapping_manager.get_role_prompt(_capturing_role),
|
||||||
|
CANCEL_COMBO_HOLD_SECONDS,
|
||||||
]
|
]
|
||||||
return
|
return
|
||||||
var input_instruction: String = "press any controller button"
|
var input_instruction: String = "press any controller button"
|
||||||
|
|
@ -408,11 +415,34 @@ func _cancel_capture() -> void:
|
||||||
_auto_map_draft.clear()
|
_auto_map_draft.clear()
|
||||||
_waiting_for_neutral = false
|
_waiting_for_neutral = false
|
||||||
_neutral_elapsed = 0.0
|
_neutral_elapsed = 0.0
|
||||||
|
_reset_cancel_combo()
|
||||||
_set_action_buttons_disabled(false)
|
_set_action_buttons_disabled(false)
|
||||||
if _progress_label != null:
|
if _progress_label != null:
|
||||||
_progress_label.text = ""
|
_progress_label.text = ""
|
||||||
|
|
||||||
|
|
||||||
|
func _track_cancel_combo_button(button_event: InputEventJoypadButton) -> void:
|
||||||
|
if button_event == null:
|
||||||
|
return
|
||||||
|
match button_event.button_index:
|
||||||
|
JOY_BUTTON_LEFT_SHOULDER:
|
||||||
|
_cancel_left_bumper_pressed = button_event.pressed
|
||||||
|
JOY_BUTTON_RIGHT_SHOULDER:
|
||||||
|
_cancel_right_bumper_pressed = button_event.pressed
|
||||||
|
_:
|
||||||
|
return
|
||||||
|
if not (
|
||||||
|
_cancel_left_bumper_pressed and _cancel_right_bumper_pressed
|
||||||
|
):
|
||||||
|
_cancel_combo_elapsed = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
func _reset_cancel_combo() -> void:
|
||||||
|
_cancel_left_bumper_pressed = false
|
||||||
|
_cancel_right_bumper_pressed = false
|
||||||
|
_cancel_combo_elapsed = 0.0
|
||||||
|
|
||||||
|
|
||||||
func _set_action_buttons_disabled(disabled: bool) -> void:
|
func _set_action_buttons_disabled(disabled: bool) -> void:
|
||||||
if _auto_map_button != null:
|
if _auto_map_button != null:
|
||||||
_auto_map_button.disabled = disabled
|
_auto_map_button.disabled = disabled
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue