diff --git a/Cargo.lock b/Cargo.lock
index 99a2064e..7b6b2dae 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -429,6 +429,7 @@ dependencies = [
"png",
"puffin",
"puffin_egui",
+ "rand",
"regex",
"renderdoc",
"ron 0.7.1",
diff --git a/Cargo.toml b/Cargo.toml
index db68d7e1..b3438f44 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -40,6 +40,7 @@ tracing-journald = "0.3.0"
tracing = { version = "0.1.37", features = ["max_level_debug", "release_max_level_info"] }
puffin = { version = "0.14.3", optional = true }
puffin_egui = { version = "0.19.2", optional = true }
+rand = "0.8.5"
[dependencies.smithay]
version = "0.3"
diff --git a/resources/protocols/ext-foreign-toplevel-list-v1.xml b/resources/protocols/ext-foreign-toplevel-list-v1.xml
new file mode 100644
index 00000000..8152e516
--- /dev/null
+++ b/resources/protocols/ext-foreign-toplevel-list-v1.xml
@@ -0,0 +1,229 @@
+
+
+
+ Copyright © 2018 Ilia Bozhinov
+ Copyright © 2020 Isaac Freund
+ Copyright © 2022 wb9688
+ Copyright © 2023 i509VCB
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+
+
+
+ The purpose of this protocol is to provide protocol object handles for
+ toplevels, possibly originating from another client.
+
+ This protocol is intentionally minimalistic and expects additional
+ functionality (e.g. creating a screencopy source from a toplevel handle,
+ getting information about the state of the toplevel) to be implemented
+ in extension protocols.
+
+ The compositor may choose to restrict this protocol to a special client
+ launched by the compositor itself or expose it to all clients,
+ this is compositor policy.
+
+ The key words "must", "must not", "required", "shall", "shall not",
+ "should", "should not", "recommended", "may", and "optional" in this
+ document are to be interpreted as described in IETF RFC 2119.
+
+ Warning! The protocol described in this file is currently in the testing
+ phase. Backward compatible changes may be added together with the
+ corresponding interface version bump. Backward incompatible changes can
+ only be done by creating a new major version of the extension.
+
+
+
+
+ A toplevel is defined as a surface with a role similar to xdg_toplevel.
+ XWayland surfaces may be treated like toplevels in this protocol.
+
+ After a client binds the ext_foreign_toplevel_list_v1, each mapped
+ toplevel window will be sent using the ext_foreign_toplevel_list_v1.toplevel
+ event.
+
+ Clients which only care about the current state can perform a roundtrip after
+ binding this global.
+
+ For each client instantiation of the ext_foreign_toplevel_list_v1 global,
+ the compositor must create a new ext_foreign_toplevel_handle_v1 object for
+ each mapped toplevel for that instance of the ext_foreign_toplevel_list_v1.
+ Compositors must not send existing ext_foreign_toplevel_handle_v1 objects
+ that were created from some other global instance.
+
+ If a compositor implementation sends the ext_foreign_toplevel_list_v1.finished
+ event after the global is bound, the compositor must not send any
+ ext_foreign_toplevel_list_v1.toplevel events.
+
+
+
+
+ These errors are sent in response to ext_foreign_toplevel_list_v1 requests.
+
+
+
+
+
+
+
+ This event is emitted whenever a new toplevel window is created. It is
+ emitted for all toplevels, regardless of the app that has created them.
+
+ All initial properties of the toplevel (title, app_id) will be sent
+ immediately after this event via the corresponding events in
+ ext_foreign_toplevel_handle_v1. The compositor will use the
+ ext_foreign_toplevel_handle_v1.done event to indicate when all data has
+ been sent.
+
+
+
+
+
+
+ This event indicates that the compositor is done sending events
+ to this object. The client should should destroy the object.
+ See ext_foreign_toplevel_list_v1.destroy for more information.
+
+ The compositor must not send any more toplevel events after this event.
+
+
+
+
+
+ This request indicates that the client no longer wishes to receive
+ events for new toplevels.
+
+ The Wayland protocol is asynchronous, meaning the compositor may send
+ further toplevel events until the stop request is processed.
+ The client should wait for a ext_foreign_toplevel_list_v1.finished
+ event before destroying this object.
+
+
+
+
+
+ This request should be called either when the client will no longer
+ use the ext_foreign_toplevel_list_v1 or after the finished event
+ has been received to allow destruction of the object.
+
+ Destroying a ext_foreign_toplevel_list_v1 while there are toplevels still
+ alive created by this ext_foreign_toplevel_list_v1 object is illegal and
+ must result in a defunct_toplevels error. If a client wishes to destroy
+ this object it should send a ext_foreign_toplevel_list_v1.stop request
+ and wait for a ext_foreign_toplevel_list_v1.finished event, then destroy
+ the handles and then this object.
+
+
+
+
+
+
+ A ext_foreign_toplevel_handle_v1 object represents a mapped toplevel
+ window. A single app may have multiple mapped toplevels.
+
+
+
+
+ This request should be used when the client will no longer use the handle
+ or after the closed event has been received to allow destruction of the
+ object.
+
+ When a handle is destroyed, a new handle may not be created by the server
+ until the toplevel is unmapped and then remapped. Destroying a toplevel handle
+ is not recommended unless the client is cleaning up child objects
+ before destroying the ext_foreign_toplevel_list_v1 object, the toplevel
+ was closed or the toplevel will not be used in the future.
+
+ Other protocols which extend the ext_foreign_toplevel_handle_v1
+ interface should require destructors are called before allowing the
+ toplevel handle to be destroyed.
+
+
+
+
+
+ The server will emit no further events on the ext_foreign_toplevel_handle_v1
+ after this event. Any requests received aside from the destroy request must
+ be ignored. Upon receiving this event, the client should destroy the handle.
+
+ Other protocols which extend the ext_foreign_toplevel_handle_v1
+ interface must also ignore requests other than destructors.
+
+
+
+
+
+ This event is sent after all changes in the toplevel state have
+ been sent.
+
+ This allows changes to the ext_foreign_toplevel_handle_v1 properties
+ to be atomically applied. Other protocols which extend the
+ ext_foreign_toplevel_handle_v1 interface may use this event to also
+ atomically apply any pending state.
+
+ This event must not be sent after the ext_foreign_toplevel_handle_v1.closed
+ event.
+
+
+
+
+
+ The title of the toplevel has changed.
+
+ The configured state must not be applied immediately. See
+ ext_foreign_toplevel_handle_v1.done for details.
+
+
+
+
+
+
+ The app id of the toplevel has changed.
+
+ The configured state must not be applied immediately. See
+ ext_foreign_toplevel_handle_v1.done for details.
+
+
+
+
+
+
+ This identifier is used to check if two or more toplevel handles belong
+ to the same toplevel.
+
+ The identifier is useful for command line tools or privileged clients
+ which may need to reference an exact toplevel across processes or
+ instances of the ext_foreign_toplevel_list_v1 global.
+
+ The compositor must only send this event when the handle is created.
+
+ Compositors must ensure the identifier is the same for all handles of the
+ toplevel. The identifier is only valid as long as the toplevel is mapped.
+ If the toplevel is unmapped and then remapped in the future, the identifier
+ must change.
+
+ The identifier is a string that contains up to 32 printable bytes.
+
+
+
+
+
diff --git a/src/shell/layout/floating/grabs/moving.rs b/src/shell/layout/floating/grabs/moving.rs
index 047150bb..da21c43f 100644
--- a/src/shell/layout/floating/grabs/moving.rs
+++ b/src/shell/layout/floating/grabs/moving.rs
@@ -239,6 +239,16 @@ impl MoveSurfaceGrab {
.common
.shell
.toplevel_info_state
+ .toplevel_enter_workspace(&window, &workspace_handle);
+ state
+ .common
+ .shell
+ .toplevel_info_state
+ .toplevel_enter_output(&window, &output);
+ state
+ .common
+ .shell
+ .foreign_toplevel_info_state
.toplevel_enter_output(&window, &output);
}
diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs
index 2c3499ba..cfd1442c 100644
--- a/src/shell/layout/floating/mod.rs
+++ b/src/shell/layout/floating/mod.rs
@@ -21,7 +21,7 @@ use crate::{
},
state::State,
utils::prelude::*,
- wayland::protocols::toplevel_info::ToplevelInfoState,
+ wayland::protocols::{ext_foreign_toplevel_list, toplevel_info::ToplevelInfoState},
};
mod grabs;
@@ -45,6 +45,10 @@ impl FloatingLayout {
&mut self,
output: &Output,
toplevel_info: &mut ToplevelInfoState,
+ foreign_toplevel_info: &mut ext_foreign_toplevel_list::ToplevelInfoState<
+ State,
+ CosmicSurface,
+ >,
) {
let windows = self
.space
@@ -54,6 +58,7 @@ impl FloatingLayout {
for window in &windows {
for (toplevel, _) in window.windows() {
toplevel_info.toplevel_leave_output(&toplevel, output);
+ foreign_toplevel_info.toplevel_leave_output(&toplevel, output);
}
}
self.space.unmap_output(output);
@@ -62,6 +67,7 @@ impl FloatingLayout {
for output in self.space.outputs_for_element(&window) {
for (toplevel, _) in window.windows() {
toplevel_info.toplevel_enter_output(&toplevel, &output);
+ foreign_toplevel_info.toplevel_enter_output(&toplevel, &output);
}
}
}
diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs
index f157c310..61776889 100644
--- a/src/shell/layout/tiling/mod.rs
+++ b/src/shell/layout/tiling/mod.rs
@@ -14,7 +14,8 @@ use crate::{
},
utils::prelude::*,
wayland::{
- handlers::xdg_shell::popup::get_popup_toplevel, protocols::toplevel_info::ToplevelInfoState,
+ handlers::xdg_shell::popup::get_popup_toplevel,
+ protocols::{ext_foreign_toplevel_list, toplevel_info::ToplevelInfoState},
},
};
@@ -282,6 +283,10 @@ impl TilingLayout {
&mut self,
output: &Output,
toplevel_info: &mut ToplevelInfoState,
+ foreign_toplevel_info: &mut ext_foreign_toplevel_list::ToplevelInfoState<
+ State,
+ CosmicSurface,
+ >,
) {
if let Some(src) = self.trees.remove(output) {
// TODO: expects last remaining output
@@ -304,6 +309,8 @@ impl TilingLayout {
for (toplevel, _) in mapped.windows() {
toplevel_info.toplevel_leave_output(&toplevel, output);
toplevel_info.toplevel_enter_output(&toplevel, &new_output.output);
+ foreign_toplevel_info.toplevel_leave_output(&toplevel, &output);
+ foreign_toplevel_info.toplevel_enter_output(&toplevel, &new_output.output);
}
mapped.output_leave(output);
mapped.output_enter(&new_output.output, mapped.bbox());
diff --git a/src/shell/mod.rs b/src/shell/mod.rs
index 0ff4b8e6..9107c31d 100644
--- a/src/shell/mod.rs
+++ b/src/shell/mod.rs
@@ -30,12 +30,15 @@ use smithay::{
use crate::{
config::{Config, OutputConfig, WorkspaceMode as ConfigMode},
utils::prelude::*,
- wayland::protocols::{
- toplevel_info::ToplevelInfoState,
- toplevel_management::{ManagementCapabilities, ToplevelManagementState},
- workspace::{
- WorkspaceCapabilities, WorkspaceGroupHandle, WorkspaceHandle, WorkspaceState,
- WorkspaceUpdateGuard,
+ wayland::{
+ protocols::ext_foreign_toplevel_list,
+ protocols::{
+ toplevel_info::ToplevelInfoState,
+ toplevel_management::{ManagementCapabilities, ToplevelManagementState},
+ workspace::{
+ WorkspaceCapabilities, WorkspaceGroupHandle, WorkspaceHandle, WorkspaceState,
+ WorkspaceUpdateGuard,
+ },
},
},
};
@@ -66,6 +69,8 @@ pub struct Shell {
// wayland_state
pub layer_shell_state: WlrLayerShellState,
pub toplevel_info_state: ToplevelInfoState,
+ pub foreign_toplevel_info_state:
+ ext_foreign_toplevel_list::ToplevelInfoState,
pub toplevel_management_state: ToplevelManagementState,
pub xdg_shell_state: XdgShellState,
pub workspace_state: WorkspaceState,
@@ -456,6 +461,13 @@ impl Shell {
//|client| client.get_data::().map_or(false, |s| s.privileged),
|_| true,
);
+
+ let foreign_toplevel_info_state = ext_foreign_toplevel_list::ToplevelInfoState::new(
+ dh,
+ //|client| client.get_data::().map_or(false, |s| s.privileged),
+ |_| true,
+ );
+
let toplevel_management_state = ToplevelManagementState::new::(
dh,
vec![
@@ -492,6 +504,7 @@ impl Shell {
layer_shell_state,
toplevel_info_state,
+ foreign_toplevel_info_state,
toplevel_management_state,
xdg_shell_state,
workspace_state,
@@ -593,7 +606,11 @@ impl Shell {
// update mapping
workspace.map_output(new_output, (0, 0).into());
- workspace.unmap_output(output, &mut self.toplevel_info_state);
+ workspace.unmap_output(
+ output,
+ &mut self.toplevel_info_state,
+ &mut self.foreign_toplevel_info_state,
+ );
workspace.refresh();
new_set.workspaces.push(workspace);
@@ -611,7 +628,11 @@ impl Shell {
WorkspaceMode::Global(set) => {
state.remove_group_output(&set.group, output);
for workspace in &mut set.workspaces {
- workspace.unmap_output(output, &mut self.toplevel_info_state);
+ workspace.unmap_output(
+ output,
+ &mut self.toplevel_info_state,
+ &mut self.foreign_toplevel_info_state,
+ );
workspace.refresh();
}
}
@@ -731,6 +752,10 @@ impl Shell {
.toplevel_leave_workspace(&toplevel, &workspace.handle);
self.toplevel_info_state
.toplevel_enter_workspace(&toplevel, &new_workspace.handle);
+ self.foreign_toplevel_info_state
+ .toplevel_leave_workspace(&toplevel, &workspace.handle);
+ self.foreign_toplevel_info_state
+ .toplevel_enter_workspace(&toplevel, &new_workspace.handle);
}
new_workspace.tiling_layer.merge(workspace.tiling_layer);
new_workspace.floating_layer.merge(workspace.floating_layer);
@@ -1071,6 +1096,10 @@ impl Shell {
self.toplevel_info_state
.refresh(Some(&self.workspace_state));
+ self.foreign_toplevel_info_state
+ .refresh(Some(&self.workspace_state));
+ self.foreign_toplevel_info_state
+ .refresh(Some(&self.workspace_state));
}
pub fn map_window(state: &mut State, window: &CosmicSurface, output: &Output) {
@@ -1086,6 +1115,11 @@ impl Shell {
let workspace = state.common.shell.workspaces.active_mut(output);
workspace.set_fullscreen(None, output);
state.common.shell.toplevel_info_state.new_toplevel(&window);
+ state
+ .common
+ .shell
+ .foreign_toplevel_info_state
+ .new_toplevel(&window);
state
.common
.shell
@@ -1096,6 +1130,16 @@ impl Shell {
.shell
.toplevel_info_state
.toplevel_enter_workspace(&window, &workspace.handle);
+ state
+ .common
+ .shell
+ .foreign_toplevel_info_state
+ .toplevel_enter_output(&window, &output);
+ state
+ .common
+ .shell
+ .foreign_toplevel_info_state
+ .toplevel_enter_workspace(&window, &workspace.handle);
let mapped = CosmicMapped::from(CosmicWindow::new(
window.clone(),
@@ -1213,12 +1257,22 @@ impl Shell {
.shell
.toplevel_info_state
.toplevel_leave_workspace(&toplevel, &from_workspace.handle);
+ state
+ .common
+ .shell
+ .foreign_toplevel_info_state
+ .toplevel_leave_workspace(&toplevel, &from_workspace.handle);
if from_output != to_output {
state
.common
.shell
.toplevel_info_state
.toplevel_leave_output(&toplevel, from_output);
+ state
+ .common
+ .shell
+ .foreign_toplevel_info_state
+ .toplevel_leave_output(&toplevel, from_output);
}
}
let elements = from_workspace.mapped().cloned().collect::>();
@@ -1325,6 +1379,16 @@ impl Shell {
.shell
.toplevel_info_state
.toplevel_leave_output(&window, &output);
+ state
+ .common
+ .shell
+ .foreign_toplevel_info_state
+ .toplevel_leave_workspace(&window, &handle);
+ state
+ .common
+ .shell
+ .foreign_toplevel_info_state
+ .toplevel_leave_output(&window, &output);
seat.get_pointer().unwrap().set_grab(
state,
grab,
diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs
index d48d38ec..630a2d93 100644
--- a/src/shell/workspace.rs
+++ b/src/shell/workspace.rs
@@ -9,6 +9,7 @@ use crate::{
wayland::{
handlers::screencopy::{DropableSession, WORKSPACE_OVERVIEW_NAMESPACE},
protocols::{
+ ext_foreign_toplevel_list,
screencopy::{BufferParams, Session as ScreencopySession},
toplevel_info::ToplevelInfoState,
workspace::WorkspaceHandle,
@@ -105,12 +106,18 @@ impl Workspace {
&mut self,
output: &Output,
toplevel_info: &mut ToplevelInfoState,
+ foreign_toplevel_info: &mut ext_foreign_toplevel_list::ToplevelInfoState<
+ State,
+ CosmicSurface,
+ >,
) {
if let Some(dead_output_window) = self.fullscreen.remove(output) {
self.unfullscreen_request(&dead_output_window);
}
- self.tiling_layer.unmap_output(output, toplevel_info);
- self.floating_layer.unmap_output(output, toplevel_info);
+ self.tiling_layer
+ .unmap_output(output, toplevel_info, foreign_toplevel_info);
+ self.floating_layer
+ .unmap_output(output, toplevel_info, foreign_toplevel_info);
self.refresh();
}
diff --git a/src/wayland/handlers/ext_foreign_toplevel_info.rs b/src/wayland/handlers/ext_foreign_toplevel_info.rs
new file mode 100644
index 00000000..f9fe299f
--- /dev/null
+++ b/src/wayland/handlers/ext_foreign_toplevel_info.rs
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-3.0-only
+
+use smithay::utils::user_data::UserDataMap;
+
+use crate::{
+ shell::CosmicSurface,
+ state::State,
+ wayland::protocols::ext_foreign_toplevel_list::{
+ delegate_foreign_toplevel_info, ToplevelInfoHandler, ToplevelInfoState, Window,
+ },
+};
+
+impl ToplevelInfoHandler for State {
+ type Window = CosmicSurface;
+
+ fn toplevel_info_state(&self) -> &ToplevelInfoState {
+ &self.common.shell.foreign_toplevel_info_state
+ }
+ fn toplevel_info_state_mut(&mut self) -> &mut ToplevelInfoState {
+ &mut self.common.shell.foreign_toplevel_info_state
+ }
+}
+
+impl Window for CosmicSurface {
+ fn title(&self) -> String {
+ CosmicSurface::title(self)
+ }
+
+ fn app_id(&self) -> String {
+ CosmicSurface::app_id(self)
+ }
+
+ fn is_activated(&self) -> bool {
+ CosmicSurface::is_activated(self)
+ }
+
+ fn is_maximized(&self) -> bool {
+ CosmicSurface::is_maximized(self)
+ }
+
+ fn is_fullscreen(&self) -> bool {
+ CosmicSurface::is_fullscreen(self)
+ }
+
+ fn is_minimized(&self) -> bool {
+ false // TODO
+ }
+
+ fn user_data(&self) -> &UserDataMap {
+ CosmicSurface::user_data(self)
+ }
+}
+
+delegate_foreign_toplevel_info!(State, CosmicSurface);
diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs
index 56e4a9be..5eceb75f 100644
--- a/src/wayland/handlers/mod.rs
+++ b/src/wayland/handlers/mod.rs
@@ -5,6 +5,7 @@ pub mod compositor;
pub mod data_device;
pub mod decoration;
pub mod dmabuf;
+pub mod ext_foreign_toplevel_info;
pub mod keyboard_shortcuts_inhibit;
pub mod layer_shell;
pub mod output;
diff --git a/src/wayland/protocols/ext_foreign_toplevel_list.rs b/src/wayland/protocols/ext_foreign_toplevel_list.rs
new file mode 100644
index 00000000..423ff412
--- /dev/null
+++ b/src/wayland/protocols/ext_foreign_toplevel_list.rs
@@ -0,0 +1,437 @@
+// SPDX-License-Identifier: GPL-3.0-only
+
+use std::{collections::HashMap, sync::Mutex};
+
+// Re-export only the actual code, and then only use this re-export
+// The `generated` module below is just some boilerplate to properly isolate stuff
+// and avoid exposing internal details.
+//
+// You can use all the types from my_protocol as if they went from `wayland_client::protocol`.
+pub use generated::{ext_foreign_toplevel_handle_v1, ext_foreign_toplevel_list_v1};
+
+mod generated {
+ use smithay::reexports::wayland_server;
+
+ pub mod __interfaces {
+ use wayland_backend;
+ wayland_scanner::generate_interfaces!(
+ "resources/protocols/ext-foreign-toplevel-list-v1.xml"
+ );
+ }
+ use self::__interfaces::*;
+
+ wayland_scanner::generate_server_code!("resources/protocols/ext-foreign-toplevel-list-v1.xml");
+}
+
+use rand::distributions::{Alphanumeric, DistString};
+use smithay::{
+ output::Output,
+ reexports::wayland_server::{
+ protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch,
+ New, Resource,
+ },
+ utils::{user_data::UserDataMap, IsAlive, Logical, Rectangle},
+};
+
+use wayland_backend::server::{ClientId, GlobalId, ObjectId};
+
+use self::{
+ ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1,
+ generated::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
+};
+
+use super::workspace::{WorkspaceHandle, WorkspaceHandler, WorkspaceState};
+
+pub trait Window: IsAlive + Clone + Send {
+ fn title(&self) -> String;
+ fn app_id(&self) -> String;
+ fn is_activated(&self) -> bool;
+ fn is_maximized(&self) -> bool;
+ fn is_fullscreen(&self) -> bool;
+ fn is_minimized(&self) -> bool;
+ fn user_data(&self) -> &UserDataMap;
+}
+
+pub struct ToplevelInfoState {
+ dh: DisplayHandle,
+ pub(super) toplevels: Vec,
+ instances: Vec,
+ global: GlobalId,
+ _dispatch_data: std::marker::PhantomData,
+}
+
+pub trait ToplevelInfoHandler: WorkspaceHandler + Sized {
+ type Window: Window;
+ fn toplevel_info_state(&self) -> &ToplevelInfoState;
+ fn toplevel_info_state_mut(&mut self) -> &mut ToplevelInfoState;
+}
+
+pub struct ToplevelInfoGlobalData {
+ filter: Box Fn(&'a Client) -> bool + Send + Sync>,
+}
+
+#[derive(Default)]
+pub(super) struct ToplevelStateInner {
+ instances: Vec,
+ outputs: Vec