Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions core/global/launch_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ var _persist_path: String = "/".join([_data_dir, "launcher.json"])
var _persist_data: Dictionary = {"version": 1}
var _ogui_window_id := 0
var should_manage_overlay := true
var steam_input_enabled := false
var logger := Log.get_logger("LaunchManager", Log.LEVEL.INFO)
var _focused_app_id := 0
var _input_manager: InputManager
Expand Down Expand Up @@ -476,6 +477,13 @@ func set_app_gamepad_profile(app: RunningApp) -> void:
var section := ".".join(["game", app.launch_item.name.to_lower()])
var profile_path := settings_manager.get_value(section, "gamepad_profile", "") as String
var profile_gamepad := settings_manager.get_value(section, "gamepad_profile_target", "") as String
if steam_input_enabled and not (profile_path.is_empty() and profile_gamepad.is_empty()):
# Steam Input handles per-game controller configuration itself while Steam
# is running, so there's no value in us applying our own on top of it.
# Defer to Steam and keep the session-wide profile and target.
logger.debug("Ignoring per-game gamepad profile. Steam Input is in use")
profile_path = ""
profile_gamepad = ""
if profile_path.is_empty():
logger.debug("Using global gamepad profile")
else:
Expand All @@ -485,23 +493,28 @@ func set_app_gamepad_profile(app: RunningApp) -> void:

## Sets the gamepad profile for the running app with the given profile
func set_gamepad_profile(profile_path: String, target_gamepad: String = "") -> void:
if target_gamepad.is_empty():
# If overridden by settings, use that, so the user's selection survives
# a restart.
Comment thread
KyleGospo marked this conversation as resolved.
#TODO: This is a global setting, refactor settings to permit individual gamepads to have different targets
target_gamepad = settings_manager.get_value("input", "gamepad_profile_target", "") as String
if target_gamepad.is_empty() and is_instance_valid(_input_manager):
# Otherwise use the default for the current mode. Overlay mode emulates a
# Steam Deck controller when Steam Input is in use.
target_gamepad = _input_manager.get_default_target_gamepad()

# Discover the currently set target for each gamepad to properly add additional
# capabilities based on that target
for device: CompositeDevice in input_plumber.get_composite_devices():
# Fall back to the gamepad currently set on this composite device
if target_gamepad.is_empty():
# First, find the currently set gamepad on the composite device
var targets = device.get_target_devices()
for target in targets:
var target_dbus_path: String = target.get("dbus_path")
if not target_dbus_path.contains("target/gamepad"):
continue
target_gamepad = target.get("device_type")
break
if not target_gamepad.is_empty():
break
# Then, if overriden by settings, use that instead
#TODO: This is a global setting, refactor settings to permit individual gamepads to have different targets
Comment thread
KyleGospo marked this conversation as resolved.
target_gamepad = settings_manager.get_value("input", "gamepad_profile_target", target_gamepad) as String

# If no profile was specified, unset the gamepad profiles
if profile_path == "":
Expand Down
8 changes: 5 additions & 3 deletions core/main.gd
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ func _show_help() -> void:
print(" help Display this help message")
print("")
print("Flags:")
print(" --vapor-ui Load Vapor UI")
print(" --only-qam Launch in overlay mode (Deprecated)")
print(" --overlay-mode Launch in overlay mode")
print(" --vapor-ui Load Vapor UI")
print(" --only-qam Launch in overlay mode (Deprecated)")
print(" --overlay-mode Launch in overlay mode")
print(" --steamos-manager Defer conflicting features to SteamOS Manager")
print(" --steam-input Defer controller configuration to Steam Input")
print("")
print("Environment Variables:")
print(" LOG_LEVEL Set the global log level (debug,info,warn,error)")
Expand Down
1 change: 1 addition & 0 deletions core/platform/handheld/gpd/gpd_gen3.tres
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ icon_mappings = Array[ExtResource("1_cxaik")]([])
name = ""
startup_actions = Array[ExtResource("3_woapc")]([])
shutdown_actions = Array[ExtResource("3_woapc")]([])
target_gamepad_override = "xbox-elite"
6 changes: 6 additions & 0 deletions core/platform/platform_provider.gd
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ class_name PlatformProvider
@export var name: String ## Name of the platform
@export var startup_actions: Array[PlatformAction] ## Actions to take upon startup
@export var shutdown_actions: Array[PlatformAction] ## Actions to take upon shutdown
## InputPlumber target device to emulate instead of the default one chosen for
## the current session. Leave empty to use the default. Set this for devices
## where the default target is a bad fit, such as handhelds without a dedicated
## Quick Access Menu button, where Steam ignores the guide+south chord on
## Steam Deck and Horipad targets and leaves the user no way to open the QAM.
@export var target_gamepad_override: String
var logger := Log.get_logger("PlatformProvider", Log.LEVEL.INFO)


