Skip to content

Commit 1b6249f

Browse files
committed
feat(Launch): add app lifecycle implementation and better handling of steam
1 parent 90e11f9 commit 1b6249f

File tree

6 files changed

+245
-39
lines changed

6 files changed

+245
-39
lines changed

core/global/launch_manager.gd

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@ signal all_apps_stopped()
3333
signal app_launched(app: RunningApp)
3434
signal app_stopped(app: RunningApp)
3535
signal app_switched(from: RunningApp, to: RunningApp)
36+
signal app_lifecycle_progressed(progress: float, type: AppLifecycleHook.TYPE)
37+
signal app_lifecycle_notified(text: String, type: AppLifecycleHook.TYPE)
3638
signal recent_apps_changed()
3739

3840
const settings_manager := preload("res://core/global/settings_manager.tres")
3941
const notification_manager := preload("res://core/global/notification_manager.tres")
42+
const library_manager := preload("res://core/global/library_manager.tres")
4043

4144
var gamescope := preload("res://core/systems/gamescope/gamescope.tres") as GamescopeInstance
4245
var input_plumber := load("res://core/systems/input/input_plumber.tres") as InputPlumberInstance
@@ -196,20 +199,34 @@ func _save_persist_data():
196199
## Launches the given application and switches to the in-game state. Returns a
197200
## [RunningApp] instance of the application.
198201
func launch(app: LibraryLaunchItem) -> RunningApp:
202+
# Create a running app from the launch item
199203
var running_app := _launch(app)
200204

201205
# Add the running app to our list and change to the IN_GAME state
202206
_add_running(running_app)
203207
state_machine.set_state([in_game_state])
204208
_update_recent_apps(app)
205209

210+
# Execute any pre-launch hooks and start the app
211+
await _execute_hooks(app, AppLifecycleHook.TYPE.PRE_LAUNCH)
212+
running_app.start()
213+
214+
# Call any hooks at different points in the app's lifecycle
215+
var on_app_state_changed := func(_from: RunningApp.STATE, to: RunningApp.STATE):
216+
if to == RunningApp.STATE.RUNNING:
217+
_execute_hooks(app, AppLifecycleHook.TYPE.LAUNCH)
218+
elif to == RunningApp.STATE.STOPPED:
219+
_execute_hooks(app, AppLifecycleHook.TYPE.EXIT)
220+
running_app.state_changed.connect(on_app_state_changed)
221+
206222
return running_app
207223

208224

209225
## Launches the given app in the background. Returns the [RunningApp] instance.
210226
func launch_in_background(app: LibraryLaunchItem) -> RunningApp:
211227
# Start the application
212228
var running_app := _launch(app)
229+
running_app.start()
213230

214231
# Listen for app state changes
215232
var on_app_state_changed := func(from: RunningApp.STATE, to: RunningApp.STATE):
@@ -224,6 +241,37 @@ func launch_in_background(app: LibraryLaunchItem) -> RunningApp:
224241
return running_app
225242

226243

244+
## Executes application lifecycle hooks for the given app
245+
func _execute_hooks(app: LibraryLaunchItem, type: AppLifecycleHook.TYPE) -> void:
246+
var library := library_manager.get_library_by_id(app._provider_id)
247+
if not library:
248+
logger.warn("Unable to find library for app:", app)
249+
return
250+
var hooks := library.get_app_lifecycle_hooks()
251+
252+
# Filter based on hook type
253+
var hooks_to_run: Array[AppLifecycleHook] = []
254+
for item in hooks:
255+
if item.get_type() != type:
256+
continue
257+
hooks_to_run.push_back(item)
258+
259+
# Emit signals if the hook has progress
260+
var on_hook_progress := func(progress: float):
261+
self.app_lifecycle_progressed.emit(progress, type)
262+
var on_hook_notify := func(text: String):
263+
self.app_lifecycle_notified.emit(text, type)
264+
265+
# Run each hook and emit signals on hook progress
266+
for hook in hooks_to_run:
267+
logger.info("Executing lifecycle hook:", hook)
268+
hook.progressed.connect(on_hook_progress)
269+
hook.notified.connect(on_hook_notify)
270+
await hook.execute(app)
271+
hook.notified.disconnect(on_hook_notify)
272+
hook.progressed.disconnect(on_hook_progress)
273+
274+
227275
## Launches the given app
228276
func _launch(app: LibraryLaunchItem) -> RunningApp:
229277
var cmd: String = app.command
@@ -283,9 +331,8 @@ func _launch(app: LibraryLaunchItem) -> RunningApp:
283331
command.append_array(args)
284332
logger.info("Launching game with command: {0} {1}".format([exec, str(command)]))
285333

