Skip to content

Commit db2e3aa

Browse files
committed
fix(Launch): better process detection and use modern app focusing
1 parent b809f9d commit db2e3aa

File tree

4 files changed

+178
-51
lines changed

4 files changed

+178
-51
lines changed

core/global/launch_manager.gd

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,28 @@ func _init() -> void:
128128
self.check_running.call_deferred()
129129
# If focusable apps has changed and the currently focused app no longer exists,
130130
# remove the manual focus
131-
var baselayer_app := _xwayland_primary.baselayer_app
132-
to.append(_xwayland_primary.focused_app)
133-
if baselayer_app > 0 and not baselayer_app in to:
134-
_xwayland_primary.remove_baselayer_app()
131+
const keep_app_ids := [GamescopeInstance.EXTRA_UNKNOWN_GAME_ID, GamescopeInstance.OVERLAY_GAME_ID]
132+
var baselayer_apps := _xwayland_primary.baselayer_apps
133+
var new_baselayer_apps := PackedInt64Array()
134+
for app_id in baselayer_apps:
135+
if app_id in keep_app_ids:
136+
new_baselayer_apps.push_back(app_id)
137+
continue
138+
if app_id in to:
139+
new_baselayer_apps.push_back(app_id)
140+
if new_baselayer_apps != baselayer_apps:
141+
_xwayland_primary.baselayer_apps = new_baselayer_apps
135142
_xwayland_primary.focusable_apps_updated.connect(on_focusable_apps_changed)
136143

144+
# Listen for when focusable windows change
145+
var on_focusable_windows_changed := func(_from: PackedInt64Array, to: PackedInt64Array):
146+
# If focusable windows has changed and the currently focused window no longer exists,
147+
# remove the manual focus
148+
var baselayer_window := _xwayland_primary.baselayer_window
149+
if baselayer_window > 0 and not baselayer_window in to:
150+
_xwayland_primary.remove_baselayer_window()
151+
_xwayland_primary.focusable_windows_updated.connect(on_focusable_windows_changed)
152+
137153
# Listen for signals from the secondary Gamescope XWayland
138154
if _xwayland_game:
139155
# Listen for window created/destroyed events
@@ -219,6 +235,29 @@ func launch(app: LibraryLaunchItem) -> RunningApp:
219235
_execute_hooks(app, AppLifecycleHook.TYPE.EXIT)
220236
running_app.state_changed.connect(on_app_state_changed)
221237

238+
# Focus any new windows that get created
239+
var switch_to_new_window := func(old_windows: PackedInt64Array, new_windows: PackedInt64Array):
240+
for window in new_windows:
241+
if window in old_windows:
242+
continue
243+
running_app.switch_window(window)
244+
break
245+
running_app.window_ids_changed.connect(switch_to_new_window)
246+
247+
# Remove/restore focus if any windows get removed
248+
var remove_focus := func(old_windows: PackedInt64Array, new_windows: PackedInt64Array):
249+
if not _xwayland_primary:
250+
return
251+
var focused_window := _xwayland_primary.baselayer_window
252+
if not focused_window in old_windows:
253+
return
254+
if new_windows.is_empty():
255+
_xwayland_primary.remove_baselayer_window()
256+
return
257+
var new_window := new_windows[0]
258+
running_app.switch_window(new_window)
259+
running_app.window_ids_changed.connect(remove_focus)
260+
222261
return running_app
223262

224263