Expand Down
8 changes: 8 additions & 0 deletions core/systems/input/input_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ func get_default_global_profile_path() -> String:
return "user://data/gamepad/profiles/global_default.json"


## Returns the InputPlumber target device to emulate when the user has not
## configured one. Stubbed out here because the full session never overrides the
## user's controller; it exists so callers can invoke it on any InputManager
## without crashing. Overridden by OverlayModeInputManager.
func get_default_target_gamepad() -> String:
return ""


## Returns true if the given event is an InputPlumber event
static func is_inputplumber_event(event: InputEvent) -> bool:
return event.has_meta("dbus_path")
Expand Down
56 changes: 56 additions & 0 deletions core/systems/input/input_manager_test.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
extends GutTest


# Test that the default input manager makes no assumptions about the target
# gamepad, leaving whatever target InputPlumber already configured in place.
func test_get_default_target_gamepad() -> void:
var input_manager := InputManager.new()
assert_eq(input_manager.get_default_target_gamepad(), "", "should not default to any target gamepad")
input_manager.free()


# Test that overlay mode only assumes a target gamepad when Steam Input is in
# use, so games running under Steam see the same controller they would on a
# Deck while other sessions keep whatever is already configured.
func test_get_default_target_gamepad_overlay_mode() -> void:
var launch_manager := load("res://core/global/launch_manager.tres") as LaunchManager
var platform := load("res://core/global/platform.tres") as Platform
var was_steam_input := launch_manager.steam_input_enabled
var was_platform := platform.platform
var input_manager := OverlayInputManager.new()
platform.platform = null

launch_manager.steam_input_enabled = false
assert_eq(input_manager.get_default_target_gamepad(), "", "should not default to any target gamepad without Steam Input")

launch_manager.steam_input_enabled = true
assert_eq(input_manager.get_default_target_gamepad(), "deck-uhid", "should default to the Steam Deck target gamepad under Steam Input")

platform.platform = was_platform
launch_manager.steam_input_enabled = was_steam_input
input_manager.free()


# Test that a platform can override the default target gamepad. Devices without
# a dedicated Quick Access Menu button need this, as Steam ignores the guide
# button chords on the Steam Deck target and leaves no way to open the QAM.
func test_get_default_target_gamepad_platform_override() -> void:
var launch_manager := load("res://core/global/launch_manager.tres") as LaunchManager
var platform := load("res://core/global/platform.tres") as Platform
var was_steam_input := launch_manager.steam_input_enabled
var was_platform := platform.platform
var input_manager := OverlayInputManager.new()

var provider := PlatformProvider.new()
provider.target_gamepad_override = "xbox-elite"
platform.platform = provider

launch_manager.steam_input_enabled = true
assert_eq(input_manager.get_default_target_gamepad(), "xbox-elite", "should use the target gamepad the platform asked for")

launch_manager.steam_input_enabled = false
assert_eq(input_manager.get_default_target_gamepad(), "", "should not default to any target gamepad without Steam Input")

platform.platform = was_platform
launch_manager.steam_input_enabled = was_steam_input
input_manager.free()
14 changes: 14 additions & 0 deletions core/systems/input/overlay_mode_input_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class_name OverlayInputManager

var menu_state_machine := preload("res://assets/state/state_machines/menu_state_machine.tres") as StateMachine
var base_state = preload("res://assets/state/states/in_game.tres") as State
var platform := load("res://core/global/platform.tres") as Platform

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
Expand All @@ -30,6 +31,19 @@ func _get_default_profile_path() -> String:
return "res://assets/gamepad/profiles/default_overlay.json"


## In overlay mode we run alongside an underlay like Steam. Emulate a Steam Deck
## controller by default so Steam recognizes the guide button chords, unless the
## detected platform asks for a different target.
func get_default_target_gamepad() -> String:
if not launch_manager.steam_input_enabled:
return ""
if platform.platform and not platform.platform.target_gamepad_override.is_empty():
var override := platform.platform.target_gamepad_override
logger.debug("Platform overrides the default target gamepad with " + override)
return override
return "deck-uhid"


