From 0cac083e9898317151277f57dd6571d7a69873c2 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Sun, 29 Sep 2024 18:11:12 -0700 Subject: [PATCH] fix(Overlay Mode): Overlay Mode Fixes - Add input focus change for steam/opengameapdui - Add InputPlumber node to OverlayModeInputManager so it can process dbus signals - Reconnect to all composite devices when InputPlumber starts - Preload all state modules, fixes visibility of overlay... Somehow. --- core/systems/input/input_manager.gd | 8 +- .../input/overlay_mode_input_manager.gd | 11 ++- .../input/overlay_mode_input_manager.tscn | 6 +- core/systems/launcher/reaper.gd | 18 +++- core/systems/state/state.gd | 1 - .../card_ui_overlay_mode.gd | 99 +++++++++++-------- 6 files changed, 96 insertions(+), 47 deletions(-) diff --git a/core/systems/input/input_manager.gd b/core/systems/input/input_manager.gd index 6628bde7..a77d8762 100644 --- a/core/systems/input/input_manager.gd +++ b/core/systems/input/input_manager.gd @@ -44,7 +44,11 @@ var logger := Log.get_logger("InputManager", Log.LEVEL.INFO) func _ready() -> void: add_to_group("InputManager") input_plumber.composite_device_added.connect(_watch_dbus_device) + input_plumber.started.connect(_init_inputplumber) + _init_inputplumber() + +func _init_inputplumber() -> void: for device in input_plumber.get_composite_devices(): _watch_dbus_device(device) @@ -175,7 +179,7 @@ func _guide_input(event: InputEvent) -> void: var dbus_path := event.get_meta("dbus_path", "") as String # Only act on release events if event.is_pressed(): - logger.warn("Guide pressed. Waiting for additional events.") + logger.debug("Guide pressed. Waiting for additional events.") # Set the gamepad profile to the global default so we can capture button events. # This ensures that we use the global profile and not the game's input profile for # processing guide button combos and navigating the menu. @@ -272,6 +276,8 @@ func _audio_input(event: InputEvent) -> void: func _watch_dbus_device(device: CompositeDevice) -> void: for target in device.dbus_devices: + if target.input_event.is_connected(_on_dbus_input_event.bind(device.dbus_path)): + continue logger.debug("Adding watch for " + device.name + " " + target.dbus_path) logger.debug(str(target.get_instance_id())) logger.debug(str(target.get_rid())) diff --git a/core/systems/input/overlay_mode_input_manager.gd b/core/systems/input/overlay_mode_input_manager.gd index 8b3e2457..4bdb6159 100644 --- a/core/systems/input/overlay_mode_input_manager.gd +++ b/core/systems/input/overlay_mode_input_manager.gd @@ -39,7 +39,11 @@ var logger := Log.get_logger("InputManager(Overlay Mode)", Log.LEVEL.INFO) func _ready() -> void: add_to_group("InputManager") input_plumber.composite_device_added.connect(_watch_dbus_device) + input_plumber.started.connect(_init_inputplumber) + _init_inputplumber() + +func _init_inputplumber() -> void: for device in input_plumber.get_composite_devices(): _watch_dbus_device(device) @@ -74,7 +78,7 @@ func _input(event: InputEvent) -> void: var dbus_path := event.get_meta("dbus_path", "") as String # Consume double inputs for controllers with DPads that have TRIGGER_HAPPY events - var possible_doubles := PackedStringArray(["ui_left", "ui_right", "ui_up", "ui_down"]) + const possible_doubles := ["ui_left", "ui_right", "ui_up", "ui_down"] for action in possible_doubles: if not event.is_action(action): continue @@ -349,6 +353,8 @@ func _audio_input(event: InputEvent) -> void: func _watch_dbus_device(device: CompositeDevice) -> void: for target in device.dbus_devices: + if target.input_event.is_connected(_on_dbus_input_event.bind(device.dbus_path)): + continue logger.debug("Adding watch for " + device.name + " " + target.dbus_path) logger.debug(str(target.get_instance_id())) logger.debug(str(target.get_rid())) @@ -357,7 +363,8 @@ func _watch_dbus_device(device: CompositeDevice) -> void: func _on_dbus_input_event(event: String, value: float, dbus_path: String) -> void: var pressed := value == 1.0 - logger.debug("Handling dbus input event: " + event + " pressed: " + str(pressed)) + logger.debug("Handling dbus input event from" + dbus_path + ": " + event + " pressed: " + str(pressed)) + var action := event match event: "ui_accept": diff --git a/core/systems/input/overlay_mode_input_manager.tscn b/core/systems/input/overlay_mode_input_manager.tscn index 61b5aed1..e3973446 100644 --- a/core/systems/input/overlay_mode_input_manager.tscn +++ b/core/systems/input/overlay_mode_input_manager.tscn @@ -1,6 +1,10 @@ -[gd_scene load_steps=2 format=3 uid="uid://bxnb8t7i08vma"] +[gd_scene load_steps=3 format=3 uid="uid://bxnb8t7i08vma"] [ext_resource type="Script" path="res://core/systems/input/overlay_mode_input_manager.gd" id="1_fvwoc"] +[ext_resource type="Script" path="res://core/systems/input/input_plumber.gd" id="2_vf8uv"] [node name="InputManager" type="Node"] script = ExtResource("1_fvwoc") + +[node name="InputPlumber" type="Node" parent="."] +script = ExtResource("2_vf8uv") diff --git a/core/systems/launcher/reaper.gd b/core/systems/launcher/reaper.gd index 940ec374..db7871ec 100644 --- a/core/systems/launcher/reaper.gd +++ b/core/systems/launcher/reaper.gd @@ -12,10 +12,13 @@ enum SIG { ## Spawn a process with PR_SET_CHILD_SUBREAPER set so child processes will ## reparent themselves to OpenGamepadUI. Returns the PID of the spawned process. static func create_process(cmd: String, args: PackedStringArray, app_id: int = -1) -> int: + var logger := Log.get_logger("Reaper") + logger.debug("Got command to execute:", cmd, args) var reaper_cmd := get_reaper_command() + logger.debug("Got reaper command:", reaper_cmd) if reaper_cmd.is_empty(): - var logger := Log.get_logger("Reaper") logger.warn("'reaper' binary not found, launching without reaper") + logger.info("Executing OS command:", cmd, args) return OS.create_process(cmd, args) # Build the arguments for reaper. @@ -26,12 +29,12 @@ static func create_process(cmd: String, args: PackedStringArray, app_id: int = - reaper_args.append("--") reaper_args.append(cmd) reaper_args.append_array(args) - + logger.info("Executing REAPER command:", reaper_cmd, reaper_args) return OS.create_process(reaper_cmd, reaper_args) - ## Discovers the 'reaper' binary to execute commands with PR_SET_CHILD_SUBREAPER. static func get_reaper_command() -> String: + var logger := Log.get_logger("Reaper") var home := OS.get_environment("HOME") var search_paths := [ "./extensions/target/release", @@ -41,7 +44,16 @@ static func get_reaper_command() -> String: ] for path in search_paths: + logger.debug("Checking for reaper in path:", path) + # Check if the path exists, its the responsible thing to do. + if not DirAccess.dir_exists_absolute(path): + logger.debug("Path does not exists!") + continue + logger.debug("Path does exists!") var directory := DirAccess.open(path) + if not directory: + logger.warn("Failed to open path:", path) + continue if directory.file_exists("reaper"): var reaper_path := "{0}/reaper".format([path]) return reaper_path diff --git a/core/systems/state/state.gd b/core/systems/state/state.gd index e76304cc..bb3f323a 100644 --- a/core/systems/state/state.gd +++ b/core/systems/state/state.gd @@ -36,4 +36,3 @@ func _to_string() -> String: if not name.is_empty(): return "".format({"name": name}) return "".format({"rid": get_rid()}) - 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 187b1e44..4b6b3076 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 @@ -1,35 +1,44 @@ extends Control +# Managers var platform := load("res://core/global/platform.tres") as Platform var gamescope := load("res://core/systems/gamescope/gamescope.tres") as GamescopeInstance var launch_manager := load("res://core/global/launch_manager.tres") as LaunchManager var settings_manager := load("res://core/global/settings_manager.tres") as SettingsManager var input_plumber := load("res://core/systems/input/input_plumber.tres") as InputPlumberInstance -var state_machine := load("res://assets/state/state_machines/global_state_machine.tres") as StateMachine - -var menu_state_machine := load("res://assets/state/state_machines/menu_state_machine.tres") as StateMachine -var popup_state_machine := load("res://assets/state/state_machines/popup_state_machine.tres") as StateMachine -var menu_state := load("res://assets/state/states/menu.tres") as State -var popup_state := load("res://assets/state/states/popup.tres") as State -var quick_bar_state := load("res://assets/state/states/quick_bar_menu.tres") as State -var settings_state := load("res://assets/state/states/settings.tres") as State -var gamepad_state := load("res://assets/state/states/gamepad_settings.tres") as State -var base_state := load("res://assets/state/states/in_game.tres") as State +# State +var state_machine := ( + preload("res://assets/state/state_machines/global_state_machine.tres") as StateMachine +) +var menu_state_machine := preload("res://assets/state/state_machines/menu_state_machine.tres") as StateMachine +var popup_state_machine := preload("res://assets/state/state_machines/popup_state_machine.tres") as StateMachine +var menu_state := preload("res://assets/state/states/menu.tres") as State +var popup_state := preload("res://assets/state/states/popup.tres") as State +var quick_bar_state := preload("res://assets/state/states/quick_bar_menu.tres") as State +var settings_state := preload("res://assets/state/states/settings.tres") as State +var gamepad_state := preload("res://assets/state/states/gamepad_settings.tres") as State +var base_state := preload("res://assets/state/states/in_game.tres") as State + +# Xwayland var managed_states: Array[State] = [quick_bar_state, settings_state, gamepad_state] -var PID: int = OS.get_process_id() -var launch_args := OS.get_cmdline_user_args() -var cmdargs := OS.get_cmdline_args() var xwayland_primary := gamescope.get_xwayland(gamescope.XWAYLAND_TYPE_PRIMARY) var xwayland_ogui := gamescope.get_xwayland(gamescope.XWAYLAND_TYPE_OGUI) var overlay_window_id := 0 + +# Process +var PID: int = OS.get_process_id() +var launch_args := OS.get_cmdline_user_args() +var cmdargs := OS.get_cmdline_args() var underlay_log: FileAccess var underlay_process: int var underlay_window_id: int +# UI References @onready var quick_bar_menu := $%QuickBarMenu @onready var settings_menu := $%SettingsMenu +# Logger var logger := Log.get_logger("Main", Log.LEVEL.INFO) ## Sets up overlay mode. @@ -43,9 +52,6 @@ func _init(): logger.error("Unable to detect Window ID. Overlay is not going to work!") logger.info("Found primary window id: {0}".format([overlay_window_id])) - # Back button wont close windows without this. OverlayInputManager prevents poping the last state. - state_machine.push_state(base_state) - # Ensure LaunchManager doesn't override our custom overlay management l launch_manager.should_manage_overlay = false @@ -64,7 +70,7 @@ func _ready() -> void: # 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 = ["steam", "-gamepadui", "-steamos3", "-steampal", "-steamdeck"] + launch_args = PackedStringArray(["steam", "-gamepadui", "-steamos3", "-steampal", "-steamdeck"]) # Configure the locale logger.debug("Setup Locale") @@ -102,12 +108,15 @@ func _ready() -> void: ## Finds needed PID's and global vars, Starts the user defined program as an ## underlay process. -func _setup_overlay_mode(args: Array) -> void: +func _setup_overlay_mode(args: PackedStringArray) -> void: # Always push the base state if we end up with an empty stack. var on_states_emptied := func(): state_machine.push_state.call_deferred(base_state) state_machine.emptied.connect(on_states_emptied) + # Back button wont close windows without this. OverlayInputManager prevents poping the last state. + state_machine.push_state(base_state) + # Whenever the menu state is refreshed, refresh the menu state machine to # re-grab focus. var on_menu_refreshed := func(): @@ -139,13 +148,15 @@ func _setup_overlay_mode(args: Array) -> void: base_state.state_exited.connect(_on_base_state_exited) # Don't crash if we're not launching another program. - if args == []: + if args.is_empty(): logger.warn("overlay mode started with no launcher arguments.") return if "steam" in args: + logger.info("Starting Steam with args:", args) _start_steam_process(args) else: + logger.info("Starting underlay process with args:", args) var log_path := OS.get_environment("HOME") + "/.underlay-stdout.log" _start_underlay_process(args, log_path) @@ -157,16 +168,16 @@ func _setup_overlay_mode(args: Array) -> void: # Setup inputplumber to receive guide presses. input_plumber.set_intercept_mode(InputPlumberInstance.INTERCEPT_MODE_PASS) - input_plumber.set_intercept_activation(["Gamepad:Button:Guide", "Gamepad:Button:East"], "Gamepad:Button:QuickAccess2") + input_plumber.set_intercept_activation(PackedStringArray(["Gamepad:Button:Guide", "Gamepad:Button:East"]), "Gamepad:Button:QuickAccess2") # TODO: Do we need this? # Sets the intercept mode and intercept activation keys to what overlay_mode expects. - #var on_device_changed := func(device: CompositeDevice): - # var intercept_mode := input_plumber.intercept_mode - # logger.debug("Setting intercept mode to: " + str(intercept_mode)) - # device.intercept_mode = intercept_mode - # device.set_intercept_activation(["Gamepad:Button:Guide", "Gamepad:Button:East"], "Gamepad:Button:QuickAccess2") - #input_plumber.composite_device_changed.connect(on_device_changed) + var on_device_changed := func(device: CompositeDevice): + var intercept_mode := input_plumber.intercept_mode + logger.debug("Setting intercept mode to: " + str(intercept_mode)) + device.intercept_mode = intercept_mode + device.set_intercept_activation(PackedStringArray(["Gamepad:Button:Guide", "Gamepad:Button:East"]), "Gamepad:Button:QuickAccess2") + input_plumber.composite_device_added.connect(on_device_changed) # Removes specified child elements from the given Node. @@ -194,7 +205,7 @@ func _remove_children(remove_list: PackedStringArray, parent:Node) -> void: ## Starts Steam as an underlay process -func _start_steam_process(args: Array) -> void: +func _start_steam_process(args: PackedStringArray) -> void: logger.debug("Starting steam: " + str(args)) var underlay_log_path := OS.get_environment("HOME") + "/.steam-stdout.log" _start_underlay_process(args, underlay_log_path) @@ -211,21 +222,23 @@ func _start_steam_process(args: Array) -> void: ## Called to start the specified underlay process and redirects logging to a ## seperate log file. -func _start_underlay_process(args: Array, log_path: String) -> void: +func _start_underlay_process(args: PackedStringArray, _log_path: String) -> void: logger.debug("Starting underlay process: " + str(args)) - # Set up loggining in the new thread. - args.append("2>&1") - args.append(log_path) + ## TODO: Fix this so it works + ## Set up logging in the new thread. + #args.append("2>&1") + #args.append(log_path) # Setup logging - underlay_log = FileAccess.open(log_path, FileAccess.WRITE) - var error := FileAccess.get_open_error() - if error != OK: - logger.warn("Got error opening log file.") - else: - logger.info("Started logging underlay process at " + log_path) - var command: String = "bash" - underlay_process = Reaper.create_process(command, ["-c", " ".join(args)]) + #underlay_log = FileAccess.open(log_path, FileAccess.WRITE) + #var error := FileAccess.get_open_error() + #if error != OK: + #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 = Reaper.create_process(command, args) ## Called to identify the xwayland window ID of the underlay process. @@ -255,10 +268,14 @@ func _find_underlay_window_id() -> void: ## Called when the base state is entered. func _on_base_state_entered(_from: State) -> void: + logger.debug("Setting Underlay proccess window as overlay") + # Manage input focus input_plumber.set_intercept_mode(InputPlumberInstance.INTERCEPT_MODE_PASS) if xwayland_ogui.set_input_focus(overlay_window_id, 0) != OK: logger.error("Unable to set STEAM_INPUT_FOCUS atom!") + if xwayland_ogui.set_input_focus(underlay_window_id, 1) != OK: + logger.error("Unable to set STEAM_INPUT_FOCUS atom!") # Manage overlay xwayland_ogui.set_overlay(overlay_window_id, 0) @@ -267,10 +284,14 @@ func _on_base_state_entered(_from: State) -> void: ## Called when a the base state is exited. func _on_base_state_exited(_to: State) -> void: + logger.debug("Setting OpenGamepadUI window as overlay") + # Manage input focus input_plumber.set_intercept_mode(InputPlumberInstance.INTERCEPT_MODE_ALL) if xwayland_ogui.set_input_focus(overlay_window_id, 1) != OK: logger.error("Unable to set STEAM_INPUT_FOCUS atom!") + if xwayland_ogui.set_input_focus(underlay_window_id, 0) != OK: + logger.error("Unable to set STEAM_INPUT_FOCUS atom!") # Manage overlay xwayland_ogui.set_overlay(overlay_window_id, 1)