From 5a969428dd0eef545b3642a1850240b468689b79 Mon Sep 17 00:00:00 2001 From: William Edwards Date: Mon, 30 Sep 2024 00:41:16 -0700 Subject: [PATCH] fix(RunningApp): focus apps by app id instead of window id --- core/global/launch_manager.gd | 15 +++- core/systems/launcher/running_app.gd | 16 +++-- core/systems/network/unix_socket_client.gd | 59 ---------------- core/ui/components/input_texture_rect.gd | 53 --------------- extensions/Cargo.lock | 45 +++++++----- extensions/core/Cargo.toml | 2 +- extensions/core/src/dbus/bluez/device1.rs | 2 + extensions/core/src/gamescope/x11_client.rs | 68 +++++++++++++++++-- .../input/inputplumber/composite_device.rs | 2 +- extensions/core/src/system/command.rs | 6 +- 10 files changed, 119 insertions(+), 149 deletions(-) delete mode 100644 core/systems/network/unix_socket_client.gd delete mode 100644 core/ui/components/input_texture_rect.gd diff --git a/core/global/launch_manager.gd b/core/global/launch_manager.gd index 9cadd051..d9b9ff68 100644 --- a/core/global/launch_manager.gd +++ b/core/global/launch_manager.gd @@ -108,9 +108,21 @@ func _init() -> void: # If the app has a gamepad profile, set it if self._current_app: set_app_gamepad_profile(self._current_app) - _xwayland_primary.focused_app_updated.connect(on_focused_app_changed) + # Listen for when focusable apps change + var on_focusable_apps_changed := func(from: PackedInt64Array, to: PackedInt64Array): + if from == to: + return + logger.debug("Focusable apps changed from", from, "to", to) + # 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() + _xwayland_primary.focusable_apps_updated.connect(on_focusable_apps_changed) + # Whenever the in-game state is entered, set the gamepad profile var on_game_state_entered := func(_from: State): if _current_app: @@ -201,7 +213,6 @@ func launch(app: LibraryLaunchItem) -> RunningApp: env["DISPLAY"] = _xwayland_game.name else: env["DISPLAY"] = "" - var display := env["DISPLAY"] as String # Set the OGUI ID environment variable env["OGUI_ID"] = app.name diff --git a/core/systems/launcher/running_app.gd b/core/systems/launcher/running_app.gd index 4f873f98..790c10aa 100644 --- a/core/systems/launcher/running_app.gd +++ b/core/systems/launcher/running_app.gd @@ -189,6 +189,9 @@ func update_wayland_app() -> void: state = STATE.RUNNING #grab_focus() # How can we grab wayland window focus? + # Update the focus state of the app + focused = is_focused() + var state_str := { STATE.STARTED: "started", STATE.RUNNING: "running", @@ -373,7 +376,7 @@ func get_child_pids() -> PackedInt32Array: ## Returns whether or not the app can be switched to/focused func can_focus() -> bool: - return window_id > 0 + return self.app_id > 0 ## Return true if the currently running app is focused @@ -383,8 +386,8 @@ func is_focused() -> bool: var xwayland_primary := gamescope.get_xwayland(gamescope.XWAYLAND_TYPE_PRIMARY) if not xwayland_primary: return false - var focused_window := xwayland_primary.focused_window - return window_id == focused_window or focused_window in window_ids + var focused_app := xwayland_primary.focused_app + return self.app_id == focused_app ## Focuses to the app's window @@ -394,7 +397,7 @@ func grab_focus() -> void: var xwayland_primary := gamescope.get_xwayland(gamescope.XWAYLAND_TYPE_PRIMARY) if not xwayland_primary: return - xwayland_primary.set_baselayer_window(window_id) + xwayland_primary.baselayer_app = self.app_id focused = true @@ -402,6 +405,7 @@ func grab_focus() -> void: ## to switch to the window func switch_window(win_id: int, focus: bool = true) -> int: # 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: return ERR_DOES_NOT_EXIST @@ -418,6 +422,7 @@ func switch_window(win_id: int, focus: bool = true) -> int: window_id = win_id if focus: grab_focus() + xwayland_primary.baselayer_window = win_id return OK @@ -446,11 +451,10 @@ 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. - var app_name := launch_item.name for window in possible_windows: if xwayland.has_app_id(window): continue - xwayland.set_app_id(window, window) + xwayland.set_app_id(window, self.app_id) ## Returns whether or not the window id of the running app needs to be discovered diff --git a/core/systems/network/unix_socket_client.gd b/core/systems/network/unix_socket_client.gd deleted file mode 100644 index 19ac3e48..00000000 --- a/core/systems/network/unix_socket_client.gd +++ /dev/null @@ -1,59 +0,0 @@ -@icon("res://assets/editor-icons/socket-bold.svg") -extends Node -class_name UnixSocketClient - -signal connected -signal data -signal disconnected -signal error - -var _is_open: bool = false -var _stream := StreamPeerUnix.new() -var _logger := Log.get_logger("UnixSocketClient") - - -func _ready() -> void: - _is_open = _stream.is_open() - - -func _process(_delta: float) -> void: - var new_status := _stream.is_open() - if new_status != _is_open: - var prev_status := _is_open - _is_open = new_status - if prev_status and not new_status: - _logger.info("Disconnected from socket.") - disconnected.emit() - if new_status: - _logger.info("Connected to socket.") - connected.emit() - - if _is_open: - var available_bytes: int = _stream.get_available_bytes() - if available_bytes > 0: - _logger.debug("available bytes: " + str(available_bytes)) - var parts := _stream.get_partial_data(available_bytes) - # Check for read error. - if parts[0] != OK: - _logger.error("Error getting data from stream: " + str(parts[0])) - error.emit() - else: - data.emit(parts[1]) - - -func open(path: String) -> void: - # Reset status so we can tell if it changes to error again. - _is_open = false - if _stream.open(path) != OK: - error.emit() - - -func send(data: PackedByteArray) -> bool: - if not _is_open: - _logger.error("Error: Stream is not currently connected.") - return false - var err: int = _stream.put_data(data) - if err != OK: - _logger.error("Error writing to stream: " + str(err)) - return false - return true diff --git a/core/ui/components/input_texture_rect.gd b/core/ui/components/input_texture_rect.gd deleted file mode 100644 index c7e48c87..00000000 --- a/core/ui/components/input_texture_rect.gd +++ /dev/null @@ -1,53 +0,0 @@ -@tool -extends TextureRect -class_name InputTextureRect - - -@export var path : String = "": - set(_path): - path = _path - if is_inside_tree(): - if force_type > 0: - texture = ControllerIcons.parse_path(path, force_type - 1) - else: - texture = ControllerIcons.parse_path(path) - -@export_enum("Both", "Keyboard/Mouse", "Controller") var show_only : int = 0: - set(_show_only): - show_only = _show_only - _on_input_type_changed(ControllerIcons._last_input_type) - -@export_enum("None", "Keyboard/Mouse", "Controller") var force_type : int = 0: - set(_force_type): - force_type = _force_type - _on_input_type_changed(ControllerIcons._last_input_type) - -@export var max_width : int = 40: - set(_max_width): - max_width = _max_width - if is_inside_tree(): - if max_width < 0: - expand_mode = TextureRect.EXPAND_KEEP_SIZE - else: - expand_mode = TextureRect.EXPAND_IGNORE_SIZE - custom_minimum_size.x = max_width - if texture: - custom_minimum_size.y = texture.get_height() * max_width / texture.get_width() - else: - custom_minimum_size.y = custom_minimum_size.x - - -func _ready(): - ControllerIcons.input_type_changed.connect(_on_input_type_changed) - self.path = path - self.max_width = max_width - - -func _on_input_type_changed(input_type): - if show_only == 0 or \ - (show_only == 1 and input_type == ControllerIcons.InputType.KEYBOARD_MOUSE) or \ - (show_only == 2 and input_type == ControllerIcons.InputType.CONTROLLER): - visible = true - self.path = path - else: - visible = false diff --git a/extensions/Cargo.lock b/extensions/Cargo.lock index 278aa620..963fe73b 100644 --- a/extensions/Cargo.lock +++ b/extensions/Cargo.lock @@ -522,7 +522,7 @@ dependencies = [ [[package]] name = "gamescope-x11-client" version = "0.1.0" -source = "git+https://github.com/ShadowBlip/gamescope-x11-client?branch=main#3a0cbe64ba60dffb5ad85f156101c056f764e659" +source = "git+https://github.com/ShadowBlip/gamescope-x11-client?branch=main#deeab5be067bfbb2add2446d8c7fbcbeba7c8c7f" dependencies = [ "log", "strum", @@ -593,7 +593,7 @@ checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" [[package]] name = "godot" version = "0.1.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#6e46fa49bd4b434ebb77e76bcd2e9d5a53ad60b7" +source = "git+https://github.com/godot-rust/gdext?branch=master#8ad9cb0ea877ccf7d4f34b17d87f0a1eb7c47f56" dependencies = [ "godot-core", "godot-macros", @@ -602,7 +602,7 @@ dependencies = [ [[package]] name = "godot-bindings" version = "0.1.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#6e46fa49bd4b434ebb77e76bcd2e9d5a53ad60b7" +source = "git+https://github.com/godot-rust/gdext?branch=master#8ad9cb0ea877ccf7d4f34b17d87f0a1eb7c47f56" dependencies = [ "gdextension-api", ] @@ -610,12 +610,12 @@ dependencies = [ [[package]] name = "godot-cell" version = "0.1.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#6e46fa49bd4b434ebb77e76bcd2e9d5a53ad60b7" +source = "git+https://github.com/godot-rust/gdext?branch=master#8ad9cb0ea877ccf7d4f34b17d87f0a1eb7c47f56" [[package]] name = "godot-codegen" version = "0.1.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#6e46fa49bd4b434ebb77e76bcd2e9d5a53ad60b7" +source = "git+https://github.com/godot-rust/gdext?branch=master#8ad9cb0ea877ccf7d4f34b17d87f0a1eb7c47f56" dependencies = [ "godot-bindings", "heck 0.5.0", @@ -628,7 +628,7 @@ dependencies = [ [[package]] name = "godot-core" version = "0.1.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#6e46fa49bd4b434ebb77e76bcd2e9d5a53ad60b7" +source = "git+https://github.com/godot-rust/gdext?branch=master#8ad9cb0ea877ccf7d4f34b17d87f0a1eb7c47f56" dependencies = [ "glam", "godot-bindings", @@ -640,7 +640,7 @@ dependencies = [ [[package]] name = "godot-ffi" version = "0.1.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#6e46fa49bd4b434ebb77e76bcd2e9d5a53ad60b7" +source = "git+https://github.com/godot-rust/gdext?branch=master#8ad9cb0ea877ccf7d4f34b17d87f0a1eb7c47f56" dependencies = [ "gensym", "godot-bindings", @@ -652,7 +652,7 @@ dependencies = [ [[package]] name = "godot-macros" version = "0.1.3" -source = "git+https://github.com/godot-rust/gdext?branch=master#6e46fa49bd4b434ebb77e76bcd2e9d5a53ad60b7" +source = "git+https://github.com/godot-rust/gdext?branch=master#8ad9cb0ea877ccf7d4f34b17d87f0a1eb7c47f56" dependencies = [ "godot-bindings", "markdown", @@ -865,9 +865,12 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "opengamepadui-core" @@ -968,6 +971,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1101,9 +1110,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -1113,9 +1122,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -1124,9 +1133,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rend" @@ -1386,9 +1395,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", diff --git a/extensions/core/Cargo.toml b/extensions/core/Cargo.toml index 6574474c..ab79849d 100644 --- a/extensions/core/Cargo.toml +++ b/extensions/core/Cargo.toml @@ -13,7 +13,7 @@ godot = { git = "https://github.com/godot-rust/gdext", branch = "master", featur "register-docs", ] } nix = { version = "0.29.0", features = ["term", "process"] } -once_cell = "1.19.0" +once_cell = "1.20.1" tokio = { version = "1.39.3", features = ["full"] } zbus = "4.4.0" zvariant = "4.2.0" diff --git a/extensions/core/src/dbus/bluez/device1.rs b/extensions/core/src/dbus/bluez/device1.rs index 06c5300d..1006f9c6 100644 --- a/extensions/core/src/dbus/bluez/device1.rs +++ b/extensions/core/src/dbus/bluez/device1.rs @@ -28,12 +28,14 @@ trait Device1 { fn connect(&self) -> zbus::Result<()>; /// ConnectProfile method + #[allow(non_snake_case)] fn connect_profile(&self, UUID: &str) -> zbus::Result<()>; /// Disconnect method fn disconnect(&self) -> zbus::Result<()>; /// DisconnectProfile method + #[allow(non_snake_case)] fn disconnect_profile(&self, UUID: &str) -> zbus::Result<()>; /// Pair method diff --git a/extensions/core/src/gamescope/x11_client.rs b/extensions/core/src/gamescope/x11_client.rs index df43b6cd..fb312f46 100644 --- a/extensions/core/src/gamescope/x11_client.rs +++ b/extensions/core/src/gamescope/x11_client.rs @@ -80,6 +80,9 @@ pub struct GamescopeXWayland { /// Current manually focused window #[var(get = get_baselayer_window, set = set_baselayer_window)] baselayer_window: u32, + /// Current manually focused app + #[var(get = get_baselayer_app, set = set_baselayer_app)] + baselayer_app: u32, } #[godot_api] @@ -98,22 +101,25 @@ impl GamescopeXWayland { fn window_property_updated(window_id: u32, property: GString); #[signal] - fn focused_app_updated(); + fn focused_app_updated(from: u32, to: u32); #[signal] - fn focused_app_gfx_updated(); + fn focused_app_gfx_updated(from: u32, to: u32); #[signal] - fn focusable_apps_updated(); + fn focusable_apps_updated(from: PackedInt64Array, to: PackedInt64Array); #[signal] - fn focused_window_updated(); + fn focused_window_updated(from: u32, to: u32); #[signal] - fn focusable_windows_updated(); + fn focusable_windows_updated(from: PackedInt64Array, to: PackedInt64Array); #[signal] - fn baselayer_window_updated(); + fn baselayer_window_updated(from: u32, to: u32); + + #[signal] + fn baselayer_app_updated(from: u32, to: u32); /// Create a new [GamescopeXWayland] with the given name (e.g. ":0") pub fn from_name(name: GString) -> Gd { @@ -189,6 +195,7 @@ impl GamescopeXWayland { blur_radius: Default::default(), allow_tearing: Default::default(), baselayer_window: Default::default(), + baselayer_app: Default::default(), } }) } @@ -895,6 +902,47 @@ impl GamescopeXWayland { self.baselayer_window = 0; } + /// Returns the app id of the currently manually focused app + #[func] + fn get_baselayer_app(&mut self) -> u32 { + if !self.is_primary { + godot_error!("XWayland instance is not primary!"); + return Default::default(); + } + let value = match self.xwayland.get_baselayer_app_id() { + Ok(value) => value, + Err(e) => { + godot_error!("Failed to get baselayer app id: {e:?}"); + return Default::default(); + } + }; + + self.baselayer_window = value.unwrap_or_default(); + self.baselayer_window + } + + /// Focuses the app with the given app id + #[func] + fn set_baselayer_app(&mut self, app_id: u32) { + if !self.is_primary { + godot_error!("XWayland instance is not primary!"); + return; + } + if let Err(e) = self.xwayland.set_baselayer_app_id(app_id) { + godot_error!("Failed to set baselayer app id to {app_id}: {e:?}"); + } + self.baselayer_window = 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() { + godot_error!("Failed to remove baselayer app: {e:?}"); + } + self.baselayer_window = 0; + } + /// Request a screenshot from Gamescope #[func] fn request_screenshot(&self) { @@ -988,6 +1036,14 @@ impl GamescopeXWayland { &[from.to_variant(), to.to_variant()], ); } + property if property == GamescopeAtom::BaselayerAppId.to_string() => { + let from = self.baselayer_app; + let to = self.get_baselayer_app(); + self.base_mut().emit_signal( + "baselayer_app_updated".into(), + &[from.to_variant(), to.to_variant()], + ); + } _ => { // Unknown prop changed } diff --git a/extensions/core/src/input/inputplumber/composite_device.rs b/extensions/core/src/input/inputplumber/composite_device.rs index b278c66a..f9f7b741 100644 --- a/extensions/core/src/input/inputplumber/composite_device.rs +++ b/extensions/core/src/input/inputplumber/composite_device.rs @@ -7,7 +7,7 @@ use crate::dbus::DBusVariant; use crate::get_dbus_system_blocking; use super::dbus_device::DBusDevice; -use super::{InputPlumberInstance, INPUT_PLUMBER_BUS}; +use super::INPUT_PLUMBER_BUS; #[derive(GodotClass)] #[class(no_init, base=Resource)] diff --git a/extensions/core/src/system/command.rs b/extensions/core/src/system/command.rs index 72a783a2..e6f77648 100644 --- a/extensions/core/src/system/command.rs +++ b/extensions/core/src/system/command.rs @@ -1,6 +1,6 @@ -use std::sync::mpsc::Receiver; - -use godot::prelude::*; +//use std::sync::mpsc::Receiver; +// +//use godot::prelude::*; //// Signals that can be emitted //#[derive(Debug)]