func get_default_global_profile_path() -> String:
return "user://data/gamepad/profiles/global_default_overlay.json"

Expand Down
34 changes: 24 additions & 10 deletions core/systems/performance/performance_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ var _settings_manager := load("res://core/global/settings_manager.tres") as Sett
var _power_station := load("res://core/systems/performance/power_station.tres") as PowerStationInstance
var _launch_manager := load("res://core/global/launch_manager.tres") as LaunchManager

## Whether the GPU settings, the performance level, and the clock
## frequency, are managed by the session OpenGamepadUI is running on top of.
## Static so it can be set before this resource is ever loaded -- _init() applies
## the saved profile as soon as performance_manager.tres is first loaded, so an
## instance variable would be set too late to stop that first TDP write.
static var session_manages_gpu_power := false

var display_device := _power_manager.get_display_device()
var current_profile: PerformanceProfile
var current_profile_state: PROFILE_STATE # docked or undocked
Expand Down Expand Up @@ -170,6 +177,23 @@ func apply_profile(profile: PerformanceProfile) -> void:

logger.info("Applying performance profile: " + profile.name)

# Apply CPU settings from the given profile
if _power_station.cpu:
logger.debug("Applying CPU performance settings from profile")
if _power_station.cpu.boost_enabled != profile.cpu_boost_enabled:
_power_station.cpu.boost_enabled = profile.cpu_boost_enabled
if _power_station.cpu.smt_enabled != profile.cpu_smt_enabled:
_power_station.cpu.smt_enabled = profile.cpu_smt_enabled
if profile.cpu_core_count_current > 0 and _power_station.cpu.cores_enabled != profile.cpu_core_count_current:
_power_station.cpu.cores_enabled = profile.cpu_core_count_current

# The power profile, clock frequency, thermal limit, and TDP are all owned by
# the session we are running on top of when it manages GPU power.
if session_manages_gpu_power:
logger.info("Applied Performance Profile: " + profile.name + ". GPU settings are managed by the session")
profile_applied.emit(profile)
return

# Detect all GPU cards
var cards: Array[GpuCard] = []
if _power_station.gpu:
Expand Down Expand Up @@ -206,16 +230,6 @@ func apply_profile(profile: PerformanceProfile) -> void:
logger.debug("Applying TDP Boost: " + str(profile.tdp_boost_current))
card.boost = profile.tdp_boost_current

# Apply CPU settings from the given profile
if _power_station.cpu:
logger.debug("Applying CPU performance settings from profile")
if _power_station.cpu.boost_enabled != profile.cpu_boost_enabled:
_power_station.cpu.boost_enabled = profile.cpu_boost_enabled
if _power_station.cpu.smt_enabled != profile.cpu_smt_enabled:
_power_station.cpu.smt_enabled = profile.cpu_smt_enabled
if profile.cpu_core_count_current > 0 and _power_station.cpu.cores_enabled != profile.cpu_core_count_current:
_power_station.cpu.cores_enabled = profile.cpu_core_count_current

logger.info("Applied Performance Profile: " + profile.name)
profile_applied.emit(profile)

Expand Down
32 changes: 31 additions & 1 deletion core/ui/card_ui/gamepad/gamepad_settings.gd
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,21 @@ func _ready() -> void:

# Load the default profile for every attached gamepad
var profile_path = settings_manager.get_value("input", "gamepad_profile", "")
var saved_gamepad := settings_manager.get_value("input", "gamepad_profile_target", "") as String
var default_gamepad := input_manager.get_default_target_gamepad()
for composite_device in input_plumber.get_composite_devices():
# Set the current profile_gamepad type to the currently configured CompositeDevice target gamepad
var targets = composite_device.get_target_devices()
for target in targets:
var target_dbus_path: String = target.get("dbus_path")
if not target_dbus_path.contains("target/gamepad"):
continue
self.profile_gamepad = target.get("device_type")
if not saved_gamepad.is_empty():
self.profile_gamepad = saved_gamepad
elif not default_gamepad.is_empty():
self.profile_gamepad = default_gamepad
else:
self.profile_gamepad = target.get("device_type")
_set_gamepad_profile(composite_device, profile_path)
break

