diff --git a/core/global/launch_manager.gd b/core/global/launch_manager.gd index dbd743b36..e2b943be2 100644 --- a/core/global/launch_manager.gd +++ b/core/global/launch_manager.gd @@ -33,10 +33,13 @@ signal all_apps_stopped() signal app_launched(app: RunningApp) signal app_stopped(app: RunningApp) signal app_switched(from: RunningApp, to: RunningApp) +signal app_lifecycle_progressed(progress: float, type: AppLifecycleHook.TYPE) +signal app_lifecycle_notified(text: String, type: AppLifecycleHook.TYPE) signal recent_apps_changed() const settings_manager := preload("res://core/global/settings_manager.tres") const notification_manager := preload("res://core/global/notification_manager.tres") +const library_manager := preload("res://core/global/library_manager.tres") var gamescope := preload("res://core/systems/gamescope/gamescope.tres") as GamescopeInstance var input_plumber := load("res://core/systems/input/input_plumber.tres") as InputPlumberInstance @@ -125,12 +128,28 @@ func _init() -> void: self.check_running.call_deferred() # If focusable apps has changed and the currently focused app no longer exists, # remove the manual focus - var baselayer_app := _xwayland_primary.baselayer_app - to.append(_xwayland_primary.focused_app) - if baselayer_app > 0 and not baselayer_app in to: - _xwayland_primary.remove_baselayer_app() + const keep_app_ids := [GamescopeInstance.EXTRA_UNKNOWN_GAME_ID, GamescopeInstance.OVERLAY_GAME_ID] + var baselayer_apps := _xwayland_primary.baselayer_apps + var new_baselayer_apps := PackedInt64Array() + for app_id in baselayer_apps: + if app_id in keep_app_ids: + new_baselayer_apps.push_back(app_id) + continue + if app_id in to: + new_baselayer_apps.push_back(app_id) + if new_baselayer_apps != baselayer_apps: + _xwayland_primary.baselayer_apps = new_baselayer_apps _xwayland_primary.focusable_apps_updated.connect(on_focusable_apps_changed) + # Listen for when focusable windows change + var on_focusable_windows_changed := func(_from: PackedInt64Array, to: PackedInt64Array): + # If focusable windows has changed and the currently focused window no longer exists, + # remove the manual focus + var baselayer_window := _xwayland_primary.baselayer_window + if baselayer_window > 0 and not baselayer_window in to: + _xwayland_primary.remove_baselayer_window() + _xwayland_primary.focusable_windows_updated.connect(on_focusable_windows_changed) + # Listen for signals from the secondary Gamescope XWayland if _xwayland_game: # Listen for window created/destroyed events @@ -196,6 +215,7 @@ func _save_persist_data(): ## Launches the given application and switches to the in-game state. Returns a ## [RunningApp] instance of the application. func launch(app: LibraryLaunchItem) -> RunningApp: + # Create a running app from the launch item var running_app := _launch(app) # Add the running app to our list and change to the IN_GAME state @@ -203,6 +223,41 @@ func launch(app: LibraryLaunchItem) -> RunningApp: state_machine.set_state([in_game_state]) _update_recent_apps(app) + # Execute any pre-launch hooks and start the app + await _execute_hooks(app, AppLifecycleHook.TYPE.PRE_LAUNCH) + running_app.start() + + # Call any hooks at different points in the app's lifecycle + var on_app_state_changed := func(_from: RunningApp.STATE, to: RunningApp.STATE): + if to == RunningApp.STATE.RUNNING: + _execute_hooks(app, AppLifecycleHook.TYPE.LAUNCH) + elif to == RunningApp.STATE.STOPPED: + _execute_hooks(app, AppLifecycleHook.TYPE.EXIT) + running_app.state_changed.connect(on_app_state_changed) + + # Focus any new windows that get created + var switch_to_new_window := func(old_windows: PackedInt64Array, new_windows: PackedInt64Array): + for window in new_windows: + if window in old_windows: + continue + running_app.switch_window(window) + break + running_app.window_ids_changed.connect(switch_to_new_window) + + # Remove/restore focus if any windows get removed + var remove_focus := func(old_windows: PackedInt64Array, new_windows: PackedInt64Array): + if not _xwayland_primary: + return + var focused_window := _xwayland_primary.baselayer_window + if not focused_window in old_windows: + return + if new_windows.is_empty(): + _xwayland_primary.remove_baselayer_window() + return + var new_window := new_windows[0] + running_app.switch_window(new_window) + running_app.window_ids_changed.connect(remove_focus) + return running_app @@ -210,6 +265,7 @@ func launch(app: LibraryLaunchItem) -> RunningApp: func launch_in_background(app: LibraryLaunchItem) -> RunningApp: # Start the application var running_app := _launch(app) + running_app.start() # Listen for app state changes var on_app_state_changed := func(from: RunningApp.STATE, to: RunningApp.STATE): @@ -224,6 +280,37 @@ func launch_in_background(app: LibraryLaunchItem) -> RunningApp: return running_app +## Executes application lifecycle hooks for the given app +func _execute_hooks(app: LibraryLaunchItem, type: AppLifecycleHook.TYPE) -> void: + var library := library_manager.get_library_by_id(app._provider_id) + if not library: + logger.warn("Unable to find library for app:", app) + return + var hooks := library.get_app_lifecycle_hooks() + + # Filter based on hook type + var hooks_to_run: Array[AppLifecycleHook] = [] + for item in hooks: + if item.get_type() != type: + continue + hooks_to_run.push_back(item) + + # Emit signals if the hook has progress + var on_hook_progress := func(progress: float): + self.app_lifecycle_progressed.emit(progress, type) + var on_hook_notify := func(text: String): + self.app_lifecycle_notified.emit(text, type) + + # Run each hook and emit signals on hook progress + for hook in hooks_to_run: + logger.info("Executing lifecycle hook:", hook) + hook.progressed.connect(on_hook_progress) + hook.notified.connect(on_hook_notify) + await hook.execute(app) + hook.notified.disconnect(on_hook_notify) + hook.progressed.disconnect(on_hook_progress) + + ## Launches the given app func _launch(app: LibraryLaunchItem) -> RunningApp: var cmd: String = app.command @@ -283,9 +370,8 @@ func _launch(app: LibraryLaunchItem) -> RunningApp: command.append_array(args) logger.info("Launching game with command: {0} {1}".format([exec, str(command)])) - # Launch the application process - var running_app := RunningApp.spawn(app, env, exec, command) - logger.info("Launched with PID: {0}".format([running_app.pid])) + # Create the running app instance, but do not start it yet. + var running_app := RunningApp.create(app, env, exec, command) return running_app @@ -466,6 +552,7 @@ func _on_app_state_changed(from: RunningApp.STATE, to: RunningApp.STATE, app: Ru logger.debug("Currently running apps:", _running) if state_machine.has_state(in_game_state) and _running.size() == 0: logger.info("No more apps are running. Removing in-game state.") + _current_app = null _xwayland_primary.remove_baselayer_window() state_machine.remove_state(in_game_state) state_machine.remove_state(in_game_menu_state) @@ -475,6 +562,8 @@ func _on_app_state_changed(from: RunningApp.STATE, to: RunningApp.STATE, app: Ru # Removes the given PID from our list of running apps func _remove_running(app: RunningApp): logger.info("Removing app", app, "from running apps.") + if app == _current_app: + _current_app = null _running.erase(app) _apps_by_name.erase(app.launch_item.name) _apps_by_pid.erase(app.pid) @@ -485,28 +574,83 @@ func _remove_running(app: RunningApp): func check_running() -> void: # Find the root window if not _xwayland_game: + logger.warn("No XWayland instance exists to check for running apps") return var root_id := _xwayland_game.root_window_id if root_id < 0: return + # Get all windows and their geometry + var all_windows := _xwayland_game.get_all_windows(root_id) + var all_window_sizes := _xwayland_game.get_window_sizes(all_windows) + logger.trace("Found windows:", all_windows) + logger.trace("Found window sizes:", all_window_sizes) + + # Only consider valid windows of a certain size + var all_valid_windows := PackedInt64Array() + var i := 0 + for window in all_windows: + var size := all_window_sizes[i] + if size.x > 20 and size.y > 20: + all_valid_windows.push_back(window) + i += 1 + logger.trace("Found valid windows:", all_valid_windows) + + # Get a list of all running processes + var all_pids := Reaper.get_pids() + + # All OGUI processes should have an OGUI_ID environment variable set. Look + # at every running process and sort them by OGUI_ID. + var ogui_id_to_pids: Dictionary[String, PackedInt64Array] = {} + for pid in all_pids: + var env := Reaper.get_pid_environment(pid) + if not env.is_empty(): + logger.trace("Found environment for pid", pid, ":", env) + if not "OGUI_ID" in env: + continue + var id := env["OGUI_ID"] as String + if not id in ogui_id_to_pids: + ogui_id_to_pids[id] = PackedInt64Array() + ogui_id_to_pids[id].push_back(pid) + if !ogui_id_to_pids.is_empty(): + logger.debug("Running processes:", ogui_id_to_pids) + # Update our view of running processes and what windows they have - _update_pids(root_id) + _update_pids(all_valid_windows) # Update the state of all running apps for app in _running: - app.update() + var app_pids := PackedInt64Array() + if app.ogui_id in ogui_id_to_pids: + app_pids = ogui_id_to_pids[app.ogui_id] + app.update(all_valid_windows, app_pids) for app in _running_background: - app.update() + var app_pids := PackedInt64Array() + if app.ogui_id in ogui_id_to_pids: + app_pids = ogui_id_to_pids[app.ogui_id] + app.update(all_valid_windows, app_pids) + + # Look for orphan windows + var windows_with_app := PackedInt64Array() + var orphan_windows := PackedInt64Array() + for app in _running: + windows_with_app.append_array(app.window_ids.duplicate()) + for app in _running_background: + windows_with_app.append_array(app.window_ids.duplicate()) + for window in all_valid_windows: + if window in windows_with_app: + continue + orphan_windows.push_back(window) + if not orphan_windows.is_empty(): + logger.warn("Found orphan windows:", orphan_windows) # Updates our mapping of PIDs to Windows. This gives us a good view of what # processes are running, and what windows they have. -func _update_pids(root_id: int): +func _update_pids(all_windows: PackedInt64Array): if not _xwayland_game: return var pids := {} - var all_windows := _xwayland_game.get_all_windows(root_id) for window in all_windows: var window_pids := _xwayland_game.get_pids_for_window(window) for window_pid in window_pids: @@ -661,7 +805,7 @@ func _make_running_app_from_process(name: String, pid: int, window_id: int, app_ # Creates a new RunningApp instance from a given LibraryLaunchItem, PID, and # xwayland instance. func _make_running_app(launch_item: LibraryLaunchItem, pid: int, display: String) -> RunningApp: - var running_app: RunningApp = RunningApp.new(launch_item, pid, display) + var running_app: RunningApp = RunningApp.new(launch_item, display) running_app.launch_item = launch_item running_app.pid = pid running_app.display = display @@ -670,8 +814,9 @@ func _make_running_app(launch_item: LibraryLaunchItem, pid: int, display: String # Returns the parent app if the focused app is a child of a currently running app. func _get_app_from_running_pid_groups(pid: int) -> RunningApp: + var pids_with_ogui_id := PackedInt64Array() for app in _running: - if pid in app.get_child_pids(): + if pid in app.get_child_pids(pids_with_ogui_id): return app return null diff --git a/core/global/plugin_loader.gd b/core/global/plugin_loader.gd index 2da34a3fa..6f6185b11 100644 --- a/core/global/plugin_loader.gd +++ b/core/global/plugin_loader.gd @@ -18,7 +18,7 @@ class_name PluginLoader ## pack. const PLUGIN_STORE_URL = "https://raw.githubusercontent.com/ShadowBlip/OpenGamepadUI-plugins/main/plugins.json" -const PLUGIN_API_VERSION = "1.1.0" +const PLUGIN_API_VERSION = "1.2.0" const PLUGINS_DIR = "user://plugins" const LOADED_PLUGINS_DIR = "res://plugins" const REQUIRED_META = ["plugin.name", "plugin.version", "plugin.min-api-version", "entrypoint"] diff --git a/core/systems/launcher/app_lifecycle_hook.gd b/core/systems/launcher/app_lifecycle_hook.gd index 3ea085a49..70d227036 100644 --- a/core/systems/launcher/app_lifecycle_hook.gd +++ b/core/systems/launcher/app_lifecycle_hook.gd @@ -8,6 +8,11 @@ class_name AppLifecycleHook ## the ability to execute actions when apps are about to start, have started, ## or have exited. +## Emit this signal if you want to indicate progression of the hook. +signal progressed(percent: float) +## Emit this signal whenever you want custom text to be displayed +signal notified(text: String) + ## The type of hook determines where in the application's lifecycle this hook ## should be executed. enum TYPE { @@ -26,6 +31,11 @@ func _init(hook_type: TYPE) -> void: _hook_type = hook_type +## Name of the lifecycle hook +func get_name() -> String: + return "" + + ## Executes whenever an app from this library reaches the stage in its lifecycle ## designated by the hook type. E.g. a `PRE_LAUNCH` hook will have this method ## called whenever an app is about to launch. @@ -37,3 +47,19 @@ func execute(item: LibraryLaunchItem) -> void: ## the hook should be executed. func get_type() -> TYPE: return _hook_type + + +func _to_string() -> String: + var kind: String + match self.get_type(): + TYPE.PRE_LAUNCH: + kind = "PreLaunch" + TYPE.LAUNCH: + kind = "Launch" + TYPE.EXIT: + kind = "Exit" + var name := self.get_name() + if name.is_empty(): + name = "Anonymous" + + return "".format([kind, name]) diff --git a/core/systems/launcher/reaper.gd b/core/systems/launcher/reaper.gd index 597a92b2e..df4e53bf9 100644 --- a/core/systems/launcher/reaper.gd +++ b/core/systems/launcher/reaper.gd @@ -78,14 +78,14 @@ static func reap(pid: int, sig: SIG = SIG.TERM) -> void: # Kill PGID var cmd := "kill" - var args := [sig_arg, "--", "-{0}".format([p])] + var args: Array[String] = [sig_arg, "--", "-{0}".format([p])] logger.info(cmd + " " + " ".join(args)) - OS.execute(cmd, args) + Command.create(cmd, args).execute() # Kill PIDs args = [sig_arg, "--", "{0}".format([p])] logger.info(cmd + " " + " ".join(args)) - OS.execute(cmd, args) + Command.create(cmd, args).execute() var verb := "Reaped" if sig == SIG.STOP: @@ -175,6 +175,51 @@ static func get_pid_status(pid: int) -> Dictionary: return status +## Returns the parsed environment for the given PID. Returns an empty dictionary +## if the PID is not found or we do not have permission to read the environment. +static func get_pid_environment(pid: int) -> Dictionary[String, String]: + var env: Dictionary[String, String] = {} + + # Open the environment file for the given process + var env_path := "/".join(["/proc", str(pid), "environ"]) + var env_file := FileAccess.open(env_path, FileAccess.READ) + if not env_file: + return env + + # Read from the environment until no data is left + var env_data := PackedByteArray() + while not env_file.eof_reached(): + env_data.append_array(env_file.get_buffer(8128)) + + # The environment data is a null-terminated list of strings. Loop + # over the bytes to find slices between the null bytes and decode each + # found slice as a string. + var current_position := 0 + while true: + var next_position := env_data.find(0, current_position) + if next_position < 0: + break + var entry := env_data.slice(current_position, next_position) + var string := entry.get_string_from_utf8() + var key_value := string.split("=", true, 1) + if key_value.size() > 1: + env[key_value[0]] = key_value[1] + current_position = next_position + 1 + + return env + + +## Returns a list of all currently running processes +static func get_pids() -> PackedInt64Array: + var pids := PackedInt64Array() + for proc in DirAccess.get_directories_at("/proc"): + if not (proc as String).is_valid_int(): + continue + var process_id := proc.to_int() + pids.push_back(process_id) + return pids + + # Recursively finds all descendant processes and returns it as an array of PIDs static func pstree(pid: int) -> Array: var logger := Log.get_logger("Reaper") diff --git a/core/systems/launcher/running_app.gd b/core/systems/launcher/running_app.gd index 974a1ea14..a0b36a61f 100644 --- a/core/systems/launcher/running_app.gd +++ b/core/systems/launcher/running_app.gd @@ -17,7 +17,7 @@ signal app_type_detected ## Emitted when the window id of the given app has changed signal window_id_changed ## Emitted whenever the windows change for the app -signal window_ids_changed(from: PackedInt32Array, to: PackedInt32Array) +signal window_ids_changed(from: PackedInt64Array, to: PackedInt64Array) ## Emitted when the app id of the given app has changed signal app_id_changed ## Emitted when the app's state has changed @@ -61,6 +61,7 @@ var state: STATE = STATE.STARTED: state = v if old_state != state: state_changed.emit(old_state, state) +var state_steam: STATE = STATE.MISSING_WINDOW ## Whether or not the running app is suspended var is_suspended := false: set(v): @@ -76,12 +77,15 @@ var window_id: int: window_id = v window_id_changed.emit() ## A list of all detected window IDs related to the application -var window_ids: PackedInt32Array = PackedInt32Array(): +var window_ids: PackedInt64Array = PackedInt64Array(): set(v): var old_windows := window_ids window_ids = v + window_ids.sort() if old_windows != window_ids: window_ids_changed.emit(old_windows, window_ids) +## The identifier that is set as the OGUI_ID environment variable +var ogui_id: String ## The current app ID of the application var app_id: int: set(v): @@ -112,54 +116,76 @@ var steam_close_tries := 0 ## Flag for if OGUI should manage this app. Set to false if app is launched ## outside OGUI and we just want to track it. var is_ogui_managed: bool = true -var logger := Log.get_logger("RunningApp", Log.LEVEL.INFO) +var logger := Log.get_logger("RunningApp", Log.LEVEL.DEBUG) -func _init(item: LibraryLaunchItem, process_id: int, dsp: String) -> void: +func _init(item: LibraryLaunchItem, dsp: String) -> void: launch_item = item - pid = process_id display = dsp -## Run the given command and return it as a [RunningApp] -static func spawn(app: LibraryLaunchItem, env: Dictionary, cmd: String, args: PackedStringArray) -> RunningApp: +## Create a [RunningApp] with the given command without starting it. +static func create(app: LibraryLaunchItem, env: Dictionary, cmd: String, args: PackedStringArray) -> RunningApp: # Generate an app id for the running application based on its name var app_id := app.get_app_id() - - # Launch the application process - var pid := Reaper.create_process(cmd, args, app_id) var display := env["DISPLAY"] as String + var ogui_id := "" + if "OGUI_ID" in env: + ogui_id = env["OGUI_ID"] var command := PackedStringArray([cmd]) command.append_array(args) # Create a running app instance - var running_app := RunningApp.new(app, pid, display) + var running_app := RunningApp.new(app, display) running_app.command = command running_app.environment = env running_app.app_id = app_id + running_app.ogui_id = ogui_id + + return running_app + + +## Run the given command and return it as a [RunningApp] +static func spawn(app: LibraryLaunchItem, env: Dictionary, cmd: String, args: PackedStringArray) -> RunningApp: + var running_app := RunningApp.create(app, env, cmd, args) + running_app.start() return running_app -# TODO: Only call this on window creation/deletion -## Updates the running app and fires signals -func update() -> void: +## Start the running app +func start() -> void: + var args: Array[String] = [] + args.assign(self.command) + var cmd := args.pop_front() as String + var process_id := Reaper.create_process(cmd, args, self.app_id) + self.pid = process_id + logger.info("Launched with PID: {0}".format([self.pid])) + + +## Updates the state of the running app and fires signals using the given +## list of window ids from XWayland and list of process ids that match +## the OGUI_ID of this application. +func update(all_windows: PackedInt64Array, pids_with_ogui_id: PackedInt64Array) -> void: + # Updating the app state differs depending on whether it is running as an + # X11 app or a Wayland app. When an app is first started, the app type is + # unknown and detection will occur until we find out the type of app it is. match self.app_type: APP_TYPE.UNKNOWN: - self.app_type = discover_app_type() + self.app_type = discover_app_type(all_windows, pids_with_ogui_id) if self.app_type != APP_TYPE.UNKNOWN: app_type_detected.emit() - self.update() + self.update(all_windows, pids_with_ogui_id) APP_TYPE.X11: - update_xwayland_app() + update_xwayland_app(all_windows, pids_with_ogui_id) APP_TYPE.WAYLAND: - update_wayland_app() + update_wayland_app(pids_with_ogui_id) ## Tries to discover if the launched app is an X11 or Wayland application -func discover_app_type() -> APP_TYPE: +func discover_app_type(all_windows: PackedInt64Array, pids_with_ogui_id: PackedInt64Array) -> APP_TYPE: # Update all the windows - self.window_ids = get_all_window_ids() + self.window_ids = self.get_all_window_ids(all_windows, pids_with_ogui_id) # Check to see if running app's app_id exists in focusable_apps var xwayland_primary := gamescope.get_xwayland(gamescope.XWAYLAND_TYPE_PRIMARY) @@ -176,7 +202,7 @@ func discover_app_type() -> APP_TYPE: return APP_TYPE.UNKNOWN -func update_wayland_app() -> void: +func update_wayland_app(pids_with_ogui_id: PackedInt64Array) -> void: # Check if the app, or any of its children, are still running var running := is_running() if not running: @@ -211,15 +237,16 @@ func update_wayland_app() -> void: if steam_close_tries < 4: steam_close_tries += 1 return - var steam_pid := find_steam() + var steam_pid := find_steam(pids_with_ogui_id) if steam_pid > 0: logger.info("Trying to stop steam with pid: " + str(steam_pid)) - OS.execute("kill", ["-15", str(steam_pid)]) + var cmd := Command.create("kill", ["-15", str(steam_pid)]) + cmd.execute() -func update_xwayland_app() -> void: +func update_xwayland_app(all_windows: PackedInt64Array, pids_with_ogui_id: PackedInt64Array) -> void: # Update all windows related to the app's PID - self.window_ids = get_all_window_ids() + self.window_ids = self.get_all_window_ids(all_windows, pids_with_ogui_id) # Ensure that all windows related to the app have an app ID set _ensure_app_id() @@ -244,39 +271,67 @@ func update_xwayland_app() -> void: if not running: not_running_count += 1 + var state_str := { + STATE.STARTED: "started", + STATE.RUNNING: "running", + STATE.MISSING_WINDOW: "no window", + STATE.STOPPING: "stopping", + STATE.STOPPED: "stopped" + } + # Update the running app's state if not_running_count > 3: state = STATE.STOPPED app_killed.emit() elif state == STATE.STARTED and has_valid_window: state = STATE.RUNNING + if is_steam_app(): + state_steam = STATE.STARTED grab_focus() elif state == STATE.RUNNING and not has_valid_window: state = STATE.MISSING_WINDOW - var state_str := { - STATE.STARTED: "started", - STATE.RUNNING: "running", - STATE.MISSING_WINDOW: "no window", - STATE.STOPPING: "stopping", - STATE.STOPPED: "stopped" - } + # If this is a Steam app, we also need to wait until Steam starts the game + if is_steam_app() and state_steam == STATE.STARTED: + for window in self.window_ids: + if self.is_steam_window(window): + continue + logger.debug("Steam game appears to be running") + state_steam = STATE.RUNNING + self.window_id = window + grab_focus() + break + logger.trace(launch_item.name + " current steam state: " + state_str[state_steam]) + + # If the Steam game started, but now only Steam windows remain, indicate + # that Steam should stop. + if is_steam_app() and state_steam == STATE.RUNNING: + var only_steam_running := true + for window in self.window_ids: + if not self.is_steam_window(window): + only_steam_running = false + break + if only_steam_running: + state_steam = STATE.STOPPING + logger.trace(launch_item.name + " current state: " + state_str[state]) # TODO: Check all windows for STEAM_GAME prop # If this was launched by Steam, try and detect if the game closed # so we can kill Steam gracefully - if is_steam_app() and state == STATE.MISSING_WINDOW and is_ogui_managed: + if is_steam_app() and state_steam == STATE.STOPPING and is_ogui_managed: logger.trace(launch_item.name + " is a Steam game and has no valid window ID. It may have closed.") # Don't try closing Steam immediately. Wait a few more ticks before attempting # to close Steam. if steam_close_tries < 4: steam_close_tries += 1 return - var steam_pid := find_steam() + var steam_pid := find_steam(pids_with_ogui_id) if steam_pid > 0: logger.info("Trying to stop steam with pid: " + str(steam_pid)) OS.execute("kill", ["-15", str(steam_pid)]) + else: + state_steam = STATE.STOPPED ## Pauses/Resumes the running app by running 'kill -STOP' or 'kill -CONT' @@ -313,17 +368,16 @@ func get_window_id_from_pid() -> int: ## Attempt to discover all window IDs from the PID of the given application and ## the PIDs of all processes in the same process group. -func get_all_window_ids() -> PackedInt32Array: +func get_all_window_ids(all_windows: PackedInt64Array, pids_with_ogui_id: PackedInt64Array) -> PackedInt64Array: var app_name := launch_item.name - var window_ids := PackedInt32Array() - var pids := get_child_pids() + var window_ids := PackedInt64Array() + var pids := get_child_pids(pids_with_ogui_id) var xwayland := gamescope.get_xwayland_by_name(display) pids.append(pid) logger.trace(app_name + " found related PIDs: " + str(pids)) # Loop through all windows and check if the window belongs to one of our # processes - var all_windows := xwayland.get_all_windows(xwayland.root_window_id) for window_id in all_windows: if xwayland.has_app_id(window_id) and xwayland.get_app_id(window_id) == self.app_id: window_ids.append(window_id) @@ -334,6 +388,7 @@ func get_all_window_ids() -> PackedInt32Array: #logger.trace("Found window for pid", window_pid, ":", window_id) window_ids.append(window_id) + window_ids.sort() logger.trace(app_name + " found related window IDs: " + str(window_ids)) return window_ids @@ -347,19 +402,19 @@ func is_running() -> bool: ## Return a list of child PIDs. When launching apps with [Reaper], PR_SET_CHILD_SUBREAPER ## is set to prevent processes from re-parenting themselves to other processes. -func get_child_pids() -> PackedInt32Array: - var pids := PackedInt32Array() +func get_child_pids(pids_with_ogui_id: PackedInt64Array) -> PackedInt64Array: + var pids := pids_with_ogui_id.duplicate() - # Get all child processes + # Find any child processes that are not in the list of PIDs var child_pids := Reaper.pstree(pid) - pids.append_array(child_pids) + for child_pid in child_pids: + if child_pid in pids: + continue + pids.append(child_pid) # Get all PIDs that share the running app's process ID group - var pids_in_group := [] - for proc in DirAccess.get_directories_at("/proc"): - if not (proc as String).is_valid_int(): - continue - var process_id := proc.to_int() + var pids_in_group := PackedInt64Array() + for process_id in Reaper.get_pids(): if process_id in pids_in_group or process_id in pids: continue var pgid := Reaper.get_pid_group(process_id) @@ -403,32 +458,51 @@ func grab_focus() -> void: var xwayland_primary := gamescope.get_xwayland(gamescope.XWAYLAND_TYPE_PRIMARY) if not xwayland_primary: return - xwayland_primary.baselayer_app = self.app_id + + # Set the baselayer app id + var focused_apps := xwayland_primary.baselayer_apps + if self.app_id in focused_apps: + # Gamescope sometimes focuses correctly if the baselayer apps is set, so this is necessary + xwayland_primary.baselayer_apps = focused_apps + else: + # The app id NEEDS to be sandwiched between the extra "unknown" app id and + # the overlay app id. + var new_focused_apps := PackedInt64Array() + new_focused_apps.push_back(GamescopeInstance.EXTRA_UNKNOWN_GAME_ID) + new_focused_apps.push_back(self.app_id) + for other_app_id in focused_apps: + if other_app_id in [GamescopeInstance.EXTRA_UNKNOWN_GAME_ID, GamescopeInstance.OVERLAY_GAME_ID]: + continue + new_focused_apps.push_back(other_app_id) + new_focused_apps.push_back(GamescopeInstance.OVERLAY_GAME_ID) + xwayland_primary.baselayer_apps = new_focused_apps + + # Set the baselayer window id + xwayland_primary.baselayer_window = self.window_id focused = true ## Switches the app window to the given window ID. Returns an error if unable ## to switch to the window func switch_window(win_id: int, focus: bool = true) -> int: + logger.info("Switching to window:", win_id) + # Error if the window does not belong to the running app # TODO: Look into how window switching can work with Wayland windows if not win_id in window_ids: + logger.debug("Failed to switch window: window id does not exist") return ERR_DOES_NOT_EXIST # Get the primary XWayland instance var xwayland_primary := gamescope.get_xwayland(gamescope.XWAYLAND_TYPE_PRIMARY) if not xwayland_primary: + logger.debug("Failed to switch window: unable to find primary XWayland") return ERR_UNAVAILABLE - # Check if this app is a focusable window. - if not win_id in xwayland_primary.focusable_windows: - return ERR_UNAVAILABLE - # Update the window ID and optionally grab focus window_id = win_id if focus: grab_focus() - xwayland_primary.baselayer_window = win_id return OK @@ -442,9 +516,8 @@ func kill(sig: Reaper.SIG = Reaper.SIG.TERM) -> void: ## Iterates through all windows related to the app and sets the app ID property ## so they will appear as focusable windows to Gamescope func _ensure_app_id() -> void: - # If this is a Steam app, there's no need to set the app ID; Steam will do - # it for us. - if is_steam_app() or not is_ogui_managed: + # Don't mess with app IDs if this was not spawned by ogui + if not is_ogui_managed: return # Get the xwayland instance this app is running on @@ -458,8 +531,21 @@ func _ensure_app_id() -> void: # Try setting the app ID on each possible Window. If they are valid windows, # gamescope will make these windows available as focusable windows. for window in possible_windows: - if xwayland.has_app_id(window): + var window_app_id := xwayland.get_app_id(window) + if window_app_id != gamescope.OVERLAY_GAME_ID and window_app_id >= 0: continue + + # If the window is using the OVERLAY_GAME_ID (769), set it to something different + # so it does not fight with OGUI for focus. + if window_app_id == gamescope.OVERLAY_GAME_ID: + var new_app_id := self.app_id + if is_steam_app() and has_meta("steam_app_id"): + new_app_id = get_meta("steam_app_id") + if new_app_id != self.app_id: + self.app_id = new_app_id + if self.app_id == gamescope.OVERLAY_GAME_ID: + self.app_id += 1 + xwayland.set_app_id(window, self.app_id) @@ -472,11 +558,7 @@ func needs_window_id() -> bool: if window_id <= 0: logger.trace(launch_item.name + " has a bad window ID: " + str(window_id)) return true - # If this is a Steam app and the current window id is a Steam window, don't - # consider this a valid window id. TODO: We might not need this since we also - # check this in _discover_window_id() - if is_steam_app() and is_steam_window(window_id): - return true + var focusable_windows := xwayland_primary.focusable_windows if not window_id in focusable_windows: logger.trace(str(window_id) + " is not in the list of focusable windows") @@ -532,11 +614,6 @@ func _discover_window_id() -> int: var focusable := xwayland_primary.focusable_windows for window in possible_windows: if window in focusable: - # If this is a steam app, don't consider any Steam windows as valid - # discovered window ids for this app. - if is_steam_app() and is_steam_window(window): - logger.debug("Window", window, "is a Steam window") - continue return window return -1 @@ -564,6 +641,11 @@ func is_steam_window(window_id: int) -> bool: if not xwayland: return false + var window_name := xwayland.get_window_name(window_id) + var steam_window_names := ["Steam Big Picture Mode", "Steam", "steamwebhelper"] + if window_name in steam_window_names: + return true + var window_pids := xwayland.get_pids_for_window(window_id) for window_pid in window_pids: var pid_info := Reaper.get_pid_status(window_pid) @@ -572,14 +654,13 @@ func is_steam_window(window_id: int) -> bool: var process_name := pid_info["Name"] as String if process_name in ["steam", "steamwebhelper"]: return true - print(process_name) return false ## Finds the steam process so it can be killed when a game closes -func find_steam() -> int: - var child_pids := get_child_pids() +func find_steam(pids_with_ogui_id: PackedInt64Array) -> int: + var child_pids := get_child_pids(pids_with_ogui_id) for child_pid in child_pids: var pid_info := Reaper.get_pid_status(child_pid) if not "Name" in pid_info: diff --git a/core/ui/card_ui/card_ui.gd b/core/ui/card_ui/card_ui.gd index f34588ac6..aaf2d7b2a 100644 --- a/core/ui/card_ui/card_ui.gd +++ b/core/ui/card_ui/card_ui.gd @@ -65,7 +65,7 @@ func _setup(window_id: int) -> void: # As of Gamescope v3.16.3+, the baselayer atom needs to be set at the start _xwayland_primary.baselayer_window = 0 - _xwayland_primary.baselayer_app = GamescopeInstance.OVERLAY_GAME_ID + _xwayland_primary.baselayer_apps = PackedInt64Array([GamescopeInstance.EXTRA_UNKNOWN_GAME_ID, GamescopeInstance.OVERLAY_GAME_ID]) # Override reserved app ids for any newly created windows # Listen for window created/destroyed events diff --git a/core/ui/card_ui/navigation/running_game_card.gd b/core/ui/card_ui/navigation/running_game_card.gd index 18860ae57..41191cba2 100644 --- a/core/ui/card_ui/navigation/running_game_card.gd +++ b/core/ui/card_ui/navigation/running_game_card.gd @@ -116,7 +116,7 @@ func set_running_app(app: RunningApp): game_label.text = item.name # Connect to app signals to allow switching between app windows - var on_windows_changed := func(_from: PackedInt32Array, to: PackedInt32Array): + var on_windows_changed := func(_from: PackedInt64Array, to: PackedInt64Array): var xwayland := gamescope.get_xwayland(gamescope.XWAYLAND_TYPE_PRIMARY) var xwayland_game := gamescope.get_xwayland(gamescope.XWAYLAND_TYPE_GAME) var focusable_windows := xwayland.get_focusable_windows() diff --git a/core/ui/common/game/game_loading.gd b/core/ui/common/game/game_loading.gd index 0865c0369..80f053601 100644 --- a/core/ui/common/game/game_loading.gd +++ b/core/ui/common/game/game_loading.gd @@ -9,10 +9,15 @@ var popup_state := preload("res://assets/state/states/popup.tres") as State var in_game_state := preload("res://assets/state/states/in_game.tres") as State var game_launching := false +@onready var label := %RichTextLabel as RichTextLabel +@onready var progress_bar := %ProgressBar as ProgressBar + # Called when the node enters the scene tree for the first time. func _ready() -> void: launch_manager.app_launched.connect(_on_app_launched) + launch_manager.app_lifecycle_progressed.connect(_on_app_lifecycle_progress) + launch_manager.app_lifecycle_notified.connect(_on_app_lifecycle_notified) var on_state_changed := func(_from: State, to: State): if not game_launching: @@ -24,6 +29,8 @@ func _ready() -> void: func _on_app_launched(app: RunningApp): game_launching = true + label.text = "Loading..." + progress_bar.visible = false app.app_type_detected.connect(_on_window_created, CONNECT_ONE_SHOT) app.app_killed.connect(_on_window_created, CONNECT_ONE_SHOT) @@ -31,3 +38,20 @@ func _on_app_launched(app: RunningApp): func _on_window_created() -> void: game_launching = false visible = false + + +## Executed if a pre-launch app lifecycle hook is running and wants to display +## progress on the loading screen +func _on_app_lifecycle_progress(progress: float, type: AppLifecycleHook.TYPE) -> void: + if type != AppLifecycleHook.TYPE.PRE_LAUNCH: + return + progress_bar.visible = true + progress_bar.value = progress + + +## Executed if a pre-launch app lifecycle hook is running and wants to display +## progress text on the loading screen +func _on_app_lifecycle_notified(text: String, type: AppLifecycleHook.TYPE) -> void: + if type != AppLifecycleHook.TYPE.PRE_LAUNCH: + return + label.text = text diff --git a/core/ui/common/game/game_loading.tscn b/core/ui/common/game/game_loading.tscn index ebab0d563..316ae9e60 100644 --- a/core/ui/common/game/game_loading.tscn +++ b/core/ui/common/game/game_loading.tscn @@ -14,6 +14,63 @@ size_flags_horizontal = 3 size_flags_vertical = 3 script = ExtResource("1_1ucmh") +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Spacer1" type="Control" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +mouse_filter = 2 + +[node name="Spacer2" type="Control" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +mouse_filter = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Spacer1" type="Control" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +mouse_filter = 2 + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 40 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 40 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/MarginContainer"] +layout_mode = 2 + +[node name="RichTextLabel" type="RichTextLabel" parent="VBoxContainer/HBoxContainer/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 +theme_override_font_sizes/normal_font_size = 24 +bbcode_enabled = true +text = "Loading..." +horizontal_alignment = 1 +vertical_alignment = 2 + +[node name="ProgressBar" type="ProgressBar" parent="VBoxContainer/HBoxContainer/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Spacer2" type="Control" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +mouse_filter = 2 + [node name="ColorRect" type="ColorRect" parent="."] layout_mode = 1 anchors_preset = 15 diff --git a/extensions/Cargo.lock b/extensions/Cargo.lock index 24da11092..ad9626771 100644 --- a/extensions/Cargo.lock +++ b/extensions/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" @@ -57,9 +57,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ "concurrent-queue", "event-listener-strategy", @@ -83,9 +83,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" dependencies = [ "async-lock", "cfg-if", @@ -94,17 +94,16 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.0.7", + "rustix 1.0.8", "slab", - "tracing", - "windows-sys", + "windows-sys 0.60.2", ] [[package]] name = "async-lock" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ "event-listener", "event-listener-strategy", @@ -113,9 +112,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" +checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00" dependencies = [ "async-channel", "async-io", @@ -126,8 +125,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 1.0.7", - "tracing", + "rustix 1.0.8", ] [[package]] @@ -138,14 +136,14 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] name = "async-signal" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" +checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1" dependencies = [ "async-io", "async-lock", @@ -153,10 +151,10 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.0.7", + "rustix 1.0.8", "signal-hook-registry", "slab", - "windows-sys", + "windows-sys 0.60.2", ] [[package]] @@ -173,7 +171,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] @@ -184,9 +182,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" @@ -232,9 +230,9 @@ dependencies = [ [[package]] name = "blocking" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ "async-channel", "async-task", @@ -263,14 +261,14 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-unit" @@ -313,9 +311,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -375,9 +373,9 @@ checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" [[package]] name = "enumflags2" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ "enumflags2_derive", "serde", @@ -385,13 +383,13 @@ dependencies = [ [[package]] name = "enumflags2_derive" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] @@ -402,19 +400,19 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.60.2", ] [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -457,9 +455,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "fastrand", "futures-core", @@ -476,7 +474,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] @@ -502,7 +500,7 @@ dependencies = [ [[package]] name = "gamescope-x11-client" version = "0.1.0" -source = "git+https://github.com/ShadowBlip/gamescope-x11-client?branch=main#9a5ec71507544f1e9ef2e77791494222c620399f" +source = "git+https://github.com/ShadowBlip/gamescope-x11-client?branch=main#3cb6fdd564e13c2222606d5fae3abd81e6fe457b" dependencies = [ "log", "strum", @@ -544,7 +542,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] @@ -567,9 +565,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glam" -version = "0.30.3" +version = "0.30.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b46b9ca4690308844c644e7c634d68792467260e051c8543e0c7871662b3ba7" +checksum = "f2d1aab06663bdce00d6ca5e5ed586ec8d18033a771906c993a1e3755b368d85" [[package]] name = "godot" @@ -660,9 +658,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "heck" @@ -678,9 +676,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -690,12 +688,12 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -760,9 +758,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.172" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "linux-raw-sys" @@ -778,9 +776,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litrs" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" dependencies = [ "proc-macro2", ] @@ -812,9 +810,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memoffset" @@ -827,9 +825,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -841,8 +839,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -968,20 +966,20 @@ dependencies = [ [[package]] name = "pest" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.12", + "thiserror 2.0.14", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -989,24 +987,23 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] name = "pest_meta" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "once_cell", "pest", "sha2", ] @@ -1036,17 +1033,16 @@ dependencies = [ [[package]] name = "polling" -version = "3.8.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.0.7", - "tracing", - "windows-sys", + "rustix 1.0.8", + "windows-sys 0.60.2", ] [[package]] @@ -1069,9 +1065,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" dependencies = [ "unicode-ident", ] @@ -1107,9 +1103,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" @@ -1156,9 +1152,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] @@ -1232,9 +1228,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.37.1" +version = "1.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50" +checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" dependencies = [ "arrayvec", "borsh", @@ -1248,9 +1244,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustix" @@ -1262,27 +1258,27 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys", + "windows-sys 0.60.2", ] [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -1319,14 +1315,14 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -1342,7 +1338,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] @@ -1358,9 +1354,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -1373,12 +1369,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -1393,7 +1386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1418,7 +1411,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] @@ -1434,9 +1427,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" dependencies = [ "proc-macro2", "quote", @@ -1458,8 +1451,8 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.7", - "windows-sys", + "rustix 1.0.8", + "windows-sys 0.59.0", ] [[package]] @@ -1473,11 +1466,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.14", ] [[package]] @@ -1488,18 +1481,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] @@ -1519,9 +1512,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", @@ -1534,7 +1527,7 @@ dependencies = [ "slab", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1545,7 +1538,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] @@ -1578,13 +1571,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] @@ -1639,9 +1632,9 @@ checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ "js-sys", "wasm-bindgen", @@ -1665,9 +1658,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -1700,7 +1693,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", "wasm-bindgen-shared", ] @@ -1722,7 +1715,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1758,6 +1751,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-sys" version = "0.59.0" @@ -1767,6 +1766,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -1791,13 +1799,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -1810,6 +1835,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -1822,6 +1853,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -1834,12 +1871,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -1852,6 +1901,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -1864,6 +1919,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -1876,6 +1937,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -1888,11 +1955,17 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -1958,7 +2031,7 @@ dependencies = [ "serde_repr", "tracing", "uds_windows", - "windows-sys", + "windows-sys 0.59.0", "winnow", "zbus_macros", "zbus_names", @@ -1974,7 +2047,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", "zbus_names", "zvariant", "zvariant_utils", @@ -1994,22 +2067,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", ] [[package]] @@ -2035,7 +2108,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.105", "zvariant_utils", ] @@ -2049,6 +2122,6 @@ dependencies = [ "quote", "serde", "static_assertions", - "syn 2.0.101", + "syn 2.0.105", "winnow", ] diff --git a/extensions/core/src/gamescope.rs b/extensions/core/src/gamescope.rs index 92e2570e5..a02b41a2d 100644 --- a/extensions/core/src/gamescope.rs +++ b/extensions/core/src/gamescope.rs @@ -37,6 +37,10 @@ impl GamescopeInstance { #[constant] const OVERLAY_GAME_ID: u32 = 769; + /// Steam sets this unknown value as a baselayer app id + #[constant] + const EXTRA_UNKNOWN_GAME_ID: u32 = 413091; + /// Return the Gamescope XWayland of the given type. #[func] pub fn get_xwayland(&self, kind: u32) -> Option> { diff --git a/extensions/core/src/gamescope/x11_client.rs b/extensions/core/src/gamescope/x11_client.rs index d4cfbe7a2..b90cf823e 100644 --- a/extensions/core/src/gamescope/x11_client.rs +++ b/extensions/core/src/gamescope/x11_client.rs @@ -24,6 +24,23 @@ enum Signal { PropertyChanged { property: String }, } +/// Structure for X11 window geometry +struct WindowGeometry { + depth: u8, + _sequence: u16, + _length: u32, + root: u32, + x: i16, + y: i16, + width: u16, + height: u16, + _border_width: u16, +} + +/// A single Gamescope XWayland instance. In typical setups there are two +/// XWayland instances: one for the overlay and one for the running game. +/// The first XWayland instance is the "primary" instance which has extra +/// information about what window is in focus, etc. #[derive(GodotClass)] #[class(no_init, base=Resource)] pub struct GamescopeXWayland { @@ -84,6 +101,9 @@ pub struct GamescopeXWayland { /// Current manually focused app #[var(get = get_baselayer_app, set = set_baselayer_app)] baselayer_app: u32, + /// Current manually focused app + #[var(get = get_baselayer_apps, set = set_baselayer_apps)] + baselayer_apps: PackedInt64Array, } #[godot_api] @@ -134,7 +154,7 @@ impl GamescopeXWayland { // Create an XWayland client instance for this display let mut xwayland = XWayland::new(name.clone().into()); if let Err(e) = xwayland.connect() { - log::error!("Failed to connect to XWayland display '{name}': {e:?}"); + log::debug!("Failed to connect to XWayland display '{name}': {e:?}"); } let is_primary = xwayland.is_primary_instance().unwrap_or_default(); let root_window_id = xwayland.get_root_window_id().unwrap_or_default(); @@ -148,13 +168,13 @@ impl GamescopeXWayland { for event in property_rx.into_iter() { let signal = Signal::PropertyChanged { property: event }; if let Err(e) = signals_tx.send(signal) { - log::error!("Error sending property changed signal: {e:?}"); + log::debug!("Error sending property changed signal: {e:?}"); break; } } }); } else { - log::error!("Failed to listen for XWayland property changes"); + log::debug!("Failed to listen for XWayland property changes"); } } @@ -168,13 +188,13 @@ impl GamescopeXWayland { WindowLifecycleEvent::Destroyed => Signal::WindowDestroyed { window_id }, }; if let Err(e) = signals_tx.send(signal) { - log::error!("Error sending window signal: {e:?}"); + log::debug!("Error sending window signal: {e:?}"); break; } } }); } else { - log::error!("Failed to listen for XWayland windows created/destroyed"); + log::debug!("Failed to listen for XWayland windows created/destroyed"); } // Setup the initial state @@ -203,6 +223,7 @@ impl GamescopeXWayland { allow_tearing: Default::default(), baselayer_window: Default::default(), baselayer_app: Default::default(), + baselayer_apps: Default::default(), } }) } @@ -272,7 +293,7 @@ impl GamescopeXWayland { let (_, rx) = match self.xwayland.listen_for_window_property_changes(window_id) { Ok(result) => result, Err(e) => { - log::error!("Failed to watch window properties for window '{window_id}': {e:?}"); + log::debug!("Failed to watch window properties for window '{window_id}': {e:?}"); return -1; } }; @@ -290,7 +311,7 @@ impl GamescopeXWayland { Err(e) => match e { TryRecvError::Empty => break 'inner, TryRecvError::Disconnected => { - log::error!("Backend thread is not running!"); + log::debug!("Backend thread is not running!"); return; } }, @@ -300,7 +321,7 @@ impl GamescopeXWayland { property: event, }; if let Err(e) = signals_tx.send(signal) { - log::error!("Failed to send property change signal: {e:?}"); + log::debug!("Failed to send property change signal: {e:?}"); break 'outer; } } @@ -332,7 +353,7 @@ impl GamescopeXWayland { // Cancel the listener task let Some(task) = self.window_watch_handles.get(&(window_id as u32)) else { - log::error!("Task wasn't found but was being watched: {window_id}"); + log::debug!("Task wasn't found but was being watched: {window_id}"); return -1; }; @@ -347,7 +368,7 @@ impl GamescopeXWayland { let pids = match self.xwayland.get_pids_for_window(window_id) { Ok(pids) => pids, Err(e) => { - log::error!("Failed to get pids for window '{window_id}': {e:?}"); + log::debug!("Failed to get pids for window '{window_id}': {e:?}"); return PackedInt64Array::new(); } }; @@ -362,7 +383,7 @@ impl GamescopeXWayland { let windows = match self.xwayland.get_windows_for_pid(pid) { Ok(windows) => windows, Err(e) => { - log::error!("Failed to get windows for pid '{pid}': {e:?}"); + log::debug!("Failed to get windows for pid '{pid}': {e:?}"); return PackedInt64Array::new(); } }; @@ -377,7 +398,7 @@ impl GamescopeXWayland { let name = match self.xwayland.get_window_name(window_id) { Ok(name) => name, Err(e) => { - log::error!("Failed to get window name for window '{window_id}': {e:?}"); + log::debug!("Failed to get window name for window '{window_id}': {e:?}"); return "".into(); } }; @@ -385,13 +406,86 @@ impl GamescopeXWayland { name.unwrap_or_default().into() } + /// Returns the window x and y position for the given window. Returns (-1, -1) + /// if the window geometry is not found. + #[func] + pub fn get_window_position(&self, window_id: u32) -> Vector2i { + let Some(geometry) = self.get_geometry_for_window(window_id) else { + return Vector2i::new(-1, -1); + }; + Vector2i::new(geometry.x as i32, geometry.y as i32) + } + + /// Returns the window width and height for the given window. Returns (-1, -1) + /// if the window geometry is not found. + #[func] + pub fn get_window_size(&self, window_id: u32) -> Vector2i { + let Some(geometry) = self.get_geometry_for_window(window_id) else { + return Vector2i::new(-1, -1); + }; + Vector2i::new(geometry.width as i32, geometry.height as i32) + } + + /// Returns the window sizes for the given list of window ids. Returns (-1, -1) + /// if the geometry for a window is not found. + #[func] + pub fn get_window_sizes(&self, window_ids: PackedInt64Array) -> Array { + let mut array = Array::new(); + for window_id in window_ids.as_slice() { + if *window_id <= 0 { + array.push(Vector2i::new(-1, -1)); + continue; + } + let size = self.get_window_size(*window_id as u32); + array.push(size); + } + + array + } + + /// Returns the window depth. Returns -1 if the window geometry is not found. + #[func] + pub fn get_window_depth(&self, window_id: u32) -> i32 { + let Some(geometry) = self.get_geometry_for_window(window_id) else { + return -1; + }; + geometry.depth as i32 + } + + /// Returns the window root. Returns 0 if the window is not found. + #[func] + pub fn get_window_root(&self, window_id: u32) -> u32 { + let Some(geometry) = self.get_geometry_for_window(window_id) else { + return 0; + }; + geometry.root + } + + /// Returns the x11 window geometry for the given window + fn get_geometry_for_window(&self, window_id: u32) -> Option { + let reply = self.xwayland.get_geometry_for_window(window_id).ok()?; + let geometry = WindowGeometry { + depth: reply.depth, + _sequence: reply.sequence, + _length: reply.length, + root: reply.root, + x: reply.x, + y: reply.y, + width: reply.width, + height: reply.height, + _border_width: reply.border_width, + }; + + Some(geometry) + } + /// Returns the window ids of the children of the given window #[func] fn get_window_children(&self, window_id: u32) -> PackedInt64Array { let windows = match self.xwayland.get_window_children(window_id) { Ok(windows) => windows, Err(e) => { - log::error!("Failed to get window children for window '{window_id}': {e:?}"); + log::debug!("Failed to get window children for window '{window_id}': {e:?}"); return PackedInt64Array::new(); } }; @@ -406,7 +500,7 @@ impl GamescopeXWayland { let windows = match self.xwayland.get_all_windows(window_id) { Ok(windows) => windows, Err(e) => { - log::error!("Failed to get all window children for window '{window_id}': {e:?}"); + log::debug!("Failed to get all window children for window '{window_id}': {e:?}"); return PackedInt64Array::new(); } }; @@ -422,7 +516,7 @@ impl GamescopeXWayland { match self.xwayland.get_app_id(window_id) { Ok(app_id) => app_id.unwrap_or_default(), Err(e) => { - log::error!("Failed to get app id for window '{window_id}': {e:?}"); + log::debug!("Failed to get app id for window '{window_id}': {e:?}"); 0 } } @@ -432,7 +526,7 @@ impl GamescopeXWayland { #[func] fn set_app_id(&self, window_id: u32, app_id: u32) -> i32 { if let Err(e) = self.xwayland.set_app_id(window_id, app_id) { - log::error!("Failed to set app id {app_id} on window '{window_id}': {e:?}"); + log::debug!("Failed to set app id {app_id} on window '{window_id}': {e:?}"); return -1; } 0 @@ -445,7 +539,7 @@ impl GamescopeXWayland { .xwayland .remove_xprop(window_id, GamescopeAtom::SteamGame) { - log::error!("Failed to remove app id from window '{window_id}': {e:?}"); + log::debug!("Failed to remove app id from window '{window_id}': {e:?}"); return -1; } 0 @@ -457,7 +551,7 @@ impl GamescopeXWayland { match self.xwayland.has_app_id(window_id) { Ok(v) => v, Err(e) => { - log::error!("Failed to check window '{window_id}' for app id: {e:?}"); + log::debug!("Failed to check window '{window_id}' for app id: {e:?}"); false } } @@ -472,7 +566,7 @@ impl GamescopeXWayland { { Ok(v) => v, Err(e) => { - log::error!( + log::debug!( "Failed to check window '{window_id}' has STEAM_NOTIFICATION property: {e:?}" ); false @@ -489,7 +583,7 @@ impl GamescopeXWayland { { Ok(v) => v, Err(e) => { - log::error!( + log::debug!( "Failed to check window '{window_id}' has STEAM_INPUT_FOCUS property: {e:?}" ); false @@ -506,7 +600,7 @@ impl GamescopeXWayland { { Ok(v) => v, Err(e) => { - log::error!( + log::debug!( "Failed to check window '{window_id}' has STEAM_OVERLAY property: {e:?}" ); false @@ -514,19 +608,19 @@ impl GamescopeXWayland { } } - /// --- XWayland Primary --- + // --- XWayland Primary --- /// Return a list of focusable apps #[func] fn get_focusable_apps(&mut self) -> PackedInt64Array { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } let value = match self.xwayland.get_focusable_apps() { Ok(value) => value, Err(e) => { - log::error!("Failed to get focusable apps: {e:?}"); + log::debug!("Failed to get focusable apps: {e:?}"); return Default::default(); } }; @@ -542,13 +636,13 @@ impl GamescopeXWayland { #[func] fn get_focusable_windows(&mut self) -> PackedInt64Array { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } let value = match self.xwayland.get_focusable_windows() { Ok(value) => value, Err(e) => { - log::error!("Failed to get focusable windows: {e:?}"); + log::debug!("Failed to get focusable windows: {e:?}"); return Default::default(); } }; @@ -564,13 +658,13 @@ impl GamescopeXWayland { #[func] fn get_focusable_window_names(&mut self) -> PackedStringArray { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } let value = match self.xwayland.get_focusable_window_names() { Ok(value) => value, Err(e) => { - log::error!("Failed to get focusable windows: {e:?}"); + log::debug!("Failed to get focusable windows: {e:?}"); return Default::default(); } }; @@ -583,13 +677,13 @@ impl GamescopeXWayland { #[func] fn get_focused_window(&mut self) -> u32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } let value = match self.xwayland.get_focused_window() { Ok(value) => value, Err(e) => { - log::error!("Failed to get focused window: {e:?}"); + log::debug!("Failed to get focused window: {e:?}"); return Default::default(); } }; @@ -602,13 +696,13 @@ impl GamescopeXWayland { #[func] fn get_focused_app(&mut self) -> u32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } let value = match self.xwayland.get_focused_app() { Ok(value) => value, Err(e) => { - log::error!("Failed to get focused app: {e:?}"); + log::debug!("Failed to get focused app: {e:?}"); return Default::default(); } }; @@ -621,13 +715,13 @@ impl GamescopeXWayland { #[func] fn get_focused_app_gfx(&mut self) -> u32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } let value = match self.xwayland.get_focused_app_gfx() { Ok(value) => value, Err(e) => { - log::error!("Failed to get focused app gfx: {e:?}"); + log::debug!("Failed to get focused app gfx: {e:?}"); return Default::default(); } }; @@ -640,14 +734,14 @@ impl GamescopeXWayland { #[func] fn get_overlay_focused(&mut self) -> bool { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } let focused = match self.xwayland.is_overlay_focused() { Ok(value) => value, Err(e) => { - log::error!("Failed to get overlay focused: {e:?}"); + log::debug!("Failed to get overlay focused: {e:?}"); Default::default() } }; @@ -659,13 +753,13 @@ impl GamescopeXWayland { #[func] fn get_fps_limit(&mut self) -> u32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } let value = match self.xwayland.get_fps_limit() { Ok(value) => value, Err(e) => { - log::error!("Failed to get fps limit: {e:?}"); + log::debug!("Failed to get fps limit: {e:?}"); return Default::default(); } }; @@ -678,11 +772,11 @@ impl GamescopeXWayland { #[func] fn set_fps_limit(&mut self, fps: u32) { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return; } if let Err(e) = self.xwayland.set_fps_limit(fps) { - log::error!("Failed to set FPS limit to {fps}: {e:?}"); + log::debug!("Failed to set FPS limit to {fps}: {e:?}"); } self.fps_limit = fps; } @@ -691,13 +785,13 @@ impl GamescopeXWayland { #[func] fn get_blur_mode(&mut self) -> u32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } let value = match self.xwayland.get_blur_mode() { Ok(value) => value, Err(e) => { - log::error!("Failed to get blur mode: {e:?}"); + log::debug!("Failed to get blur mode: {e:?}"); return Default::default(); } }; @@ -718,7 +812,7 @@ impl GamescopeXWayland { #[func] fn set_blur_mode(&mut self, mode: u32) { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } let blur_mode = match mode { @@ -728,7 +822,7 @@ impl GamescopeXWayland { _ => BlurMode::Off, }; if let Err(e) = self.xwayland.set_blur_mode(blur_mode) { - log::error!("Failed to set blur mode to: {mode}: {e:?}"); + log::debug!("Failed to set blur mode to: {mode}: {e:?}"); } self.blur_mode = mode; } @@ -737,7 +831,7 @@ impl GamescopeXWayland { #[func] fn get_blur_radius(&self) -> u32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } self.blur_radius @@ -747,11 +841,11 @@ impl GamescopeXWayland { #[func] fn set_blur_radius(&mut self, radius: u32) { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return; } if let Err(e) = self.xwayland.set_blur_radius(radius) { - log::error!("Failed to set blur radius to: {radius}: {e:?}"); + log::debug!("Failed to set blur radius to: {radius}: {e:?}"); } self.blur_radius = radius; } @@ -760,7 +854,7 @@ impl GamescopeXWayland { #[func] fn get_allow_tearing(&self) -> bool { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } self.allow_tearing @@ -770,11 +864,11 @@ impl GamescopeXWayland { #[func] fn set_allow_tearing(&mut self, allow: bool) { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return; } if let Err(e) = self.xwayland.set_allow_tearing(allow) { - log::error!("Failed to set allow tearing to: {allow}: {e:?}"); + log::debug!("Failed to set allow tearing to: {allow}: {e:?}"); } self.allow_tearing = allow; } @@ -783,13 +877,13 @@ impl GamescopeXWayland { #[func] fn is_focusable_app(&self, window_id: u32) -> bool { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } match self.xwayland.is_focusable_app(window_id) { Ok(is_focusable) => is_focusable, Err(e) => { - log::error!("Failed to check if window '{window_id}' is focusable app: {e:?}"); + log::debug!("Failed to check if window '{window_id}' is focusable app: {e:?}"); Default::default() } } @@ -801,11 +895,11 @@ impl GamescopeXWayland { #[func] fn set_main_app(&self, window_id: u32) -> i32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } if let Err(e) = self.xwayland.set_main_app(window_id) { - log::error!("Failed to set window '{window_id}' as main app: {e:?}"); + log::debug!("Failed to set window '{window_id}' as main app: {e:?}"); return -1; } 0 @@ -816,11 +910,11 @@ impl GamescopeXWayland { #[func] fn set_input_focus(&self, window_id: u32, value: u32) -> i32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } if let Err(e) = self.xwayland.set_input_focus(window_id, value) { - log::error!("Failed to set input focus on '{window_id}' to '{value}': {e:?}"); + log::debug!("Failed to set input focus on '{window_id}' to '{value}': {e:?}"); return -1; } 0 @@ -830,13 +924,13 @@ impl GamescopeXWayland { #[func] fn get_overlay(&self, window_id: u32) -> u32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } match self.xwayland.get_overlay(window_id) { Ok(value) => value.unwrap_or_default(), Err(e) => { - log::error!("Failed to get overlay status for window '{window_id}': {e:?}"); + log::debug!("Failed to get overlay status for window '{window_id}': {e:?}"); 0 } } @@ -846,11 +940,11 @@ impl GamescopeXWayland { #[func] fn set_overlay(&self, window_id: u32, value: u32) -> i32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } if let Err(e) = self.xwayland.set_overlay(window_id, value) { - log::error!("Failed to set overlay on '{window_id}' to '{value}': {e:?}"); + log::debug!("Failed to set overlay on '{window_id}' to '{value}': {e:?}"); return -1; } 0 @@ -861,11 +955,11 @@ impl GamescopeXWayland { #[func] fn set_notification(&self, window_id: u32, value: u32) -> i32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } if let Err(e) = self.xwayland.set_notification(window_id, value) { - log::error!("Failed to set notification on '{window_id}' to '{value}': {e:?}"); + log::debug!("Failed to set notification on '{window_id}' to '{value}': {e:?}"); return -1; } 0 @@ -875,11 +969,11 @@ impl GamescopeXWayland { #[func] fn set_external_overlay(&self, window_id: u32, value: u32) -> i32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } if let Err(e) = self.xwayland.set_external_overlay(window_id, value) { - log::error!("Failed to set external overlay on '{window_id}' to '{value}': {e:?}"); + log::debug!("Failed to set external overlay on '{window_id}' to '{value}': {e:?}"); return -1; } 0 @@ -889,13 +983,13 @@ impl GamescopeXWayland { #[func] fn get_baselayer_window(&mut self) -> u32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } let value = match self.xwayland.get_baselayer_window() { Ok(value) => value, Err(e) => { - log::error!("Failed to get baselayer window: {e:?}"); + log::debug!("Failed to get baselayer window: {e:?}"); return Default::default(); } }; @@ -908,11 +1002,11 @@ impl GamescopeXWayland { #[func] fn set_baselayer_window(&mut self, window_id: u32) { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return; } if let Err(e) = self.xwayland.set_baselayer_window(window_id) { - log::error!("Failed to set baselayer window to {window_id}: {e:?}"); + log::debug!("Failed to set baselayer window to {window_id}: {e:?}"); } self.baselayer_window = window_id; } @@ -921,7 +1015,7 @@ impl GamescopeXWayland { #[func] fn remove_baselayer_window(&mut self) { if let Err(e) = self.xwayland.remove_baselayer_window() { - log::error!("Failed to remove baselayer window: {e:?}"); + log::debug!("Failed to remove baselayer window: {e:?}"); } self.baselayer_window = 0; } @@ -930,48 +1024,84 @@ impl GamescopeXWayland { #[func] fn get_baselayer_app(&mut self) -> u32 { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return Default::default(); } let value = match self.xwayland.get_baselayer_app_id() { Ok(value) => value, Err(e) => { - log::error!("Failed to get baselayer app id: {e:?}"); + log::debug!("Failed to get baselayer app id: {e:?}"); return Default::default(); } }; - self.baselayer_window = value.unwrap_or_default(); - self.baselayer_window + self.baselayer_app = value.unwrap_or_default(); + self.baselayer_app } /// Focuses the app with the given app id #[func] fn set_baselayer_app(&mut self, app_id: u32) { if !self.is_primary { - log::error!("XWayland instance is not primary!"); + log::debug!("XWayland instance is not primary!"); return; } if let Err(e) = self.xwayland.set_baselayer_app_id(app_id) { - log::error!("Failed to set baselayer app id to {app_id}: {e:?}"); + log::debug!("Failed to set baselayer app id to {app_id}: {e:?}"); } - self.baselayer_window = app_id; + self.baselayer_app = app_id; } /// Removes the baselayer property to un-focus apps #[func] fn remove_baselayer_app(&mut self) { if let Err(e) = self.xwayland.remove_baselayer_app_id() { - log::error!("Failed to remove baselayer app: {e:?}"); + log::debug!("Failed to remove baselayer app: {e:?}"); } self.baselayer_window = 0; } + /// Returns the app id(s) of the currently manually focused app + #[func] + fn get_baselayer_apps(&mut self) -> PackedInt64Array { + if !self.is_primary { + log::debug!("XWayland instance is not primary!"); + return Default::default(); + } + let value = match self.xwayland.get_baselayer_app_ids() { + Ok(value) => value, + Err(e) => { + log::debug!("Failed to get baselayer app ids: {e:?}"); + return Default::default(); + } + }; + let Some(app_ids) = value else { + return Default::default(); + }; + let app_ids: Vec = app_ids.into_iter().map(|v| v as i64).collect(); + self.baselayer_apps = app_ids.into(); + self.baselayer_apps.clone() + } + + /// Focuses the app with the given app id(s) + #[func] + fn set_baselayer_apps(&mut self, app_ids: PackedInt64Array) { + if !self.is_primary { + log::debug!("XWayland instance is not primary!"); + return; + } + let ids = app_ids.to_vec().iter().map(|v| *v as u32).collect(); + if let Err(e) = self.xwayland.set_baselayer_app_ids(ids) { + log::debug!("Failed to set baselayer app ids to {app_ids:?}: {e:?}"); + } + self.baselayer_apps = app_ids; + } + /// Request a screenshot from Gamescope #[func] fn request_screenshot(&self) { if let Err(e) = self.xwayland.request_screenshot() { - log::error!("Failed to request screenshot: {e:?}"); + log::debug!("Failed to request screenshot: {e:?}"); } } @@ -984,7 +1114,7 @@ impl GamescopeXWayland { Err(e) => match e { TryRecvError::Empty => break, TryRecvError::Disconnected => { - log::error!("Backend thread is not running!"); + log::debug!("Backend thread is not running!"); return; } }, diff --git a/package/nix/package.nix b/package/nix/package.nix index 199c15a0a..509097be5 100644 --- a/package/nix/package.nix +++ b/package/nix/package.nix @@ -25,7 +25,7 @@ stdenv.mkDerivation (finalAttrs: { cargoDeps = rustPlatform.fetchCargoVendor { inherit (finalAttrs) src; sourceRoot = "OpenGamepadUI/${finalAttrs.cargoRoot}"; - hash = "sha256-vgaa7Pe0lksiGEpQbn2he5CzhVWoHUSPuXqCwSkoDco="; + hash = "sha256-xewyt1KuQ96FNYBKlC9VT7KEDDTUasavTsl+/5WXnU4="; }; cargoRoot = "extensions";