@@ -513,6 +552,7 @@ func _on_app_state_changed(from: RunningApp.STATE, to: RunningApp.STATE, app: Ru
513552
logger.debug("Currently running apps:", _running)
514553
if state_machine.has_state(in_game_state) and _running.size() == 0:
515554
logger.info("No more apps are running. Removing in-game state.")
555+
_current_app = null
516556
_xwayland_primary.remove_baselayer_window()
517557
state_machine.remove_state(in_game_state)
518558
state_machine.remove_state(in_game_menu_state)
@@ -522,6 +562,8 @@ func _on_app_state_changed(from: RunningApp.STATE, to: RunningApp.STATE, app: Ru
522562
# Removes the given PID from our list of running apps
523563
func _remove_running(app: RunningApp):
524564
logger.info("Removing app", app, "from running apps.")
565+
if app == _current_app:
566+
_current_app = null
525567
_running.erase(app)
526568
_apps_by_name.erase(app.launch_item.name)
527569
_apps_by_pid.erase(app.pid)
@@ -532,20 +574,75 @@ func _remove_running(app: RunningApp):
532574
func check_running() -> void:
533575
# Find the root window
534576
if not _xwayland_game:
577+
logger.warn("No XWayland instance exists to check for running apps")
535578
return
536579
var root_id := _xwayland_game.root_window_id
537580
if root_id < 0:
538581
return
582+
583+
# Get all windows and their geometry
539584
var all_windows := _xwayland_game.get_all_windows(root_id)
585+
var all_window_sizes := _xwayland_game.get_window_sizes(all_windows)
586+
logger.trace("Found windows:", all_windows)
587+
logger.trace("Found window sizes:", all_window_sizes)
588+
589+
# Only consider valid windows of a certain size
590+
var all_valid_windows := PackedInt64Array()
591+
var i := 0
592+
for window in all_windows:
593+
var size := all_window_sizes[i]
594+
if size.x > 20 and size.y > 20:
595+
all_valid_windows.push_back(window)
596+
i += 1
597+
logger.trace("Found valid windows:", all_valid_windows)
598+
599+
# Get a list of all running processes
600+
var all_pids := Reaper.get_pids()
601+
602+
# All OGUI processes should have an OGUI_ID environment variable set. Look
603+
# at every running process and sort them by OGUI_ID.
604+
var ogui_id_to_pids: Dictionary[String, PackedInt64Array] = {}
605+
for pid in all_pids:
606+
var env := Reaper.get_pid_environment(pid)
607+
if not env.is_empty():
608+
logger.trace("Found environment for pid", pid, ":", env)
609+
if not "OGUI_ID" in env:
610+
continue
611+
var id := env["OGUI_ID"] as String
612+
if not id in ogui_id_to_pids:
613+
ogui_id_to_pids[id] = PackedInt64Array()
614+
ogui_id_to_pids[id].push_back(pid)
615+
if !ogui_id_to_pids.is_empty():
616+
logger.debug("Running processes:", ogui_id_to_pids)
540617

541618
# Update our view of running processes and what windows they have
542-
_update_pids(all_windows)
619+
_update_pids(all_valid_windows)
543620

544621
# Update the state of all running apps
545622
for app in _running:
546-
app.update()
623+
var app_pids := PackedInt64Array()
624+
if app.ogui_id in ogui_id_to_pids:
625+
app_pids = ogui_id_to_pids[app.ogui_id]
626+
app.update(all_valid_windows, app_pids)
627+
for app in _running_background:
628+
var app_pids := PackedInt64Array()
629+
if app.ogui_id in ogui_id_to_pids:
630+
app_pids = ogui_id_to_pids[app.ogui_id]
631+
app.update(all_valid_windows, app_pids)
632+
633+
# Look for orphan windows
634+
var windows_with_app := PackedInt64Array()
635+
var orphan_windows := PackedInt64Array()
636+
for app in _running:
637+
windows_with_app.append_array(app.window_ids.duplicate())
547638
for app in _running_background:
548-
app.update()
639+
windows_with_app.append_array(app.window_ids.duplicate())
640+
for window in all_valid_windows:
641+
if window in windows_with_app:
642+
continue
643+
orphan_windows.push_back(window)
644+
if not orphan_windows.is_empty():
645+
logger.warn("Found orphan windows:", orphan_windows)
549646

550647

551648
# Updates our mapping of PIDs to Windows. This gives us a good view of what
@@ -717,8 +814,9 @@ func _make_running_app(launch_item: LibraryLaunchItem, pid: int, display: String
717814

718815
# Returns the parent app if the focused app is a child of a currently running app.
719816
func _get_app_from_running_pid_groups(pid: int) -> RunningApp:
817+
var pids_with_ogui_id := PackedInt64Array()
720818
for app in _running:
721-
if pid in app.get_child_pids():
819+
if pid in app.get_child_pids(pids_with_ogui_id):
722820
return app
723821
return null
724822

0 commit comments

Comments
 (0)