Skip to content

Commit

Permalink
fix(Overlay Mode): Overlay Mode Fixes
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
pastaq committed Sep 30, 2024
1 parent 2c694ea commit 539abf9
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 47 deletions.
8 changes: 7 additions & 1 deletion core/systems/input/input_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()))
Expand Down
11 changes: 9 additions & 2 deletions core/systems/input/overlay_mode_input_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()))
Expand All @@ -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":
Expand Down
6 changes: 5 additions & 1 deletion core/systems/input/overlay_mode_input_manager.tscn
Original file line number Diff line number Diff line change
@@ -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")
18 changes: 15 additions & 3 deletions core/systems/launcher/reaper.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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",
Expand All @@ -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
Expand Down
1 change: 0 additions & 1 deletion core/systems/state/state.gd
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,3 @@ func _to_string() -> String:
if not name.is_empty():
return "<State:{name}>".format({"name": name})
return "<State:{rid}>".format({"rid": get_rid()})

99 changes: 60 additions & 39 deletions core/ui/card_ui_overlay_mode/card_ui_overlay_mode.gd
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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

Expand All @@ -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")
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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)

Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit 539abf9

Please sign in to comment.