Expand Down Expand Up @@ -153,6 +160,14 @@ func _on_state_entered(_from: State) -> void:
if not self.library_item:
self.library_item = launch_manager.get_current_app_library_item()

# Steam Input configures controllers per-game itself while Steam is running,
# so a per-game profile of ours would only ever be ignored. Edit the global
# profile instead; the user can still pick a target gamepad, it just applies
# to every game.
if self.library_item and launch_manager.steam_input_enabled:
logger.debug("Steam Input is in use. Editing the global gamepad profile")
self.library_item = null

# Set the current profile_gamepad type to the current CompositeDevice target gamepad
var targets = self.gamepad.get_target_devices()
for target in targets:
Expand Down Expand Up @@ -624,13 +639,28 @@ func _set_gamepad_profile(device: CompositeDevice, profile_path: String = "") ->
if not library_item:
library_item = launch_manager.get_current_app_library_item()

# Per-game profiles are gated while Steam Input is in use, since
# Steam Input configures controllers per-game itself.
if launch_manager.steam_input_enabled:
library_item = null

# If no library item was set with the state, then use the default
if not library_item:
profile_path = input_manager.get_default_global_profile_path()
profile_path = settings_manager.get_value("input", "gamepad_profile", profile_path) as String
else:
profile_path = settings_manager.get_library_value(library_item, "gamepad_profile", "")

if self.profile_gamepad.is_empty():
if not library_item:
self.profile_gamepad = settings_manager.get_value("input", "gamepad_profile_target", "") as String
else:
self.profile_gamepad = settings_manager.get_library_value(library_item, "gamepad_profile_target", "") as String
if self.profile_gamepad.is_empty() and is_instance_valid(input_manager):
# Fall back to the default for the current mode, so devices added after
# startup get the same target gamepad as the ones present at startup.
self.profile_gamepad = input_manager.get_default_target_gamepad()

logger.debug("Setting " + device.name + " to profile: " + profile_path)
InputPlumber.load_target_modified_profile(device, profile_path, self.profile_gamepad)

Expand Down
26 changes: 20 additions & 6 deletions core/ui/card_ui_overlay_mode/card_ui_overlay_mode.gd
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,26 @@ func _init():
# Ensure LaunchManager doesn't override our custom overlay management l
launch_manager.should_manage_overlay = false

# Workaround old versions that don't pass launch args via update pack
# TODO: Parse the parent PID's CLI args and use those instead.
if "--skip-update-pack" in cmdargs and self.launch_args.size() == 0:
logger.warn("Launched via update pack without arguments! Falling back to default.")
self.launch_args = PackedStringArray(["steam", "-gamepadui", "-steamos3", "-steampal", "-steamdeck"])

# SteamOS Manager owns TDP and GPU clocks when it is in use. This has to stay
# in _init(), which runs before our children are instantiated -- moving it to
# _setup_overlay_mode() would run it from _ready(), after the quick bar has
# already loaded the performance manager and applied the saved profile's TDP.
if "--steamos-manager" in cmdargs:
logger.info("Launched with --steamos-manager. TDP and GPU clocks are managed by the session")
PerformanceManager.session_manages_gpu_power = true

# Steam Input configures controllers itself when it is in use, so defer to
# it instead of applying our own configuration on top.
if "--steam-input" in cmdargs:
logger.info("Launched with --steam-input, deferring controller configuration to Steam Input.")
launch_manager.steam_input_enabled = true

# Set up plugin manager for quick-bar tags
var plugin_loader := load("res://core/global/plugin_loader.tres") as PluginLoader
var filters : Array[Callable] = [plugin_loader.filter_by_tag.bind("quick-bar")]
Expand All @@ -86,12 +106,6 @@ func _init():

## Starts the --overlay-mode session.
func _ready() -> void:
# Workaround old versions that don't pass launch args via update pack
# TODO: Parse the parent PID's CLI args and use those instead.
if "--skip-update-pack" in cmdargs and launch_args.size() == 0:
logger.warn("Launched via update pack without arguments! Falling back to default.")
launch_args = PackedStringArray(["steam", "-gamepadui", "-steamos3", "-steampal", "-steamdeck"])

# Configure the locale
logger.debug("Setup Locale")
var locale := settings_manager.get_value("general", "locale", "en_US") as String
Expand Down
Loading
Loading