286-
# Launch the application process
287-
var running_app := RunningApp.spawn(app, env, exec, command)
288-
logger.info("Launched with PID: {0}".format([running_app.pid]))
334+
# Create the running app instance, but do not start it yet.
335+
var running_app := RunningApp.create(app, env, exec, command)
289336

290337
return running_app
291338

@@ -489,9 +536,10 @@ func check_running() -> void:
489536
var root_id := _xwayland_game.root_window_id
490537
if root_id < 0:
491538
return
539+
var all_windows := _xwayland_game.get_all_windows(root_id)
492540

493541
# Update our view of running processes and what windows they have
494-
_update_pids(root_id)
542+
_update_pids(all_windows)
495543

496544
# Update the state of all running apps
497545
for app in _running:
@@ -502,11 +550,10 @@ func check_running() -> void:
502550

503551
# Updates our mapping of PIDs to Windows. This gives us a good view of what
504552
# processes are running, and what windows they have.
505-
func _update_pids(root_id: int):
553+
func _update_pids(all_windows: PackedInt64Array):
506554
if not _xwayland_game:
507555
return
508556
var pids := {}
509-
var all_windows := _xwayland_game.get_all_windows(root_id)
510557
for window in all_windows:
511558
var window_pids := _xwayland_game.get_pids_for_window(window)
512559
for window_pid in window_pids:
@@ -661,7 +708,7 @@ func _make_running_app_from_process(name: String, pid: int, window_id: int, app_
661708
# Creates a new RunningApp instance from a given LibraryLaunchItem, PID, and
662709
# xwayland instance.
663710
func _make_running_app(launch_item: LibraryLaunchItem, pid: int, display: String) -> RunningApp:
664-
var running_app: RunningApp = RunningApp.new(launch_item, pid, display)
711+
var running_app: RunningApp = RunningApp.new(launch_item, display)
665712
running_app.launch_item = launch_item
666713
running_app.pid = pid
667714
running_app.display = display

core/systems/launcher/app_lifecycle_hook.gd

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ class_name AppLifecycleHook
88
## the ability to execute actions when apps are about to start, have started,
99
## or have exited.
1010

11+
## Emit this signal if you want to indicate progression of the hook.
12+
signal progressed(percent: float)
13+
## Emit this signal whenever you want custom text to be displayed
14+
signal notified(text: String)
15+
1116
## The type of hook determines where in the application's lifecycle this hook
1217
## should be executed.
1318
enum TYPE {
@@ -26,6 +31,11 @@ func _init(hook_type: TYPE) -> void:
2631
_hook_type = hook_type
2732

2833

34+
## Name of the lifecycle hook
35+
func get_name() -> String:
36+
return ""
37+
38+
2939
## Executes whenever an app from this library reaches the stage in its lifecycle
3040
## designated by the hook type. E.g. a `PRE_LAUNCH` hook will have this method
3141
## called whenever an app is about to launch.
@@ -37,3 +47,19 @@ func execute(item: LibraryLaunchItem) -> void:
3747
## the hook should be executed.
3848
func get_type() -> TYPE:
3949
return _hook_type
50+
51+
52+
func _to_string() -> String:
53+
var kind: String
54+
match self.get_type():
55+
TYPE.PRE_LAUNCH:
56+
kind = "PreLaunch"
57+
TYPE.LAUNCH:
58+
kind = "Launch"
59+
TYPE.EXIT:
60+
kind = "Exit"
61+
var name := self.get_name()
62+
if name.is_empty():
63+
name = "Anonymous"
64+
65+
return "<AppLifecycleHook.{0}-{1}>".format([kind, name])

0 commit comments

Comments
 (0)