diff --git a/assets/state/states/settings_general_controller.tres b/assets/state/states/settings_general_controller.tres new file mode 100644 index 00000000..22f1db99 --- /dev/null +++ b/assets/state/states/settings_general_controller.tres @@ -0,0 +1,8 @@ +[gd_resource type="Resource" script_class="State" load_steps=2 format=3 uid="uid://bcekyu20uvkxv"] + +[ext_resource type="Script" path="res://core/systems/state/state.gd" id="1_ltxd0"] + +[resource] +script = ExtResource("1_ltxd0") +name = "General Controller Settings" +data = {} diff --git a/core/global/launch_manager.gd b/core/global/launch_manager.gd index fd2a0932..68eb83f6 100644 --- a/core/global/launch_manager.gd +++ b/core/global/launch_manager.gd @@ -208,6 +208,11 @@ func launch(app: LibraryLaunchItem) -> RunningApp: # Set the OGUI ID environment variable env["OGUI_ID"] = app.name + # Set input environment variables + var hidapi_enabled: bool = SettingsManager.get_value("general.controller", "sdl_hidapi_enabled", false) + if not hidapi_enabled: + env["SDL_JOYSTICK_HIDAPI"] = "0" + # Build any environment variables to include in the command var env_vars := PackedStringArray() for key in env.keys(): diff --git a/core/systems/input/gamepad_manager.gd b/core/systems/input/gamepad_manager.gd index 122906ea..b67556a6 100644 --- a/core/systems/input/gamepad_manager.gd +++ b/core/systems/input/gamepad_manager.gd @@ -20,7 +20,7 @@ signal gamepad_added(gamepad: ManagedGamepad) signal gamepad_removed var platform := load("res://core/global/platform.tres") as Platform -var device_hider := load("res://core/systems/input/device_hider.tres") as DeviceHider +var device_unhider := load("res://core/systems/input/device_hider.tres") as DeviceHider var input_thread := load("res://core/systems/threading/input_thread.tres") as SharedThread var gamepads := GamepadArray.new() @@ -38,8 +38,8 @@ func _init() -> void: logger.info("Not initializing. Ran from editor.") return - # If we crashed, unhide any device events that were orphaned - await device_hider.restore_all_hidden() + # Unhide any device events that were orphaned by bad programs like HandyGCCS + await device_unhider.restore_all_hidden() # Discover any gamepads and grab exclusive access to them. Create a # duplicate virtual gamepad for each physical one. @@ -143,17 +143,14 @@ func exit() -> void: gamepad.phys_device.grab(false) gamepad.virt_device.close() gamepad.phys_device.close() - device_hider.restore_event_device(gamepad.phys_path) if gamepad is HandheldGamepad: logger.debug("Cleaning up handheld gamepad: " + gamepad.gamepad.phys_path) gamepad.gamepad.phys_device.grab(false) gamepad.gamepad.virt_device.close() gamepad.gamepad.phys_device.close() - device_hider.restore_event_device(gamepad.gamepad.phys_path) gamepad.kb_device.grab(false) gamepad.kb_device.close() - device_hider.restore_event_device(gamepad.kb_event_path) ## Sets the gamepad intercept mode @@ -167,8 +164,6 @@ func set_intercept(mode: ManagedGamepad.INTERCEPT_MODE) -> void: ## access variables from the main thread func _process_input(_delta: float) -> void: # Process the input for all currently managed gamepads - if not is_instance_valid(gamepads): - return for gamepad in gamepads.items(): gamepad.process_input() @@ -236,10 +231,6 @@ func _on_gamepad_change(device: int, connected: bool) -> void: # Hide the device from other processes var path := discovered_handheld.get_path() - logger.debug("Trying to re-hide handheld gamepad") - var hidden_path := await device_hider.hide_event_device(path) - if hidden_path == "": - logger.warn("Unable to re-hide handheld gamepad: " + path) # Setup any handheld gamepads if they are discovered and not yet configured if not gamepads.has_handheld() and discovered_handheld: @@ -247,21 +238,13 @@ func _on_gamepad_change(device: int, connected: bool) -> void: logger.info("A handheld gamepad was discovered at: " + path) # Hide the device from other processes logger.debug("Trying to hide handheld gamepad") - var hidden_path := await device_hider.hide_event_device(path) - if hidden_path == "": - logger.warn("Unable to hide handheld gamepad: " + path) - logger.warn("Opening the raw handheld gamepad instead") - # Try to open the non-hidden device instead - hidden_path = path # Create a new managed gamepad with physical/virtual gamepad pair - logger.debug("Opening handheld gamepad at: " + hidden_path) + logger.debug("Opening handheld gamepad at: " + path) var gamepad := HandheldGamepad.new() - if gamepad.open(hidden_path) != OK: - logger.error("Unable to create handheld gamepad for: " + hidden_path) - if hidden_path != path: - logger.debug("Restoring device back to its regular path") - device_hider.restore_event_device(hidden_path) + if gamepad.open(path) != OK: + logger.error("Unable to create handheld gamepad for: " + path) + else: gamepad.setup(discovered_keyboards) gamepads.add(gamepad) @@ -277,24 +260,14 @@ func _on_gamepad_change(device: int, connected: bool) -> void: continue # See if we've identified the gamepad defined by the device platform. - if dev.get_phys() == "": - logger.debug("Device appears to be virtual, skipping " + path) + if is_device_virtual(dev): + logger.debug("Device appears to be virtual , skipping " + path) continue - - # Hide the device from other processes - var hidden_path := await device_hider.hide_event_device(path) - if hidden_path == "": - logger.warn("Unable to hide gamepad: " + path) - logger.warn("Opening the raw gamepad instead") - # Try to open the non-hidden device instead - hidden_path = path # Create a new managed gamepad with physical/virtual gamepad pair var gamepad := ManagedGamepad.new() - if gamepad.open(hidden_path) != OK: - logger.error("Unable to create managed gamepad for: " + hidden_path) - if hidden_path != path: - device_hider.restore_event_device(hidden_path) + if gamepad.open(path) != OK: + logger.error("Unable to create managed gamepad for: " + path) continue gamepads.add(gamepad) @@ -312,6 +285,35 @@ func _get_event_from_phys(phys_path: String) -> String: return event +## Returns true if the InputDevice is a virtual device. +func is_device_virtual(device: InputDevice) -> bool: + var event := device.get_path() + logger.debug("Checking if " + event + " is a virtual device.") + + if device.get_phys() != "": + logger.debug(event + " is real, it has a physical address: " + device.get_phys()) + return false + + var event_file := event.split("/")[-1] + var sysfs_devices := SysfsDevice.get_all() + logger.debug("Looking for " + event_file) + for sysfs_device in sysfs_devices: + logger.debug("Event Handlers: " + str(sysfs_device.handlers)) + if not event_file in sysfs_device.handlers: + continue + logger.debug("Found sysfs device for " + event_file) + + if "/devices/virtual" in sysfs_device.sysfs_path: + logger.debug("Device appears to be virtual") + return true + + logger.debug("Device is not in /devices/virtual. Treating as real.") + return false + + logger.debug("Unable to match device to any sysfs device.") + return true + + ## Structure for looking up and maintaining Gamepads objects class GamepadArray: var handheld: HandheldGamepad diff --git a/core/systems/input/managed_gamepad.gd b/core/systems/input/managed_gamepad.gd index 6beefeab..ec8512bb 100644 --- a/core/systems/input/managed_gamepad.gd +++ b/core/systems/input/managed_gamepad.gd @@ -38,16 +38,22 @@ var virt_device: VirtualInputDevice var virt_mouse := GamepadMouse.new() var abs_y_max: int var abs_y_min: int +var abs_y_mid: int var abs_x_max: int var abs_x_min: int +var abs_x_mid: int var abs_z_max: int var abs_z_min: int +var abs_z_mid: int var abs_ry_max: int var abs_ry_min: int +var abs_ry_mid: int var abs_rx_max: int var abs_rx_min: int +var abs_rx_mid: int var abs_rz_max: int var abs_rz_min: int +var abs_rz_mid: int ## Bitwise flags indicating what left-stick axis directions are currently being pressed. var axis_pressed: AXIS_PRESSED var should_process_mouse := false @@ -140,16 +146,27 @@ func open_physical(path: String) -> int: # Query information about the device abs_y_max = phys_device.get_abs_max(InputDeviceEvent.ABS_Y) abs_y_min = phys_device.get_abs_min(InputDeviceEvent.ABS_Y) + abs_y_mid = (abs_y_max + abs_y_min)/2 + logger.debug("Found axis range: " + " ".join([str(abs_y_min), str(abs_y_mid), str(abs_y_max)])) abs_x_max = phys_device.get_abs_max(InputDeviceEvent.ABS_X) abs_x_min = phys_device.get_abs_min(InputDeviceEvent.ABS_X) + abs_x_mid = (abs_x_max + abs_x_min)/2 + abs_z_max = phys_device.get_abs_max(InputDeviceEvent.ABS_Z) abs_z_min = phys_device.get_abs_min(InputDeviceEvent.ABS_Z) + abs_z_mid = (abs_z_max + abs_z_min)/2 + abs_ry_max = phys_device.get_abs_max(InputDeviceEvent.ABS_RY) abs_ry_min = phys_device.get_abs_min(InputDeviceEvent.ABS_RY) + abs_ry_mid = (abs_ry_max + abs_ry_min)/2 + abs_rx_max = phys_device.get_abs_max(InputDeviceEvent.ABS_RX) abs_rx_min = phys_device.get_abs_min(InputDeviceEvent.ABS_RX) + abs_rx_mid = (abs_rx_max + abs_rx_min)/2 + abs_rz_max = phys_device.get_abs_max(InputDeviceEvent.ABS_RZ) abs_rz_min = phys_device.get_abs_min(InputDeviceEvent.ABS_RZ) + abs_rz_mid = (abs_rz_max + abs_rz_min)/2 # Store value of get_phys() so this can be reidentified if disconnected. phys = phys_device.get_phys() @@ -753,7 +770,7 @@ func _translate_output_event(out_event: MappableEvent, value: float) -> Mappable func _is_axis_pressed(event: InputDeviceEvent, is_positive: bool) -> bool: var threshold := 0.35 var value := _normalize_axis(event) - logger.debug("_is_axis_pressed: " + event.get_code_name() + " code: " + str(event.get_code()) + " value: " + str(value)) + logger.debug("_is_axis_pressed: " + event.get_code_name() + " code: " + str(event.get_code()) + " value: " + str(value) + " event_value: " + str(event.value)) if is_positive: if value > threshold: logger.debug("_is_axis_pressed: true") @@ -770,57 +787,61 @@ func _is_axis_pressed(event: InputDeviceEvent, is_positive: bool) -> bool: # reflecting how far the axis has been pushed from the center func _normalize_axis(event: InputDeviceEvent) -> float: match event.get_code(): - event.ABS_Y: - if event.value > 0: - var maximum := abs_y_max - var value := event.value / float(maximum) + event.ABS_X: + var event_value := event.value - abs_x_mid + if event_value >= 0: + var maximum := abs_x_max - abs_x_mid + var value := event_value / float(maximum) return value - if event.value <= 0: - var minimum := abs_y_min - var value := event.value / float(minimum) + if event_value < 0: + var minimum := abs_x_min - abs_x_mid + var value := event_value / float(minimum) return -value - event.ABS_X: - if event.value > 0: - var maximum := abs_x_max - var value := event.value / float(maximum) + event.ABS_Y: + var event_value := event.value - abs_y_mid + if event_value >= 0: + var maximum := abs_y_max - abs_y_mid + var value := event_value / float(maximum) return value - if event.value <= 0: - var minimum := abs_x_min - var value := event.value / float(minimum) + if event_value < 0: + var minimum := abs_y_min - abs_y_mid + var value := event_value / float(minimum) return -value event.ABS_Z: - if event.value > 0: + if event.value >= 0: var maximum := abs_z_max var value := event.value / float(maximum) return value - if event.value <= 0: + if event.value < 0: var minimum := abs_z_min var value := event.value / float(minimum) return -value - event.ABS_RY: - if event.value > 0: - var maximum := abs_ry_max - var value := event.value / float(maximum) + event.ABS_RX: + var event_value := event.value - abs_rx_mid + if event_value >= 0: + var maximum := abs_rx_max - abs_rx_mid + var value := event_value / float(maximum) return value - if event.value <= 0: - var minimum := abs_ry_min - var value := event.value / float(minimum) + if event_value < 0: + var minimum := abs_rx_min - abs_rx_mid + var value := event_value / float(minimum) return -value - event.ABS_RX: - if event.value > 0: - var maximum := abs_rx_max - var value := event.value / float(maximum) + event.ABS_RY: + var event_value := event.value - abs_ry_mid + if event_value >= 0: + var maximum := abs_ry_max - abs_ry_mid + var value := event_value / float(maximum) return value - if event.value <= 0: - var minimum := abs_rx_min - var value := event.value / float(minimum) + if event_value < 0: + var minimum := abs_ry_min - abs_ry_mid + var value := event_value / float(minimum) return -value event.ABS_RZ: - if event.value > 0: + if event.value >= 0: var maximum := abs_rz_max var value := event.value / float(maximum) return value - if event.value <= 0: + if event.value < 0: var minimum := abs_rz_min var value := event.value / float(minimum) return -value @@ -832,58 +853,59 @@ func _normalize_axis(event: InputDeviceEvent) -> float: # and maximum values for the given axis. This does the opposite of _normalize_axis(). # E.g. _denormalize_axis(event.get_code(), 0.75) func _denormalize_axis(axis_code: int, normalized_value: float) -> float: + var normalized_value_abs: float = abs(normalized_value) match axis_code: + InputDeviceEvent.ABS_X: + if normalized_value >= 0: + var maximum := abs_x_max - abs_x_mid + var value := normalized_value * float(maximum) + abs_x_mid + return value + if normalized_value < 0: + var minimum := abs_x_min - abs_x_mid + var value: float = (normalized_value_abs * float(minimum)) + abs_x_mid + return value InputDeviceEvent.ABS_Y: - if normalized_value > 0: - var maximum := abs_y_max - var value := normalized_value * float(maximum) + if normalized_value >= 0: + var maximum := abs_y_max - abs_y_mid + var value := (normalized_value * float(maximum)) + abs_y_mid return value - if normalized_value <= 0: - var minimum := abs_y_min - var value := normalized_value * float(minimum) - return -value - InputDeviceEvent.ABS_X: - if normalized_value > 0: - var maximum := abs_x_max - var value := normalized_value * float(maximum) + if normalized_value < 0: + var minimum := abs_y_min - abs_y_mid + var value: float = (normalized_value_abs * float(minimum)) + abs_y_mid return value - if normalized_value <= 0: - var minimum := abs_x_min - var value := normalized_value * float(minimum) - return -value InputDeviceEvent.ABS_Z: - if normalized_value > 0: + if normalized_value >= 0: var maximum := abs_z_max var value := normalized_value * float(maximum) return value - if normalized_value <= 0: + if normalized_value < 0: var minimum := abs_z_min var value := normalized_value * float(minimum) return -value + InputDeviceEvent.ABS_RX: + if normalized_value >= 0: + var maximum := abs_rx_max - abs_rx_mid + var value := normalized_value * float(maximum) + abs_rx_mid + return value + if normalized_value < 0: + var minimum := abs_rx_min - abs_rx_mid + var value := (normalized_value_abs * float(minimum)) + abs_rx_mid + return value InputDeviceEvent.ABS_RY: - if normalized_value > 0: - var maximum := abs_ry_max - var value := normalized_value * float(maximum) + if normalized_value >= 0: + var maximum := abs_ry_max - abs_ry_mid + var value := normalized_value * float(maximum) + abs_ry_mid return value - if normalized_value <= 0: - var minimum := abs_ry_min - var value := normalized_value * float(minimum) - return -value - InputDeviceEvent.ABS_RX: - if normalized_value > 0: - var maximum := abs_rx_max - var value := normalized_value * float(maximum) + if normalized_value < 0: + var minimum := abs_ry_min - abs_ry_mid + var value := (normalized_value_abs * float(minimum)) + abs_ry_mid return value - if normalized_value <= 0: - var minimum := abs_rx_min - var value := normalized_value * float(minimum) - return -value InputDeviceEvent.ABS_RZ: - if normalized_value > 0: + if normalized_value >= 0: var maximum := abs_rz_max var value := normalized_value * float(maximum) return value - if normalized_value <= 0: + if normalized_value < 0: var minimum := abs_rz_min var value := normalized_value * float(minimum) return -value diff --git a/core/systems/input/managed_gamepad_test.gd b/core/systems/input/managed_gamepad_test.gd new file mode 100644 index 00000000..b8cc4e48 --- /dev/null +++ b/core/systems/input/managed_gamepad_test.gd @@ -0,0 +1,160 @@ +extends GutTest + +const EV_ABS := InputDeviceEvent.EV_ABS +const ABS_X := InputDeviceEvent.ABS_X +const ABS_Y := InputDeviceEvent.ABS_Y +const ABS_Z := InputDeviceEvent.ABS_Z +const ABS_RX := InputDeviceEvent.ABS_RX +const ABS_RY := InputDeviceEvent.ABS_RY +const ABS_RZ := InputDeviceEvent.ABS_RZ + + +# Test parameters for testing normalizing an axis value from -1 - 1. +# The test data should be in the following form: +# [axis_min, axis_max, [event_codes], value, normalized_value] +var normalize_axis_params := [ + # Test values that emulate a Playstation controller. These controllers + # use only positive values for their joystick axes. + [NormalizeAxisParam.new( 0, 300, [ABS_X, ABS_Y, ABS_RX, ABS_RY], 150, 0.0)], + [NormalizeAxisParam.new( 0, 300, [ABS_X, ABS_Y, ABS_RX, ABS_RY], 0, -1.0)], + [NormalizeAxisParam.new( 0, 300, [ABS_X, ABS_Y, ABS_RX, ABS_RY], 300, 1.0)], + [NormalizeAxisParam.new( 0, 300, [ABS_X, ABS_Y, ABS_RX, ABS_RY], 75, -0.5)], + [NormalizeAxisParam.new( 0, 300, [ABS_X, ABS_Y, ABS_RX, ABS_RY], 225, 0.5)], + + # Test values with a positive/negative min/max range like XBox controllers. + [NormalizeAxisParam.new(-100, 100, [ABS_X, ABS_Y, ABS_RX, ABS_RY], 0, 0.0)], + [NormalizeAxisParam.new(-100, 100, [ABS_X, ABS_Y, ABS_RX, ABS_RY],-100, -1.0)], + [NormalizeAxisParam.new(-100, 100, [ABS_X, ABS_Y, ABS_RX, ABS_RY], 100, 1.0)], + [NormalizeAxisParam.new(-100, 100, [ABS_X, ABS_Y, ABS_RX, ABS_RY], -50, -0.5)], + [NormalizeAxisParam.new(-100, 100, [ABS_X, ABS_Y, ABS_RX, ABS_RY], 50, 0.5)], + + # Test values for triggers with only positive values. These work differently + # in that they should be expressed in distance from zero. + [NormalizeAxisParam.new( 0, 300, [ABS_Z, ABS_RZ], 0, 0.0)], + [NormalizeAxisParam.new( 0, 300, [ABS_Z, ABS_RZ], 300, 1.0)], + [NormalizeAxisParam.new( 0, 300, [ABS_Z, ABS_RZ], 150, 0.5)], + + # Test values for triggers with positive/negative min/max ranges. These + # work differently in that they should be expressed in distance from zero. + [NormalizeAxisParam.new(-100, 100, [ABS_Z, ABS_RZ], 0, 0.0)], + [NormalizeAxisParam.new(-100, 100, [ABS_Z, ABS_RZ], -100, -1.0)], + [NormalizeAxisParam.new(-100, 100, [ABS_Z, ABS_RZ], 100, 1.0)], + [NormalizeAxisParam.new(-100, 100, [ABS_Z, ABS_RZ], -50, -0.5)], + [NormalizeAxisParam.new(-100, 100, [ABS_Z, ABS_RZ], 50, 0.5)], +] + +# Uses the axis minimum and maximum values to return a value from -1.0 - 1.0 +# reflecting how far the axis has been pushed from a center point. +func test_normalize_axis(params=use_parameters(normalize_axis_params)) -> void: + # Pull out the test params + var param := params[0] as NormalizeAxisParam + var axis_min := param.axis_min + var axis_max := param.axis_max + var events := param.get_events() + var expected := param.normalized + + # Create a gamepad and set the appropriate axis values + var gamepad := ManagedGamepad.new() + set_gamepad_min_mid_max(gamepad, axis_min, axis_max) + + # Try to normalize the input events + for event in events: + var normalized := gamepad._normalize_axis(event) + assert_eq(normalized, expected) + + +# Uses the axis minimum and maximum values to return a value from axis_min -> axis_max +# from a normalized axis value ranging from -1.0 - 1.0 +func test_denormalize_axis(params=use_parameters(normalize_axis_params)) -> void: + # Pull out the test params + var param := params[0] as NormalizeAxisParam + var axis_min := param.axis_min + var axis_max := param.axis_max + var events := param.get_events() + var expected := param.normalized + + # Create a gamepad and set the appropriate axis values + var gamepad := ManagedGamepad.new() + set_gamepad_min_mid_max(gamepad, axis_min, axis_max) + + # Try to denormalize the input events + for event in events: + gut.p("Testing params: " + str(event.code) + " " + str(expected)) + var denormalized := gamepad._denormalize_axis(event.code, expected) + assert_eq(denormalized, event.value) + + +func create_event(type: int, code: int, value: int) -> InputDeviceEvent: + var event := InputDeviceEvent.new() + event.type = type + event.code = code + event.value = value + return event + + +func create_events(type: int, codes: Array[int], value: int) -> Array[InputDeviceEvent]: + var events: Array[InputDeviceEvent] = [] + for code in codes: + var event := create_event(type, code, value) + events.append(event) + + return events + + +func set_gamepad_min_mid_max(gamepad: ManagedGamepad, axis_min: int, axis_max: int) -> void: + var axis_mid := (axis_max + axis_min)/2 + gut.p("Mid point for axis: " + str(axis_mid)) + gamepad.abs_x_min = axis_min + gamepad.abs_x_max = axis_max + gamepad.abs_x_mid = axis_mid + gamepad.abs_y_min = axis_min + gamepad.abs_y_max = axis_max + gamepad.abs_y_mid = axis_mid + gamepad.abs_z_min = axis_min + gamepad.abs_z_max = axis_max + gamepad.abs_z_mid = axis_mid + gamepad.abs_rx_min = axis_min + gamepad.abs_rx_max = axis_max + gamepad.abs_rx_mid = axis_mid + gamepad.abs_ry_min = axis_min + gamepad.abs_ry_max = axis_max + gamepad.abs_ry_mid = axis_mid + gamepad.abs_rz_min = axis_min + gamepad.abs_rz_max = axis_max + gamepad.abs_rz_mid = axis_mid + + +# Used for testing axis normalization. +class NormalizeAxisParam: + var axis_min: int + var axis_max: int + var type: int = EV_ABS + var codes: Array[int] = [] + var event_value: int + var normalized: float + + func _init(ax_min: int, ax_max: int, ev_codes: Array[int], value: int, norm: float) -> void: + axis_min = ax_min + axis_max = ax_max + codes = ev_codes + event_value = value + normalized = norm + + func create_event(code: int) -> InputDeviceEvent: + var event := InputDeviceEvent.new() + event.type = type + event.code = code + event.value = event_value + return event + + func get_events() -> Array[InputDeviceEvent]: + var events: Array[InputDeviceEvent] = [] + for code in codes: + var event := create_event(code) + events.append(event) + return events + + func _to_string() -> String: + return "AxisRange({0}-{1}) Codes({2}) EventValue({3}) NormalizedValue({4})".format( + [axis_min, axis_max, " ".join(codes), event_value, normalized] + ) diff --git a/core/ui/card_ui/settings/general_controller_settings_menu.gd b/core/ui/card_ui/settings/general_controller_settings_menu.gd new file mode 100644 index 00000000..d568c4a8 --- /dev/null +++ b/core/ui/card_ui/settings/general_controller_settings_menu.gd @@ -0,0 +1,16 @@ +extends Control + +var settings_manager := preload("res://core/global/settings_manager.tres") as SettingsManager + +@onready var sdl_hidapi_toggle := $%SDLHIDAPIToggle as Toggle + +# Called when the node enters the scene tree for the first time. +func _ready(): + sdl_hidapi_toggle.button_pressed = settings_manager.get_value("general.controller", "sdl_hidapi_enabled", false) + sdl_hidapi_toggle.toggled.connect(_on_sdl_hidapi_toggled) + + +## Called when the SDLHIDAPIToggle is toggled. +func _on_sdl_hidapi_toggled(pressed: bool) -> void: + settings_manager.set_value("general.controller", "sdl_hidapi_enabled", pressed) + pass diff --git a/core/ui/card_ui/settings/general_controller_settings_menu.tscn b/core/ui/card_ui/settings/general_controller_settings_menu.tscn new file mode 100644 index 00000000..eae88151 --- /dev/null +++ b/core/ui/card_ui/settings/general_controller_settings_menu.tscn @@ -0,0 +1,52 @@ +[gd_scene load_steps=8 format=3 uid="uid://cgxl1qiu50h15"] + +[ext_resource type="Script" path="res://core/ui/card_ui/settings/general_controller_settings_menu.gd" id="1_r712q"] +[ext_resource type="PackedScene" uid="uid://8m20p2s0v5gb" path="res://core/systems/input/focus_group.tscn" id="2_fdccl"] +[ext_resource type="PackedScene" uid="uid://shvyhrv5sx3v" path="res://core/systems/state/state_watcher.tscn" id="2_siloa"] +[ext_resource type="PackedScene" uid="uid://d1qb7euwlu7bh" path="res://core/ui/components/toggle.tscn" id="2_xfhsb"] +[ext_resource type="Resource" uid="uid://dgi16frh3mgj8" path="res://core/ui/card_ui/settings/settings_menu_focus.tres" id="3_6p4qa"] +[ext_resource type="Resource" uid="uid://bcekyu20uvkxv" path="res://assets/state/states/settings_general_controller.tres" id="3_yy4fy"] +[ext_resource type="PackedScene" uid="uid://bw8113ocotx2r" path="res://core/systems/effects/fade_effect.tscn" id="4_g1xc4"] + +[node name="GeneralControllerSettings" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_r712q") + +[node name="StateWatcher" parent="." instance=ExtResource("2_siloa")] +state = ExtResource("3_yy4fy") + +[node name="FadeEffect" parent="StateWatcher" node_paths=PackedStringArray("target") instance=ExtResource("4_g1xc4")] +target = NodePath("../..") +on_signal = "state_entered" +fade_out_signal = "state_exited" +on_signal = "state_entered" + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="FocusGroup" parent="MarginContainer/VBoxContainer" instance=ExtResource("2_fdccl")] +focus_stack = ExtResource("3_6p4qa") + +[node name="SDLHIDAPIToggle" parent="MarginContainer/VBoxContainer" instance=ExtResource("2_xfhsb")] +unique_name_in_owner = true +layout_mode = 2 +text = "SDL HIDAPI Enabled" +description = "Alternate input system that can cause double input. Some games use SDL for gyro controls or all input. Requires restart." +button_pressed = false diff --git a/core/ui/card_ui/settings/settings_menu.tscn b/core/ui/card_ui/settings/settings_menu.tscn index fbd3780e..cdeb1202 100644 --- a/core/ui/card_ui/settings/settings_menu.tscn +++ b/core/ui/card_ui/settings/settings_menu.tscn @@ -1,11 +1,11 @@ -[gd_scene load_steps=41 format=3 uid="uid://d2jiecrd5sw4s"] +[gd_scene load_steps=43 format=3 uid="uid://d2jiecrd5sw4s"] [ext_resource type="Script" path="res://core/ui/card_ui/settings/settings_menu.gd" id="1_x5bjx"] [ext_resource type="PackedScene" uid="uid://orey8uxm7v6v" path="res://core/systems/state/visibility_manager.tscn" id="2_xwevg"] [ext_resource type="Resource" uid="uid://d3gp85f35oiw6" path="res://assets/state/states/settings.tres" id="3_an6bd"] [ext_resource type="Resource" uid="uid://e7bbebwf7guj" path="res://assets/state/states/main_menu.tres" id="4_3yyw8"] [ext_resource type="Resource" uid="uid://bmgs1ngma1523" path="res://assets/state/states/in_game_menu.tres" id="5_t0lm2"] -[ext_resource type="Resource" uid="uid://bp807nlks8eq1" path="res://assets/state/states/quick_bar.tres" id="6_l4ywh"] +[ext_resource type="Resource" uid="uid://bp807nlks8eq1" path="res://assets/state/states/quick_bar_menu.tres" id="6_l4ywh"] [ext_resource type="Resource" uid="uid://dja3m1mevv6xw" path="res://assets/state/states/osk.tres" id="7_mmjyu"] [ext_resource type="Resource" uid="uid://bw0mtk7sso8m2" path="res://assets/state/states/power_menu.tres" id="8_u3uua"] [ext_resource type="PackedScene" uid="uid://ccd4sw84h1qbc" path="res://core/systems/input/back_input_handler.tscn" id="9_op1y2"] @@ -29,6 +29,7 @@ [ext_resource type="Resource" uid="uid://cakuo0qwrrkk8" path="res://assets/state/states/settings_logging.tres" id="25_nijem"] [ext_resource type="PackedScene" uid="uid://b0cyl6fdqxevn" path="res://core/systems/input/scroller_joystick.tscn" id="27_krt45"] [ext_resource type="PackedScene" uid="uid://dithv38oqgy58" path="res://core/ui/components/section_label.tscn" id="27_x4tym"] +[ext_resource type="Resource" uid="uid://bcekyu20uvkxv" path="res://assets/state/states/settings_general_controller.tres" id="28_nmfnj"] [ext_resource type="PackedScene" uid="uid://dsgrw1grwef4m" path="res://core/ui/card_ui/settings/general_settings_menu.tscn" id="29_8hwjo"] [ext_resource type="PackedScene" uid="uid://bo077a5mwi7xl" path="res://core/ui/components/transition_fade_in.tscn" id="29_grs7x"] [ext_resource type="PackedScene" uid="uid://521da7e2cdxd" path="res://core/ui/vapor_ui/settings/display_settings_menu.tscn" id="30_22tgj"] @@ -39,6 +40,7 @@ [ext_resource type="PackedScene" uid="uid://cliqk7lo4t8ao" path="res://core/ui/card_ui/settings/plugin_settings_menu.tscn" id="35_dooq8"] [ext_resource type="PackedScene" uid="uid://bfu4edkk5dqt2" path="res://core/ui/vapor_ui/debug/processes_menu.tscn" id="36_hnokx"] [ext_resource type="PackedScene" uid="uid://csor0e54svgja" path="res://core/ui/vapor_ui/settings/logging_settings_menu.tscn" id="37_iwtxk"] +[ext_resource type="PackedScene" uid="uid://cgxl1qiu50h15" path="res://core/ui/card_ui/settings/general_controller_settings_menu.tscn" id="40_uqw2r"] [sub_resource type="StyleBoxLine" id="StyleBoxLine_ob3o7"] content_margin_top = 20.0 @@ -311,6 +313,20 @@ on_signal = "focus_entered" target = NodePath("../../../../../../../../../ContentContainer/VBoxContainer/ContentContainer/MarginContainer/LoggingSettings/MarginContainer/VBoxContainer/FocusGroup") on_signal = "pressed" +[node name="ControllerButton" parent="MarginContainer/HBoxContainer/MenuContainer/VBoxContainer/ButtonContainer/MarginContainer/ScrollContainer/MarginContainer/SettingButtonsContainer" instance=ExtResource("14_obber")] +layout_mode = 2 +text = "Controllers" + +[node name="StateUpdater" parent="MarginContainer/HBoxContainer/MenuContainer/VBoxContainer/ButtonContainer/MarginContainer/ScrollContainer/MarginContainer/SettingButtonsContainer/ControllerButton" instance=ExtResource("14_cid64")] +state_machine = ExtResource("15_cb33u") +state = ExtResource("28_nmfnj") +action = 3 +on_signal = "focus_entered" + +[node name="FocusGroupSetter" parent="MarginContainer/HBoxContainer/MenuContainer/VBoxContainer/ButtonContainer/MarginContainer/ScrollContainer/MarginContainer/SettingButtonsContainer/ControllerButton" node_paths=PackedStringArray("target") instance=ExtResource("18_axp35")] +target = NodePath("../../../../../../../../../ContentContainer/VBoxContainer/ContentContainer/MarginContainer/GeneralControllerSettings/MarginContainer/VBoxContainer/FocusGroup") +on_signal = "pressed" + [node name="ScrollerJoystick" parent="MarginContainer/HBoxContainer/MenuContainer/VBoxContainer/ButtonContainer/MarginContainer/ScrollContainer" instance=ExtResource("27_krt45")] [node name="ContentContainer" type="MarginContainer" parent="MarginContainer/HBoxContainer"] @@ -431,6 +447,10 @@ state = ExtResource("25_nijem") [node name="TransitionFadeIn" parent="MarginContainer/HBoxContainer/ContentContainer/VBoxContainer/ContentContainer/MarginContainer/LoggingSettings/VisibilityManager" instance=ExtResource("29_grs7x")] +[node name="GeneralControllerSettings" parent="MarginContainer/HBoxContainer/ContentContainer/VBoxContainer/ContentContainer/MarginContainer" instance=ExtResource("40_uqw2r")] +visible = false +layout_mode = 2 + [editable path="MarginContainer/HBoxContainer/ContentContainer/VBoxContainer/ContentContainer/MarginContainer/GeneralSettings"] [editable path="MarginContainer/HBoxContainer/ContentContainer/VBoxContainer/ContentContainer/MarginContainer/DisplaySettings"] [editable path="MarginContainer/HBoxContainer/ContentContainer/VBoxContainer/ContentContainer/MarginContainer/NetworkSettings"] @@ -440,3 +460,4 @@ state = ExtResource("25_nijem") [editable path="MarginContainer/HBoxContainer/ContentContainer/VBoxContainer/ContentContainer/MarginContainer/PluginStoreMenu"] [editable path="MarginContainer/HBoxContainer/ContentContainer/VBoxContainer/ContentContainer/MarginContainer/ProcessesMenu"] [editable path="MarginContainer/HBoxContainer/ContentContainer/VBoxContainer/ContentContainer/MarginContainer/LoggingSettings"] +[editable path="MarginContainer/HBoxContainer/ContentContainer/VBoxContainer/ContentContainer/MarginContainer/GeneralControllerSettings"] diff --git a/core/ui/card_ui_overlay_mode/card_ui_overlay_mode.gd b/core/ui/card_ui_overlay_mode/card_ui_overlay_mode.gd index 25124b41..6b046112 100644 --- a/core/ui/card_ui_overlay_mode/card_ui_overlay_mode.gd +++ b/core/ui/card_ui_overlay_mode/card_ui_overlay_mode.gd @@ -17,9 +17,8 @@ var display := Gamescope.XWAYLAND.OGUI var game_running: bool = false var window_id: int var pid: int = OS.get_process_id() -var shared_thread: SharedThread var underlay_log: FileAccess -var underlay_process: InteractiveProcess +var underlay_process: int var underlay_window_id: int @onready var quick_bar_menu := $%QuickBarMenu @@ -143,22 +142,14 @@ func _run_child_killer(remove_list: PackedStringArray, parent:Node) -> void: func _start_steam_process(args: Array) -> void: # Setup steam var underlay_log_path = OS.get_environment("HOME") + "/.steam-stdout.log" + if not settings_manager.get_value("general.controller", "sdl_hidapi_enabled", false): + logger.debug("SDL HIDAPI Disabled.") + args.push_front("&&") + args.push_front("SDL_JOYSTICK_HIDAPI=0") + args.push_front("export") _start_underlay_process(args, underlay_log_path) - # Look for steam and save window ID - while not underlay_window_id: - # Find Steam in the display tree - var root_win_id := gamescope.get_root_window_id(display) - var all_windows := gamescope.get_all_windows(root_win_id, display) - for window in all_windows: - if window == window_id: - continue - if gamescope.has_xprop(window, "STEAM_OVERLAY", display): - underlay_window_id = window - logger.debug("Found steam! " + str(underlay_window_id)) - break - # Wait a bit to reduce cpu load. - OS.delay_msec(1000) + _find_underlay_window_id() # Start timer to check if steam has exited. var exit_timer := Timer.new() @@ -169,10 +160,11 @@ func _start_steam_process(args: Array) -> void: exit_timer.start() +## Called to start the specified underlay process. func _start_underlay_process(args: Array, log_path: String) -> void: - # Start the shared thread we use for logging. - shared_thread = SharedThread.new() - shared_thread.start() + # Set up loggining in the new thread. + args.append("2>&1") + args.append(log_path) # Setup logging underlay_log = FileAccess.open(log_path, FileAccess.WRITE) @@ -181,15 +173,31 @@ func _start_underlay_process(args: Array, log_path: String) -> void: logger.warn("Got error opening log file.") else: logger.info("Started logging underlay process at " + log_path) - var command: String = args[0] - args.remove_at(0) - underlay_process = InteractiveProcess.new(command, args) - if underlay_process.start() != OK: - logger.error("Failed to start child process.") - return - var logger_func := func(delta: float): - underlay_process.output_to_log_file(underlay_log) - shared_thread.add_process(logger_func) + var command: String = "bash" + underlay_process = Reaper.create_process(command, ["-c", " ".join(args)]) + + +## Called to idendify the xwayland window ID of the underlay process. +func _find_underlay_window_id() -> void: + # Find Steam in the display tree + var root_win_id := gamescope.get_root_window_id(display) + var all_windows := gamescope.get_all_windows(root_win_id, display) + for window in all_windows: + if window == window_id: + continue + if gamescope.has_xprop(window, "STEAM_OVERLAY", display): + underlay_window_id = window + logger.debug("Found steam! " + str(underlay_window_id)) + break + + # If we didn't find the window_id, set up a tiemr to loop back and try again. + if not underlay_window_id: + var underlay_window_timer := Timer.new() + underlay_window_timer.set_one_shot(true) + underlay_window_timer.set_timer_process_callback(Timer.TIMER_PROCESS_IDLE) + underlay_window_timer.timeout.connect(_find_underlay_window_id) + add_child(underlay_window_timer) + underlay_window_timer.start() ## Called when "quick_bar_menu_state" is entered. @@ -226,6 +234,8 @@ func _on_app_switched(_from: RunningApp, to: RunningApp) -> void: ## Verifies steam is still running by checking for the steam overlay, closes otherwise. func _check_exit() -> void: + if Reaper.get_pid_state(underlay_process) in ["R (running)", "S (sleeping)"]: + return if gamescope.has_xprop(underlay_window_id, "STEAM_OVERLAY", display): return logger.debug("Steam closed. Shutting down.") diff --git a/core/ui/components/toggle.tscn b/core/ui/components/toggle.tscn index fcf363ce..e1f23f91 100644 --- a/core/ui/components/toggle.tscn +++ b/core/ui/components/toggle.tscn @@ -27,6 +27,7 @@ label_settings = ExtResource("2_msehc") [node name="CheckButton" type="CheckButton" parent="ToggleContainer"] unique_name_in_owner = true +modulate = Color(0, 0, 0, 1) layout_mode = 2 button_pressed = true @@ -35,6 +36,7 @@ unique_name_in_owner = true visible = false layout_mode = 2 label_settings = ExtResource("3_l7kb7") +autowrap_mode = 3 [node name="HSeparator" type="HSeparator" parent="."] layout_mode = 2