3 Commits

Author SHA1 Message Date
Thomas Colliers
fbf31e1df7 Code style 2025-08-18 18:20:58 +02:00
Thomas Colliers
d1043f7b8c fix: typo 2025-08-17 22:17:41 +02:00
Thomas Colliers
a9e4d91f9a fix: don't lower framerate when window is out of focus 2025-08-17 22:17:41 +02:00
72 changed files with 2145 additions and 3676 deletions

View File

@@ -1,2 +1,2 @@
[build]
rustflags = ["--cfg", "tokio_unstable"]
rustflags = ["--cfg", "tokio_unstable"]

841
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
edition = "2024"
rust-version = "1.89"
rust-version = "1.88"
name = "stardust-xr-server"
version = "0.45.0"
authors = ["Nova King <technobaboo@proton.me>"]
@@ -23,9 +23,8 @@ path = "src/main.rs"
[features]
default = ["wayland"]
wayland = [
"dep:cluFlock",
"dep:waynest",
"dep:waynest-protocols",
"dep:waynest-server",
"dep:tokio-stream",
"dep:memmap2",
"dep:drm-fourcc",
@@ -35,12 +34,7 @@ wayland = [
"dep:ash",
]
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
profile_app = [
"dep:tracing-tracy",
"bevy/trace_tracy",
"bevy/trace",
"dep:tracy-client",
]
profile_app = ["dep:tracing-tracy", "bevy/trace_tracy", "bevy/trace"]
bevy_debugging = ["bevy/bevy_remote", "bevy/track_location"]
[package.metadata.appimage]
@@ -49,19 +43,16 @@ auto_link_exclude_list = ["libc*", "libdl*", "libpthread*", "ld-linux*"]
[profile.release]
opt-level = "s"
lto = true
codegen-units = 1
panic = "abort"
# strip = "symbols"
debug = true
strip = true
opt-level = 3
[profile.dev.package."*"]
opt-level = 3
[patch.crates-io]
bevy_mod_openxr = { git = "https://github.com/awtterpip/bevy_oxr", rev = "9fd0c797" }
bevy_mod_xr = { git = "https://github.com/awtterpip/bevy_oxr", rev = "9fd0c797" }
bevy_mod_openxr = { git = "https://github.com/awtterpip/bevy_oxr" }
bevy_mod_xr = { git = "https://github.com/awtterpip/bevy_oxr" }
bevy_gltf = { git = "https://github.com/Schmarni-Dev/bevy", branch = "gltf_backport" }
# TODO: figure out how to not need this horrifing patch
wgpu = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabuf_workaround" }
@@ -69,15 +60,12 @@ wgpu-types = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabu
wgpu-core = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabuf_workaround" }
naga = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabuf_workaround" }
wgpu-hal = { git = "https://github.com/Schmarni-Dev/wgpu", branch = "bad_dmabuf_workaround" }
bevy_pbr = { git = "https://github.com/Schmarni-Dev/bevy", branch = "premul_oit_016" }
bevy_core_pipeline = { git = "https://github.com/Schmarni-Dev/bevy", branch = "premul_oit_016" }
bevy_render = { git = "https://github.com/Schmarni-Dev/bevy", branch = "render_thread_init_callback" }
[dependencies]
# small utility thingys
nanoid = "0.4.0"
lazy_static = "1.5.0"
rand = "0.9.2"
rand = "0.8.5"
rustc-hash = "2.0.0"
slotmap = "1.0.7"
global_counter = "=0.2.2"
@@ -92,12 +80,12 @@ thiserror = "2.0.11"
tracing = { version = "0.1.40", features = ["release_max_level_warn"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-tracy = { version = "0.11.1", optional = true }
tracy-client = { version = "=0.18.0", optional = true }
tracy-client = { version = "=0.18.0" }
# (de)serialization
serde = { version = "1.0.205", features = ["derive"] }
serde_repr = "0.1.19"
toml = "0.9.7"
toml = "0.8.19"
# mathy stuffs
glam = { version = "0.29.0", features = ["mint", "serde"] }
@@ -118,6 +106,7 @@ bevy = { version = "0.16", default-features = false, features = [
"bevy_window",
"bevy_winit",
"std",
"x11",
"wayland",
"mp3",
"wav",
@@ -125,6 +114,7 @@ bevy = { version = "0.16", default-features = false, features = [
"png",
"hdr",
"jpeg",
"tonemapping_luts",
"multi_threaded",
] }
bevy_mod_xr = "0.3"
@@ -133,7 +123,6 @@ bevy_mod_openxr = "0.3"
bevy_sk = { git = "https://github.com/technobaboo/bevy_sk", branch = "stochastic" }
# bevy-dmabuf = "0.2.0"
bevy-dmabuf.git = "https://github.com/Schmarni-Dev/bevy-dmabuf"
bevy-equirect = "0.1.1"
# bevy-mesh-text-3d.git = "https://github.com/terhechte/bevy-mesh-text-3d"
# use my fork until my pr to use minimal bevy features was merged
@@ -143,33 +132,32 @@ openxr = "0.19"
# linux stuffs
input-event-codes = "6.2.0"
zbus = { version = "5.11.0", features = [
"blocking-api",
"tokio",
], default-features = false }
directories = "6.0.0"
zbus = { version = "5.0.0", default-features = false, features = ["tokio"] }
directories = "5.0.1"
xkbcommon-rs = "0.1.0"
cosmic-text = "0.14.2"
# Wayland
waynest = { git = "https://github.com/verdiwm/waynest.git", default-features = false, optional = true }
waynest-protocols = { git = "https://github.com/verdiwm/waynest.git", features = [
cluFlock = { version = "1.2.7", optional = true } # for the lockfile checking
# waynest = { git = "https://github.com/verdiwm/waynest.git", features = [
# "server",
# "stable",
# "tracing",
# ], default-features = false, optional = true }
waynest = { git = "https://github.com/technobaboo/waynest.git", branch = "fix/wayland-drm", features = [
"server",
"stable",
"mesa",
"external",
"tracing",
], default-features = false, optional = true }
waynest-server = { git = "https://github.com/verdiwm/waynest.git", default-features = false, optional = true }
tokio-stream = { version = "0.1.17", optional = true }
memmap2 = { version = "0.9.5", optional = true }
drm-fourcc = { version = "2.2.0", optional = true }
memfd = { version = "0.6.4", optional = true }
vulkano = { git = "https://github.com/Schmarni-Dev/vulkano", branch = "0_35_dmabuf_fixes", default-features = false, optional = true }
vulkano = { git = "https://github.com/Schmarni-Dev/vulkano", branch = "0_35_dmabuf_fixes", optional = true }
wgpu-hal = { version = "24", optional = true, features = ["vulkan"] }
ash = { version = "0.38.0", optional = true, default-features = false }
rustix = { version = "1.0.8", features = ["time"] }
pin-project-lite = "0.2.16"
futures-sink = "0.3.31"
[dependencies.stardust-xr]
workspace = true

View File

@@ -9,6 +9,8 @@ proc-macro = true
[dependencies]
convert_case = "0.6.0"
quote = "1.0.33"
mint = "0.5.9"
proc-macro2 = "1.0.71"
split-iter = "0.1.0"
stardust-xr = { workspace = true }

View File

@@ -1,6 +1,7 @@
use convert_case::{Case, Casing};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use split_iter::Splittable;
use stardust_xr::schemas::protocol::*;
fn fold_tokens(a: TokenStream, b: TokenStream) -> TokenStream {
@@ -63,7 +64,6 @@ fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
description: protocol.description.clone(),
inherits: vec![],
members: p.members,
inherited_aspects: vec![],
});
quote! {
#node_id
@@ -105,7 +105,7 @@ fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
let aspects = protocol
.aspects
.iter()
.map(|a| generate_aspect(&a.blocking_read()))
.map(generate_aspect)
.reduce(fold_tokens)
.unwrap_or_default();
quote!(#custom_enums #custom_unions #custom_structs #aspects #interface).into()
@@ -183,18 +183,14 @@ fn generate_custom_struct(custom_struct: &CustomStruct) -> TokenStream {
fn generate_aspect(aspect: &Aspect) -> TokenStream {
let description = &aspect.description;
let (client_members, server_members) = aspect
.members
.iter()
.partition::<Vec<_>, _>(|m| m.side == Side::Client);
let (client_members, server_members) = aspect.members.iter().split(|m| m.side == Side::Server);
let client_mod_name = Ident::new(
&format!("{}_client", &aspect.name.to_case(Case::Snake)),
Span::call_site(),
);
let client_side_members = client_members
.into_iter()
.map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Pascal), m))
.map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Snake), m))
.reduce(fold_tokens)
.map(|t| {
// TODO: properly import all dependencies
@@ -232,7 +228,6 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
let alias_info = generate_alias_info(aspect);
let server_side_members = server_members
.into_iter()
.map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Pascal), m))
.reduce(fold_tokens)
.unwrap_or_default();
@@ -286,6 +281,9 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
}
macro_rules! #aspect_macro_name {
() => {
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
self
}
#[allow(clippy::all)]
fn run_signal(
&self,
@@ -306,12 +304,12 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
_node: std::sync::Arc<crate::nodes::Node>,
_method: u64,
_message: crate::nodes::Message,
_method_response: crate::core::scenegraph::MethodResponseSender,
_method_response: crate::nodes::MethodResponseSender,
) {
match _method {
#run_methods
_ => {
let _ = _method_response.send_err(stardust_xr::scenegraph::ScenegraphError::MemberNotFound);
let _ = _method_response.send(Err(stardust_xr::scenegraph::ScenegraphError::MemberNotFound));
}
}
}
@@ -383,18 +381,6 @@ fn generate_member(aspect_id: u64, aspect_name: &str, member: &Member) -> TokenS
}
Side::Client => quote!(_node: &crate::nodes::Node),
};
let arguments = member
.arguments
.iter()
.map(|a| Ident::new(&a.name.to_case(Case::Snake), Span::call_site()));
let argument_debug = member
.arguments
.iter()
.map(|a| Ident::new(&a.name.to_case(Case::Snake), Span::call_site()))
.map(|n| quote!(?#n))
.reduce(|a, b| quote!(#a, #b))
.map(|args| quote!(#args,));
let argument_decls = member
.arguments
.iter()
@@ -419,13 +405,12 @@ fn generate_member(aspect_id: u64, aspect_name: &str, member: &Member) -> TokenS
quote! {
#[doc = #description]
pub fn #name(#argument_decls) -> crate::core::error::Result<()> {
let arguments = (#argument_uses);
let (#(#arguments),*) = &arguments;
::tracing::trace!(#argument_debug "sent signal to client: {}::{}", #aspect_name, #name_str);
let result = stardust_xr::schemas::flex::serialize(&arguments).map_err(|e|e.into()).and_then(|serialized|_node.send_remote_signal(#aspect_id, #opcode, serialized));
let result = stardust_xr::schemas::flex::serialize((#argument_uses)).map_err(|e|e.into()).and_then(|serialized|_node.send_remote_signal(#aspect_id, #opcode, serialized));
if let Err(err) = result.as_ref() {
::tracing::warn!(#argument_debug "failed to send signal to client : {}::{}, error: {}",#aspect_name,#name_str,err);
::tracing::warn!("failed to send remote signal: {}::{}, error: {}",#aspect_name,#name_str,err);
} else {
::tracing::trace!("sent remote signal: {}::{}",#aspect_name,#name_str);
}
result
}
@@ -435,18 +420,11 @@ fn generate_member(aspect_id: u64, aspect_name: &str, member: &Member) -> TokenS
quote! {
#[doc = #description]
pub async fn #name(#argument_decls) -> crate::core::error::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> {
let arguments = (#argument_uses);
let (#(#arguments),*) = &arguments;
::tracing::trace!(#argument_debug "called client method: {}::{}",#aspect_name,#name_str);
let result = _node.execute_remote_method_typed(#aspect_id, #opcode, &arguments, vec![]).await;
match result.as_ref() {
Ok(value) => {
::tracing::trace!(?value, "client method returned value: {}::{}",#aspect_name,#name_str);
},
Err(err) => {
::tracing::warn!(#argument_debug "client method returned error: {}::{}, error: {}",#aspect_name,#name_str,err);
}
let result = _node.execute_remote_method_typed(#aspect_id, #opcode, &(#argument_uses), vec![]).await;
if let Err(err) = result.as_ref() {
::tracing::warn!("failed to call remote method: {}::{}, error: {}",#aspect_name,#name_str,err);
} else {
::tracing::trace!("called remote method: {}::{}",#aspect_name,#name_str);
}
result
}
@@ -469,8 +447,6 @@ fn generate_member(aspect_id: u64, aspect_name: &str, member: &Member) -> TokenS
fn generate_run_member(aspect_name: &Ident, _type: MemberType, member: &Member) -> TokenStream {
let opcode = member.opcode;
let member_name_ident = Ident::new(&member.name, Span::call_site());
let member_name = member_name_ident.to_string();
let aspect_name_str = aspect_name.to_string();
let argument_names = member
.arguments
@@ -485,13 +461,6 @@ fn generate_run_member(aspect_name: &Ident, _type: MemberType, member: &Member)
generate_argument_type(&_type, a.optional, true)
})
.reduce(|a, b| quote!(#a, #b));
let argument_debug = member
.arguments
.iter()
.map(|a| Ident::new(&a.name.to_case(Case::Snake), Span::call_site()))
.map(|n| quote!(?#n))
.reduce(|a, b| quote!(#a, #b))
.map(|args| quote!(#args,));
// dbg!(&argument_types);
let deserialize = argument_names
.clone()
@@ -514,30 +483,33 @@ fn generate_run_member(aspect_name: &Ident, _type: MemberType, member: &Member)
.map(|a| generate_argument_deserialize(&a.name, &a._type, a.optional))
.reduce(|a, b| quote!(#a, #b))
.unwrap_or_default();
let member_name = member_name_ident.to_string();
let aspect_name_str = aspect_name.to_string();
match _type {
MemberType::Signal => quote! {
#opcode => (move || {
#opcode => { let result = (move || {
#deserialize
::tracing::trace!(#argument_debug "received local signal: {}::{}",#aspect_name_str,#member_name);
<Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses)
})().map_err(|e: crate::core::error::ServerError| stardust_xr::scenegraph::ScenegraphError::MemberError { error: e.to_string() }),
})().map_err(|e: crate::core::error::ServerError| stardust_xr::scenegraph::ScenegraphError::MemberError { error: e.to_string() });
if let Err(err) = result.as_ref() {
::tracing::warn!("failed to receive local signal: {}::{}, error: {}",#aspect_name_str,#member_name,err);
} else {
::tracing::trace!("received local signal: {}::{}",#aspect_name_str,#member_name);
}
result
},
},
MemberType::Method => quote! {
#opcode => _method_response.wrap_async(async move {
#deserialize
::tracing::trace!(#argument_debug "called local method: {}::{}",#aspect_name_str,#member_name);
let result = <Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await;
match result.as_ref() {
Ok(value) => {
::tracing::trace!(?value, "client method returned value: {}::{}",#aspect_name_str,#member_name);
},
Err(err) => {
::tracing::warn!("client method returned error: {}::{}, error: {}",#aspect_name_str,#member_name,err);
}
}
let result = result?;
Ok((#serialize, Vec::<std::os::fd::OwnedFd>::new()))
#deserialize
let result = <Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await;
if let Err(err) = result.as_ref() {
::tracing::warn!("failed to call local method: {}::{}, error: {}",#aspect_name_str,#member_name,err);
} else {
::tracing::trace!("called local method: {}::{}",#aspect_name_str,#member_name);
};
let result = result?;
Ok((#serialize, Vec::<std::os::fd::OwnedFd>::new()))
}),
},
}

51
flake.lock generated
View File

@@ -26,11 +26,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1754487366,
"narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=",
"lastModified": 1722555600,
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18",
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"type": "github"
},
"original": {
@@ -47,11 +47,11 @@
]
},
"locked": {
"lastModified": 1754487366,
"narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=",
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
@@ -65,11 +65,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1725868201,
"narHash": "sha256-rDBQ9tXQCCA7emikSYH59ADJELE2IpzB7eoLrpHYzU4=",
"lastModified": 1713050562,
"narHash": "sha256-m7c6XpmpTM1URuqMG2KqtaWbL2Vt8vJFJtmvq123BmY=",
"owner": "StardustXR",
"repo": "flatland",
"rev": "0914dd3df54a5e6258dfc0a02d65af1c0fc0fc90",
"rev": "b3b0f29c4ea1b82c96cf9de507837bf15a5e4c0e",
"type": "github"
},
"original": {
@@ -84,11 +84,11 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1755233722,
"narHash": "sha256-AavrbMltJKcC2Fx0lfJoZfmy7g87ebXU0ddVenhajLA=",
"lastModified": 1719226092,
"narHash": "sha256-YNkUMcCUCpnULp40g+svYsaH1RbSEj6s4WdZY/SHe38=",
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"rev": "99e03e72e3f7e13506f80ef9ebaedccb929d84d0",
"rev": "11e4b8dc112e2f485d7c97e1cee77f9958f498f5",
"type": "github"
},
"original": {
@@ -115,26 +115,23 @@
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1753579242,
"narHash": "sha256-zvaMGVn14/Zz8hnp4VWT9xVnhc8vuL3TStRqwk22biA=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "0f36c44e01a6129be94e3ade315a5883f0228a6e",
"type": "github"
"lastModified": 1722555339,
"narHash": "sha256-uFf2QeW7eAHlYXuDktm9c25OxOyCoUOQmh5SZ9amE5Q=",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1755027561,
"narHash": "sha256-IVft239Bc8p8Dtvf7UAACMG5P3ZV+3/aO28gXpGtMXI=",
"lastModified": 1713714899,
"narHash": "sha256-+z/XjO3QJs5rLE5UOf015gdVauVRQd2vZtsFkaXBq2Y=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "005433b926e16227259a1843015b5b2b7f7d1fc3",
"rev": "6143fc5eeb9c4f00163267708e26191d1e918932",
"type": "github"
},
"original": {
@@ -146,11 +143,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1755186698,
"narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=",
"lastModified": 1723991338,
"narHash": "sha256-Grh5PF0+gootJfOJFenTTxDTYPidA3V28dqJ/WV7iis=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c",
"rev": "8a3354191c0d7144db9756a74755672387b702ba",
"type": "github"
},
"original": {

View File

@@ -61,11 +61,14 @@
};
overlayAttrs = config.packages;
packages =
let
sk_gpu = pkgs.callPackage ./nix/sk_gpu.nix { };
in
{
default = self'.packages.${name};
gnome-graphical-test = self'.checks.gnome-graphical-test;
"${name}" = pkgs.callPackage ./nix/stardust-xr-server.nix {
inherit name src;
inherit name src sk_gpu;
};
};
apps.default = {

View File

@@ -20,8 +20,8 @@
# Set a nice desktop background that is pleasing to the eyes :3
extraGSettingsOverrides = ''
[org.gnome.desktop.background]
picture-uri='file://${pkgs.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
picture-uri-dark='file://${pkgs.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
picture-uri='file://${pkgs.gnome.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
picture-uri-dark='file://${pkgs.gnome.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
'';
};
displayManager = {
@@ -100,7 +100,7 @@
# Eval API is now internal so Shell needs to run in unsafe mode.
# TODO: improve test driver so that it supports openqa-like manipulation
# that would allow us to drop this mess.
"${pkgs.gnome-shell}/bin/gnome-shell --unsafe-mode"
"${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode"
];
};
};

View File

@@ -1,27 +1,25 @@
{
rustPlatform,
src,
name,
vulkan-loader,
vulkan-headers,
mesa,
xorg,
fontconfig,
libxkbcommon,
libclang,
{ rustPlatform
, src
, name
, libGL
, mesa
, xorg
, fontconfig
, libxkbcommon
, libclang
cmake,
pkg-config,
llvmPackages,
fetchFromGitHub,
libXau,
, cmake
, cpm-cmake
, pkg-config
, llvmPackages
, fetchFromGitHub
, sk_gpu
, libXau
libXdmcp,
stdenv,
lib,
openxr-loader,
wayland,
alsa-lib,
, libXdmcp
, stdenv
, lib
, openxr-loader
}:
rustPlatform.buildRustPackage rec {
@@ -30,27 +28,50 @@ rustPlatform.buildRustPackage rec {
lockFile = (src + "/Cargo.lock");
allowBuiltinFetchGit = true;
};
buildFeatures = [ "local_deps" ];
FORCE_LOCAL_DEPS = true;
CPM_LOCAL_PACKAGES_ONLY = true;
CPM_SOURCE_CACHE = "./build";
CPM_USE_LOCAL_PACKAGES = true;
CPM_DOWNLOAD_ALL = false;
preBuild = ''
substituteInPlace /build/cargo-vendor-dir/bevy_gltf-0.16.1/Cargo.toml \
--replace-fail '[lints]' "" \
--replace-fail 'workspace = true' ""
meshoptimizer = fetchFromGitHub {
owner = "zeux";
repo = "meshoptimizer";
rev = "c21d3be6ddf627f8ca852ba4b6db9903b0557858";
sha256 = "sha256-QCxpM2g8WtYSZHkBzLTJNQ/oHb5j/n9rjaVmZJcCZIA=";
};
basis_universal = fetchFromGitHub {
owner = "BinomialLLC";
repo = "basis_universal";
rev = "900e40fb5d2502927360fe2f31762bdbb624455f";
sha256 = "sha256-zBRAXgG5Fi6+5uPQCI/RCGatY6O4ELuYBoKrPNn4K+8=";
};
DEP_MESHOPTIMIZER_SOURCE = "${meshoptimizer}";
DEP_BASIS_UNIVERSAL_SOURCE = "${basis_universal}";
DEP_SK_GPU_SOURCE = "${sk_gpu}";
postPatch = let libPath = lib.makeLibraryPath [ stdenv.cc.cc.lib ];
in ''
sk=$(echo $cargoDepsCopy/stereokit-rust-*/StereoKit)
mkdir -p $sk/build/cpm
# This is not ideal, the original approach was to fetch the exact cmake
# file version that was wanted from GitHub directly, but at least this way it comes from Nixpkgs.. so meh
cp ${cpm-cmake}/share/cpm/CPM.cmake $sk/build/cpm/CPM_0.38.7.cmake
mkdir -p $sk/sk_gpu
cp -R ${sk_gpu}/* $sk/sk_gpu
chmod -R 755 $sk/sk_gpu/tools/linux_x64/*
export DEP_SK_GPU_SOURCE=$sk/sk_gpu
export LD_LIBRARY_PATH="${stdenv.cc.cc.lib}/lib";
patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \
--set-rpath "${libPath}" \
$sk/sk_gpu/tools/linux_x64/skshaderc
'';
postFixup = ''
patchelf $out/bin/stardust-xr-server --add-rpath ${vulkan-loader}/lib
patchelf $out/bin/stardust-xr-server --add-rpath ${openxr-loader}/lib
patchelf $out/bin/stardust-xr-server --add-rpath ${libxkbcommon}/lib
'';
nativeBuildInputs = [
cmake
pkg-config
llvmPackages.libcxxClang
];
nativeBuildInputs = [ cmake pkg-config llvmPackages.libcxxClang ];
buildInputs = [
vulkan-loader
vulkan-headers
libGL
mesa
xorg.libX11.dev
xorg.libXft
@@ -60,9 +81,6 @@ rustPlatform.buildRustPackage rec {
libXau
libXdmcp
openxr-loader
wayland
alsa-lib
];
LIBCLANG_PATH = "${libclang.lib}/lib";
}

View File

@@ -144,7 +144,12 @@ impl Client {
.unwrap_or_else(|| "??".to_string());
let _ = client.dispatch_join_handle.get_or_init(|| {
task::new(
|| format!("Stardust client \"{exe_printable}\" dispatch, pid={pid_printable}"),
|| {
format!(
"client dispatch pid={} exe={}",
&pid_printable, &exe_printable,
)
},
{
let client = client.clone();
async move {
@@ -161,7 +166,7 @@ impl Client {
});
let _ = client.flush_join_handle.get_or_init(|| {
task::new(
|| format!("Stardust client \"{exe_printable}\" flush, pid={pid_printable}"),
|| format!("client flush pid={} exe={}", &pid_printable, &exe_printable,),
{
let client = client.clone();
async move {

View File

@@ -33,8 +33,7 @@ impl LaunchInfo {
#[derive(Debug, Serialize, Deserialize)]
pub struct ClientStateParsed {
pub launch_info: Option<LaunchInfo>,
#[serde(skip)]
pub data: Option<Vec<u8>>,
pub data: Vec<u8>,
pub root: Mat4,
pub spatial_anchors: FxHashMap<String, Mat4>,
}
@@ -42,7 +41,7 @@ impl ClientStateParsed {
pub fn from_deserialized(client: &Client, state: ClientState) -> Self {
ClientStateParsed {
launch_info: LaunchInfo::from_client(client),
data: state.data,
data: state.data.unwrap_or_default(),
root: Self::spatial_transform(client, state.root).unwrap_or_default(),
spatial_anchors: state
.spatial_anchors
@@ -64,9 +63,7 @@ impl ClientStateParsed {
}
pub fn from_file(file: &Path) -> Option<Self> {
let file_string = std::fs::read_to_string(file).ok()?;
let mut client_state: Self = toml::from_str(&file_string).ok()?;
client_state.data = std::fs::read(file.with_extension("bin")).ok();
Some(client_state)
toml::from_str(&file_string).ok()
}
pub fn to_file(&self, directory: &Path) {
let app_name = self
@@ -74,14 +71,11 @@ impl ClientStateParsed {
.as_ref()
.map(|l| l.cmdline.first().unwrap().split('/').next_back().unwrap())
.unwrap_or("unknown");
let state_file_prefix = directory.join(format!("{app_name}-{}", nanoid::nanoid!()));
let state_metadata_path = state_file_prefix.with_extension("toml");
let state_data_path = state_file_prefix.with_extension("bin");
let state_file_path = directory
.join(format!("{app_name}-{}", nanoid::nanoid!()))
.with_extension("toml");
std::fs::write(state_metadata_path, toml::to_string(&self).unwrap()).unwrap();
if let Some(data) = self.data.as_deref() {
std::fs::write(state_data_path, data).unwrap();
}
std::fs::write(state_file_path, toml::to_string(&self).unwrap()).unwrap();
}
pub fn apply_to(&self, client: &Arc<Client>) -> ClientState {
@@ -89,7 +83,7 @@ impl ClientStateParsed {
root.set_transform(self.root)
}
ClientState {
data: self.data.clone(),
data: Some(self.data.clone()),
root: 0,
spatial_anchors: self
.spatial_anchors
@@ -117,7 +111,7 @@ impl Default for ClientStateParsed {
fn default() -> Self {
Self {
launch_info: None,
data: None,
data: Default::default(),
root: Mat4::IDENTITY,
spatial_anchors: Default::default(),
}

View File

@@ -3,8 +3,10 @@ use stardust_xr::values::color::{AlphaColor, Rgb, color_space::LinearRgb};
pub trait ColorConvert {
fn to_bevy(&self) -> bevy::color::Color;
}
// even tho its supposed to be linear the values have to be interpreted as Srgba to produce the
// correct result because StereoKit used Srgba while it was assumed that is uses linear rgba
impl ColorConvert for AlphaColor<f32, Rgb<f32, LinearRgb>> {
fn to_bevy(&self) -> bevy::color::Color {
bevy::color::Color::linear_rgba(self.c.r, self.c.g, self.c.b, self.a)
bevy::color::Color::srgba(self.c.r, self.c.g, self.c.b, self.a)
}
}

View File

@@ -1,10 +1,5 @@
use std::ops::Deref;
use std::sync::Arc;
use bevy::prelude::*;
use crate::nodes::spatial::SpatialNode;
use super::bevy_channel::{BevyChannel, BevyChannelReader};
pub struct EntityHandlePlugin;
@@ -15,46 +10,24 @@ impl Plugin for EntityHandlePlugin {
}
}
fn despawn(
mut cmds: Commands,
mut reader: ResMut<BevyChannelReader<Entity>>,
child_query: Query<&Children>,
has_spatial: Query<Has<SpatialNode>>,
) {
fn despawn(mut cmds: Commands, mut reader: ResMut<BevyChannelReader<Entity>>) {
while let Some(e) = reader.read() {
if let Ok(children) = child_query.get(e) {
for e in children {
if has_spatial.get(*e).unwrap_or_default() {
cmds.entity(*e).try_remove::<ChildOf>();
}
}
}
cmds.entity(e).despawn();
cmds.entity(e).try_despawn();
}
}
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct EntityHandle(Arc<EntityHandleInner>);
impl EntityHandle {
pub fn get(&self) -> Entity {
self.0.0
}
pub fn new(entity: Entity) -> Self {
Self(EntityHandleInner(entity).into())
}
}
impl Deref for EntityHandle {
type Target = Entity;
fn deref(&self) -> &Self::Target {
&self.0.0
}
}
static DESTROY: BevyChannel<Entity> = BevyChannel::new();
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct EntityHandleInner(Entity);
impl Drop for EntityHandleInner {
#[derive(Deref, DerefMut, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct EntityHandle(pub Entity);
impl Drop for EntityHandle {
fn drop(&mut self) {
DESTROY.send(self.0);
if DESTROY.send(self.0).is_none() {
error!("Entity Destroy channel not open");
}
}
}
impl From<Entity> for EntityHandle {
fn from(value: Entity) -> Self {
Self(value)
}
}

View File

@@ -28,6 +28,9 @@ pub enum ServerError {
DeserializationError(#[from] DeserializationError),
#[error("Reader error: {0}")]
ReaderError(#[from] ReaderError),
#[cfg(feature = "wayland")]
#[error("Wayland error: {0}")]
WaylandError(waynest::server::Error),
#[error("Aspect {} does not exist for node", 0.to_string())]
NoAspect(TypeId),
#[error("{0}")]

View File

@@ -36,18 +36,18 @@ impl<T: Send + Sync + ?Sized> Registry<T> {
for pair in new.0.iter() {
let (id, entry) = pair.pair();
if let Some(entry) = entry.upgrade()
&& !old.0.contains_key(id)
{
added.push(entry);
if let Some(entry) = entry.upgrade() {
if !old.0.contains_key(id) {
added.push(entry);
}
}
}
for pair in old.0.iter() {
let (id, entry) = pair.pair();
if let Some(entry) = entry.upgrade()
&& !new.0.contains_key(id)
{
removed.push(entry);
if let Some(entry) = entry.upgrade() {
if !new.0.contains_key(id) {
removed.push(entry);
}
}
}
(added, removed)
@@ -143,7 +143,7 @@ impl<T: Send + Sync + ?Sized> OwnedRegistry<T> {
pub const fn new() -> Self {
OwnedRegistry(const_mutex(None))
}
fn lock(&self) -> MappedMutexGuard<'_, FxHashMap<usize, Arc<T>>> {
fn lock(&self) -> MappedMutexGuard<FxHashMap<usize, Arc<T>>> {
MutexGuard::map(self.0.lock(), |r| r.get_or_insert_with(FxHashMap::default))
}
pub fn add(&self, t: T) -> Arc<T>

View File

@@ -1,80 +1,19 @@
use crate::{
core::{
client::Client,
error::{Result, ServerError},
},
nodes::{Message, Node, alias::get_original},
};
use crate::core::error::Result;
use crate::nodes::Node;
use crate::nodes::alias::get_original;
use crate::{core::client::Client, nodes::Message};
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::Serialize;
use stardust_xr::{
messenger::MethodResponse,
scenegraph::{self, ScenegraphError},
schemas::flex::serialize,
};
use std::{
os::fd::OwnedFd,
sync::{Arc, OnceLock, Weak},
};
use stardust_xr::scenegraph;
use stardust_xr::scenegraph::ScenegraphError;
use stardust_xr::schemas::flex::serialize;
use std::future::Future;
use std::os::fd::OwnedFd;
use std::sync::{Arc, OnceLock, Weak};
use tokio::sync::oneshot;
use tracing::{debug, debug_span};
pub struct MethodResponseSender(pub(crate) MethodResponse);
impl MethodResponseSender {
pub fn send_err(self, error: ScenegraphError) {
self.0.send(Err(error));
}
pub fn send<T: Serialize>(self, result: Result<T, ServerError>) {
let data = match result {
Ok(d) => d,
Err(e) => {
self.0.send(Err(ScenegraphError::MemberError {
error: e.to_string(),
}));
return;
}
};
let Ok(serialized) = stardust_xr::schemas::flex::serialize(data) else {
self.0.send(Err(ScenegraphError::MemberError {
error: "Internal: Failed to serialize".to_string(),
}));
return;
};
self.0.send(Ok((&serialized, Vec::<OwnedFd>::new())));
}
pub fn wrap<T: Serialize, F: FnOnce() -> Result<T>>(self, f: F) {
self.send(f())
}
pub fn wrap_async<T: Serialize>(
self,
f: impl Future<Output = Result<(T, Vec<OwnedFd>)>> + Send + 'static,
) {
tokio::task::spawn(async move {
let (value, fds) = match f.await {
Ok(d) => d,
Err(e) => {
self.0.send(Err(ScenegraphError::MemberError {
error: e.to_string(),
}));
return;
}
};
let Ok(serialized) = serialize(value) else {
self.0.send(Err(ScenegraphError::MemberError {
error: "Internal: Failed to serialize".to_string(),
}));
return;
};
self.0.send(Ok((&serialized, fds)));
});
}
}
impl std::fmt::Debug for MethodResponseSender {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TypedMethodResponse").finish()
}
}
#[derive(Default)]
pub struct Scenegraph {
pub(super) client: OnceLock<Weak<Client>>,
@@ -106,6 +45,43 @@ impl Scenegraph {
self.nodes.lock().remove(&node)
}
}
pub type MethodResponse = Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>;
pub struct MethodResponseSender(oneshot::Sender<MethodResponse>);
impl MethodResponseSender {
pub fn send(self, t: Result<Message, ScenegraphError>) {
let _ = self.0.send(t.map(|m| (m.data, m.fds)));
}
// pub fn send_method_return<T: Serialize>(
// self,
// result: color_eyre::eyre::Result<(T, Vec<OwnedFd>)>,
// ) {
// let _ = self.0.send(map_method_return(result));
// }
pub fn wrap_sync<F: FnOnce() -> crate::core::error::Result<Message>>(self, f: F) {
self.send(f().map_err(|e| ScenegraphError::MemberError {
error: e.to_string(),
}))
}
pub fn wrap_async<T: Serialize>(
self,
f: impl Future<Output = Result<(T, Vec<OwnedFd>)>> + Send + 'static,
) {
tokio::task::spawn(async move { self.0.send(map_method_return(f.await)) });
}
}
fn map_method_return<T: Serialize>(
result: Result<(T, Vec<OwnedFd>)>,
) -> Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError> {
let (value, fds) = result.map_err(|e| ScenegraphError::MemberError {
error: e.to_string(),
})?;
let serialized_value = serialize(value).map_err(|e| ScenegraphError::MemberError {
error: format!("Internal: Serialization failed: {e}"),
})?;
Ok((serialized_value, fds))
}
impl scenegraph::Scenegraph for Scenegraph {
fn send_signal(
&self,
@@ -139,15 +115,15 @@ impl scenegraph::Scenegraph for Scenegraph {
method: u64,
data: &[u8],
fds: Vec<OwnedFd>,
response: MethodResponse,
response: oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>,
) {
let Some(client) = self.get_client() else {
response.send(Err(ScenegraphError::NodeNotFound));
let _ = response.send(Err(ScenegraphError::NodeNotFound));
return;
};
debug!(aspect_id, node_id, method, "Handle method");
let Some(node) = self.get_node(node_id) else {
response.send(Err(ScenegraphError::NodeNotFound));
let _ = response.send(Err(ScenegraphError::NodeNotFound));
return;
};
node.execute_local_method(

View File

@@ -1,3 +1,4 @@
use color_eyre::eyre::Result;
use std::future::Future;
use tokio::task::JoinHandle;
@@ -10,7 +11,7 @@ pub fn new<
>(
name_fn: F,
async_future: A,
) -> std::io::Result<JoinHandle<O>> {
) -> Result<JoinHandle<O>> {
#[cfg(not(feature = "profile_tokio"))]
let result = Ok(tokio::task::spawn(async_future));
#[cfg(feature = "profile_tokio")]

View File

@@ -1,4 +1,3 @@
#![recursion_limit = "256"]
#![allow(clippy::empty_docs)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::type_complexity)]
@@ -6,14 +5,11 @@ mod core;
mod nodes;
mod objects;
mod session;
mod spectator_cam;
pub mod tracking_offset;
#[cfg(feature = "wayland")]
mod wayland;
use crate::nodes::drawable::sky::SkyPlugin;
use crate::nodes::input;
use crate::spectator_cam::SpectatorCameraPlugin;
use bevy::MinimalPlugins;
use bevy::a11y::AccessibilityPlugin;
@@ -21,22 +17,17 @@ use bevy::app::{App, ScheduleRunnerPlugin, TerminalCtrlCHandlerPlugin};
use bevy::asset::{AssetMetaCheck, UnapprovedPathMode};
use bevy::audio::AudioPlugin;
use bevy::core_pipeline::CorePipelinePlugin;
use bevy::core_pipeline::oit::OrderIndependentTransparencySettings;
use bevy::core_pipeline::tonemapping::Tonemapping;
use bevy::diagnostic::DiagnosticsPlugin;
use bevy::ecs::schedule::{ExecutorKind, ScheduleLabel};
use bevy::gizmos::GizmoPlugin;
use bevy::gltf::GltfPlugin;
use bevy::input::InputPlugin;
use bevy::pbr::PbrPlugin;
use bevy::render::pipelined_rendering::{
PipelinedRenderThreadOnCreateCallback, PipelinedRenderingPlugin,
};
use bevy::render::settings::{Backends, RenderCreation, WgpuSettings};
use bevy::render::{RenderDebugFlags, RenderPlugin};
use bevy::scene::ScenePlugin;
use bevy::window::{CompositeAlphaMode, PresentMode};
use bevy::winit::{WakeUp, WinitPlugin};
use bevy::winit::{WakeUp, WinitPlugin, WinitSettings, UpdateMode};
use bevy_dmabuf::import::DmabufImportPlugin;
use bevy_mod_openxr::action_set_attaching::OxrActionAttachingPlugin;
use bevy_mod_openxr::action_set_syncing::OxrActionSyncingPlugin;
@@ -70,20 +61,17 @@ use objects::play_space::PlaySpacePlugin;
use openxr::{EnvironmentBlendMode, ReferenceSpaceType};
use session::{launch_start, save_session};
use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
use stardust_xr::server::LockedSocket;
use stardust_xr::server;
use std::ops::DerefMut as _;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::{Arc, OnceLock};
use tokio::net::UnixListener;
use tokio::sync::Notify;
use tokio::task::JoinError;
use tracing::metadata::LevelFilter;
use tracing::{error, info};
use tracing_subscriber::filter::Directive;
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
use tracking_offset::TrackingOffsetPlugin;
#[cfg(feature = "wayland")]
use wayland::{Wayland, WaylandPlugin};
use zbus::Connection;
use zbus::fdo::ObjectManager;
@@ -95,11 +83,7 @@ use bevy::prelude::*;
struct CliArgs {
/// Force flatscreen mode and use the mouse pointer as a 3D pointer
#[clap(short, long, action)]
force_flatscreen: bool,
/// Replaces the flatscreen mode with a first person spectator camera
#[clap(short, long, action)]
spectator: bool,
flatscreen: bool,
/// Creates a transparent window fot the flatscreen mode
#[clap(short, long, action)]
@@ -147,7 +131,9 @@ async fn main() -> Result<AppExit, JoinError> {
);
#[cfg(feature = "profile_tokio")]
let registry = registry.with(console_subscriber::spawn());
let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build();
#[cfg(feature = "profile_tokio")]
let registry = registry.with(console_layer);
let log_layer = fmt::Layer::new()
.with_thread_names(true)
@@ -156,23 +142,22 @@ async fn main() -> Result<AppExit, JoinError> {
.with_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into())
.from_env_lossy()
.add_directive(Directive::from_str("bevy_mesh_text_3d::text_glyphs=off").unwrap()),
.from_env_lossy(),
);
registry.with(log_layer).init();
let cli_args = CliArgs::parse();
let locked_socket =
LockedSocket::get_free().expect("Unable to find a free stardust socket path");
STARDUST_INSTANCE.set(locked_socket.socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell");
let socket_path =
server::get_free_socket_path().expect("Unable to find a free stardust socket path");
STARDUST_INSTANCE.set(socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell");
info!(
socket_path = ?locked_socket.socket_path.display(),
socket_path = ?socket_path.display(),
"Stardust socket created"
);
let socket = UnixListener::bind(locked_socket.socket_path)
.expect("Couldn't spawn stardust server at {socket_path}");
task::new(|| "Stardust socket accept loop", async move {
let socket =
UnixListener::bind(socket_path).expect("Couldn't spawn stardust server at {socket_path}");
task::new(|| "client join loop", async move {
loop {
let Ok((stream, _)) = socket.accept().await else {
continue;
@@ -210,9 +195,10 @@ async fn main() -> Result<AppExit, JoinError> {
.await
.expect("Couldn't add the object manager");
let object_registry = ObjectRegistry::new(&dbus_connection).await;
let object_registry = ObjectRegistry::new(&dbus_connection).await.expect(
"Couldn't make the object registry to find all objects with given interfaces in d-bus",
);
#[cfg(feature = "wayland")]
let _wayland = Wayland::new().expect("Couldn't create Wayland instance");
let ready_notifier = Arc::new(Notify::new());
@@ -255,7 +241,7 @@ async fn main() -> Result<AppExit, JoinError> {
#[derive(ScheduleLabel, Hash, Debug, PartialEq, Eq, Clone, Copy)]
pub struct PreFrameWait;
#[derive(Resource, Deref)]
pub struct ObjectRegistryRes(Arc<ObjectRegistry>);
pub struct ObjectRegistryRes(ObjectRegistry);
#[derive(Resource, Deref)]
pub struct DbusConnection(Connection);
@@ -264,7 +250,7 @@ fn bevy_loop(
_project_dirs: Option<ProjectDirs>,
args: CliArgs,
dbus_connection: Connection,
object_registry: Arc<ObjectRegistry>,
object_registry: ObjectRegistry,
) -> AppExit {
let mut app = App::new();
app.insert_resource(DbusConnection(dbus_connection));
@@ -312,11 +298,8 @@ fn bevy_loop(
// .add(AnimationPlugin)
.add(AudioPlugin::default())
.add(GizmoPlugin)
.add(WindowPlugin::default());
#[cfg(feature = "wayland")]
{
plugins = plugins.add(DmabufImportPlugin);
}
.add(WindowPlugin::default())
.add(DmabufImportPlugin);
let mut task_pool_plugin = TaskPoolPlugin::default();
// make tokio work
let handle = tokio::runtime::Handle::current();
@@ -332,22 +315,19 @@ fn bevy_loop(
.async_compute
.on_thread_spawn = Some(enter_runtime_context.clone());
plugins = plugins.set(task_pool_plugin);
if std::env::var("DISPLAY").is_ok_and(|s| !s.is_empty())
|| std::env::var("WAYLAND_DISPLAY").is_ok_and(|s| !s.is_empty())
{
let flatscreenmode = args.flatscreen
|| std::env::var_os("DISPLAY").is_some_and(|s| !s.is_empty())
|| std::env::var_os("WAYLAND_DISPLAY").is_some_and(|s| !s.is_empty());
if flatscreenmode {
let mut plugin = WinitPlugin::<WakeUp>::default();
plugin.run_on_any_thread = true;
plugins = plugins.add(plugin).disable::<ScheduleRunnerPlugin>();
plugins = match args.spectator {
true => plugins.add(SpectatorCameraPlugin),
false => plugins.add(FlatscreenInputPlugin),
};
plugins = plugins
.add(plugin)
.disable::<ScheduleRunnerPlugin>()
.add(FlatscreenInputPlugin);
}
app.insert_resource(PipelinedRenderThreadOnCreateCallback(
enter_runtime_context.clone(),
));
app.add_plugins(
if !args.force_flatscreen {
if !args.flatscreen {
add_xr_plugins(plugins)
.set(OxrInitPlugin {
app_info: AppInfo {
@@ -397,10 +377,15 @@ fn bevy_loop(
..default()
}),
);
app.add_plugins(PipelinedRenderingPlugin);
if flatscreenmode {
app.insert_resource(WinitSettings {
focused_mode: UpdateMode::Continuous,
unfocused_mode: UpdateMode::Continuous,
});
}
app.add_plugins(bevy_sk::hand::HandPlugin);
app.add_plugins(bevy_equirect::EquirectangularPlugin);
// app.add_plugins(HandGizmosPlugin);
app.world_mut().resource_mut::<AmbientLight>().brightness = 1000.0;
if let Some(priority) = args.overlay_priority {
@@ -437,19 +422,15 @@ fn bevy_loop(
TextNodePlugin,
LinesNodePlugin,
AudioNodePlugin,
// not really a node ig? at least for now
SkyPlugin,
));
// object plugins
app.add_plugins((PlaySpacePlugin, HandPlugin, ControllerPlugin, HmdPlugin));
// feature plugins
#[cfg(feature = "wayland")]
app.add_plugins(WaylandPlugin);
app.add_plugins((TrackingOffsetPlugin, FieldDebugGizmoPlugin));
app.add_plugins((WaylandPlugin, TrackingOffsetPlugin, FieldDebugGizmoPlugin));
app.add_systems(PostStartup, move || {
ready_notifier.notify_waiters();
});
app.add_observer(cam_settings);
app.add_observer(cam_observer);
app.add_systems(
XrFirst,
xr_step
@@ -460,13 +441,11 @@ fn bevy_loop(
app.run()
}
fn cam_settings(
fn cam_observer(
trigger: Trigger<OnAdd, Camera3d>,
mut query: Query<(Entity, &mut Projection, &mut Msaa, &mut Tonemapping), With<Camera3d>>,
mut cmds: Commands,
mut query: Query<(&mut Projection, &mut Msaa), With<Camera3d>>,
) {
let Ok((entity, mut projection, mut msaa, mut tonemapping)) = query.get_mut(trigger.target())
else {
let Ok((mut projection, mut msaa)) = query.get_mut(trigger.target()) else {
return;
};
info!("modifying cam");
@@ -482,9 +461,6 @@ fn cam_settings(
}
}
*msaa = Msaa::Off;
*tonemapping = Tonemapping::None;
cmds.entity(entity)
.insert(OrderIndependentTransparencySettings::default());
}
fn xr_step(world: &mut World) {
@@ -494,6 +470,13 @@ fn xr_step(world: &mut World) {
let time = world.resource::<bevy::prelude::Time>().delta_secs_f64();
nodes::root::Root::send_frame_events(time);
// we are targeting the frame after the wait
if let Some(mut state) = world.get_resource_mut::<OxrFrameState>() {
state.predicted_display_time = openxr::Time::from_nanos(
state.predicted_display_time.as_nanos() + state.predicted_display_period.as_nanos(),
);
}
let should_wait = world
.run_system_cached(should_run_frame_loop)
.unwrap_or(false);
@@ -513,13 +496,3 @@ fn xr_step(world: &mut World) {
tick_internal_client();
}
pub fn get_time(pipelined: bool, state: &OxrFrameState) -> openxr::Time {
if pipelined {
openxr::Time::from_nanos(
state.predicted_display_time.as_nanos() + state.predicted_display_period.as_nanos(),
)
} else {
state.predicted_display_time
}
}

View File

@@ -1,7 +1,5 @@
use super::{Aspect, AspectIdentifier, Node};
use crate::core::{
client::Client, error::Result, registry::Registry, scenegraph::MethodResponseSender,
};
use crate::core::{client::Client, error::Result, registry::Registry};
use std::{
ops::Add,
sync::{Arc, Weak},
@@ -73,6 +71,9 @@ impl AspectIdentifier for Alias {
const ID: u64 = 0;
}
impl Aspect for Alias {
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
self
}
fn run_signal(
&self,
_calling_client: Arc<Client>,
@@ -88,7 +89,7 @@ impl Aspect for Alias {
_node: Arc<Node>,
_method: u64,
_message: super::Message,
_response: MethodResponseSender,
_response: crate::core::scenegraph::MethodResponseSender,
) {
}
}
@@ -121,7 +122,6 @@ impl AliasList {
fn add(&self, node: &Arc<Node>) {
self.0.add_raw(node);
}
#[tracing::instrument(level = "trace", skip_all)]
pub fn get_from_original_node(&self, original: Weak<Node>) -> Option<Arc<Node>> {
self.0
.get_valid_contents()

View File

@@ -57,27 +57,29 @@ fn update_sound_event(
for sound in SOUND_REGISTRY.get_valid_contents() {
if sound.entity.get().is_none() {
let handle = asset_server.load(sound.pending_audio_path.as_path());
let entity = cmds
.spawn((
Name::new("Audio Node"),
SpatialNode(Arc::downgrade(&sound.spatial)),
AudioPlayer::new(handle),
PlaybackSettings {
mode: PlaybackMode::Once,
volume: Volume::Linear(sound.volume),
speed: 1.0,
paused: true,
muted: false,
spatial: true,
spatial_scale: None,
},
))
.id();
let entity = EntityHandle::new(entity);
sound.spatial.set_entity(entity.clone());
sound.entity.set(entity).unwrap();
sound
.entity
.set(
cmds.spawn((
Name::new("Audio Node"),
SpatialNode(Arc::downgrade(&sound.spatial)),
AudioPlayer::new(handle),
PlaybackSettings {
mode: PlaybackMode::Once,
volume: Volume::Linear(sound.volume),
speed: 1.0,
paused: true,
muted: false,
spatial: true,
spatial_scale: None,
},
))
.id()
.into(),
)
.unwrap();
}
if let Some(sink) = sound.entity.get().and_then(|e| sinks.get(e.get()).ok()) {
if let Some(sink) = sound.entity.get().and_then(|e| sinks.get(e.0).ok()) {
if sound.play.lock().take().is_some() {
sink.play();
}

View File

@@ -1,56 +0,0 @@
#import bevy_pbr::{
pbr_fragment::pbr_input_from_standard_material,
pbr_functions::alpha_discard,
}
#ifdef PREPASS_PIPELINE
#import bevy_pbr::{
prepass_io::{VertexOutput, FragmentOutput},
pbr_deferred_functions::deferred_output,
}
#else
#import bevy_pbr::{
forward_io::{VertexOutput, FragmentOutput},
pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing},
}
#endif
#ifdef OIT_ENABLED
#import bevy_core_pipeline::oit::oit_draw
#endif
@fragment
fn fragment(
in: VertexOutput,
@builtin(front_facing) is_front: bool,
) -> FragmentOutput {
// generate a PbrInput struct from the StandardMaterial bindings
var pbr_input = pbr_input_from_standard_material(in, is_front);
#ifdef VERTEX_COLORS
// Multiply emissive color by vertex color
pbr_input.material.emissive *= vec4(in.color.rgb, 1.0);
#endif
// alpha discard
// pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color);
#ifdef PREPASS_PIPELINE
// in deferred mode we can't modify anything after that, as lighting is run in a separate fullscreen shader.
let out = deferred_output(in, pbr_input);
#else
var out: FragmentOutput;
// apply lighting
out.color = apply_pbr_lighting(pbr_input);
// apply in-shader post processing (fog, alpha-premultiply, and also tonemapping, debanding if the camera is non-hdr)
// note this does not include fullscreen postprocessing effects like bloom.
out.color = main_pass_post_lighting_processing(pbr_input, out.color);
#endif
#ifdef OIT_ENABLED
oit_draw(in.position, out.color, false);
discard;
#else
return out;
#endif
}

View File

@@ -5,17 +5,17 @@ use crate::{
client::Client, color::ColorConvert, entity_handle::EntityHandle, error::Result,
registry::Registry,
},
nodes::{Node, drawable::LinePoint, spatial::Spatial},
nodes::{
Node,
spatial::{Spatial, SpatialNode},
},
};
use bevy::{
asset::{AssetEvents, RenderAssetUsages, weak_handle},
pbr::{ExtendedMaterial, MaterialExtension},
asset::RenderAssetUsages,
prelude::*,
render::{
mesh::{Indices, PrimitiveTopology, VertexAttributeValues},
mesh::{Indices, MeshAabb, PrimitiveTopology},
primitives::Aabb,
render_resource::{AsBindGroup, ShaderRef},
view::VisibilitySystems,
},
};
use glam::Vec3;
@@ -24,79 +24,25 @@ use std::sync::{
Arc, OnceLock,
atomic::{AtomicBool, Ordering},
};
use tokio::sync::Notify;
type LineMaterial = ExtendedMaterial<BevyMaterial, LineExtension>;
const LINE_SHADER_HANDLE: Handle<Shader> = weak_handle!("7d28aa5a-3abd-43bb-b0e9-0de8b81b650d");
// No extra data needed for a simple holdout
#[derive(Default, Asset, AsBindGroup, TypePath, Debug, Clone)]
#[data(50, u32, binding_array(101))]
#[bindless(index_table(range(50..51), binding(100)))]
pub struct LineExtension {}
impl From<&LineExtension> for u32 {
fn from(_: &LineExtension) -> Self {
0
}
}
impl MaterialExtension for LineExtension {
fn fragment_shader() -> ShaderRef {
LINE_SHADER_HANDLE.into()
}
fn prepass_fragment_shader() -> ShaderRef {
LINE_SHADER_HANDLE.into()
}
fn deferred_fragment_shader() -> ShaderRef {
LINE_SHADER_HANDLE.into()
}
fn alpha_mode() -> Option<AlphaMode> {
Some(AlphaMode::Blend)
}
}
pub struct LinesNodePlugin;
impl Plugin for LinesNodePlugin {
fn build(&self, app: &mut App) {
app.add_systems(
PostUpdate,
build_line_mesh
.after(TransformSystem::TransformPropagate)
.before(AssetEvents)
.after(VisibilitySystems::VisibilityPropagate)
.before(VisibilitySystems::CheckVisibility),
);
app.world_mut().resource_mut::<Assets<Shader>>().insert(
LINE_SHADER_HANDLE.id(),
Shader::from_wgsl(
include_str!("line.wgsl"),
std::path::Path::new(file!())
.parent()
.unwrap()
.join("line.wgsl")
.to_string_lossy(),
),
);
app.add_plugins(MaterialPlugin::<LineMaterial>::default());
app.add_systems(Update, build_line_mesh);
}
}
fn build_line_mesh(
mut cmds: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<LineMaterial>>,
query: Query<(Ref<GlobalTransform>, &InheritedVisibility)>,
mut cmds: Commands,
mut materials: ResMut<Assets<BevyMaterial>>,
) {
for lines in LINES_REGISTRY.get_valid_contents().into_iter()
for lines in LINES_REGISTRY
.get_valid_contents()
.into_iter()
.filter(|l| l.gen_mesh.load(Ordering::Relaxed))
{
let Some((transform, visibil)) = lines.spatial.get_entity().and_then(|e| query.get(e).ok())
else {
continue;
};
if !(lines.gen_mesh.load(Ordering::Relaxed) || transform.is_changed()) {
continue;
}
lines.gen_mesh.store(false, Ordering::Relaxed);
let mut vertex_positions = Vec::<Vec3>::new();
let mut vertex_normals = Vec::<Vec3>::new();
@@ -104,14 +50,16 @@ fn build_line_mesh(
let mut vertex_indices = Vec::<u32>::new();
let lines_data = lines.data.lock();
if lines_data.is_empty() {
*lines.bounds.lock() = Some(Aabb::default());
lines.setup_complete.notify_waiters();
*lines.bounds.lock() = Aabb::default();
match lines.entity.get() {
Some(e) => cmds.entity(**e),
None => {
// if we couldn't get the lines entity then we need to gen the mesh later
lines.gen_mesh.store(true, Ordering::Relaxed);
continue;
let e = cmds.spawn((
Name::new("LinesNode"),
SpatialNode(Arc::downgrade(&lines.spatial)),
));
_ = lines.entity.set(e.id().into());
e
}
}
.remove::<Mesh3d>();
@@ -120,33 +68,21 @@ fn build_line_mesh(
let mut indices_set = 0;
for line in lines_data.iter() {
// yes this alloc is suboptimal, but good enough for now
let line_points = line
.points
.iter()
.map(|p: &LinePoint| LinePoint {
// point: transform.transform_point(p.point.into()).into(),
point: transform.transform_point(p.point.into()).into(),
thickness: p.thickness,
color: p.color,
})
.collect::<Vec<_>>();
let start_set = indices_set;
// Create a sliding window of points to process each segment of the line
// For cyclic lines: wraps around by connecting last point back to first
// For non-cyclic lines: handles endpoints with None values
let point_windows = {
let mut out = Vec::new();
let mut last = line.cyclic.then(|| line_points.last()).flatten();
let mut peekable = line_points.iter().peekable();
let mut last = line.cyclic.then(|| line.points.last()).flatten();
let mut peekable = line.points.iter().peekable();
while let Some(curr) = peekable.next() {
// Skip this point if it has the same position as the previous point
if let Some(prev) = last
&& Vec3::from(prev.point) == Vec3::from(curr.point)
{
last = Some(curr);
continue;
if let Some(prev) = last {
if Vec3::from(prev.point) == Vec3::from(curr.point) {
last = Some(curr);
continue;
}
}
let mut end = false;
@@ -156,7 +92,7 @@ fn build_line_mesh(
Some(v) => Some(*v),
None => {
end = true;
line.cyclic.then(|| line_points.first()).flatten()
line.cyclic.then(|| line.points.first()).flatten()
}
};
@@ -191,10 +127,6 @@ fn build_line_mesh(
(Some(last), None) => last,
(Some(last), Some(next)) => last.lerp(next, 0.5),
};
if !quat.is_finite() {
error!("non finite quat: next: {next:?}, last: {last:?}, curr: {curr:?},");
break;
}
let normals = [
Vec3::X,
Vec3::new(1., 0., 1.).normalize(),
@@ -206,81 +138,71 @@ fn build_line_mesh(
Vec3::new(1., 0., -1.).normalize(),
]
.map(Vec3::normalize)
.map(|v| quat * v);
.map(|v| (quat * v));
let points = normals.map(|v| (v * curr.thickness) + Vec3::from(curr.point));
vertex_normals.extend(normals);
vertex_positions.extend(points);
vertex_colors.extend([curr.color.to_bevy().to_linear().to_f32_array(); 8]);
vertex_colors.extend([curr.color.to_bevy().to_srgba().to_f32_array(); 8]);
// Only connect vertices between segments if this isn't the end point
if !last_point {
vertex_indices.extend(indices(indices_set));
}
indices_set += 1;
}
if indices_set > 0 {
// Handle the connection between start and end points:
// - For cyclic lines: connect last segment back to first
// - For non-cyclic lines: add caps at both ends
if line.cyclic {
vertex_indices.extend(cyclic_indices(start_set, indices_set - 1));
} else {
vertex_indices.extend(cap_indices(start_set, false));
vertex_indices.extend(cap_indices(indices_set - 1, true));
}
// Handle the connection between start and end points:
// - For cyclic lines: connect last segment back to first
// - For non-cyclic lines: add caps at both ends
if line.cyclic {
vertex_indices.extend(cyclic_indices(start_set, indices_set - 1));
} else {
vertex_indices.extend(cap_indices(start_set, false));
vertex_indices.extend(cap_indices(indices_set - 1, true));
}
}
let mut mesh = Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::RENDER_WORLD,
);
if vertex_colors.iter().flatten().any(|v| !v.is_finite()) {
panic!("vertex colors contains non finite float: {vertex_colors:#?}",);
}
if vertex_normals.iter().any(|v| !v.is_finite()) {
panic!("normals contains non finite dir: {vertex_normals:#?}",);
}
if vertex_normals.iter().any(|v| !v.is_normalized()) {
panic!("normals contains non normalized dir: {vertex_normals:#?}",);
}
if vertex_positions.iter().any(|v| !v.is_finite()) {
panic!("vertex positions contains non finite pos: {vertex_positions:#?}",);
}
mesh.insert_indices(Indices::U32(vertex_indices));
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_positions);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_normals);
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_colors);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_normals);
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_positions.clone());
if let Some(aabb) = mesh.compute_aabb() {
info!(?aabb);
*lines.bounds.lock() = aabb;
}
let mut entity = match lines.entity.get() {
match lines.entity.get() {
Some(e) => cmds.entity(**e),
None => {
let e = cmds.spawn((
Name::new("LinesNode"),
MeshMaterial3d(materials.add(ExtendedMaterial {
base: BevyMaterial {
base_color: Color::WHITE,
perceptual_roughness: 1.0,
alpha_mode: AlphaMode::Premultiplied,
emissive: Color::linear_rgba(0.25, 0.25, 0.25, 1.0).into(),
..default()
},
extension: LineExtension {},
SpatialNode(Arc::downgrade(&lines.spatial)),
MeshMaterial3d(materials.add(BevyMaterial {
base_color: Color::WHITE,
perceptual_roughness: 1.0,
// TODO: this should be Blend
alpha_mode: AlphaMode::Opaque,
..default()
})),
));
_ = lines.entity.set(EntityHandle::new(e.id()));
_ = lines.entity.set(e.id().into());
e
}
};
if let Some(VertexAttributeValues::Float32x3(values)) =
mesh.attribute(Mesh::ATTRIBUTE_POSITION)
{
let global_to_local = transform.affine().inverse();
let local_aabb = Aabb::enclosing(
values
.iter()
.map(|p| global_to_local.transform_point3(Vec3::from_slice(p))),
)
.unwrap_or_default();
let global_aabb =
Aabb::enclosing(values.iter().map(|p| Vec3::from_slice(p))).unwrap_or_default();
*lines.bounds.lock() = Some(local_aabb);
lines.setup_complete.notify_waiters();
entity.insert(global_aabb);
}
entity
.insert(Mesh3d(meshes.add(mesh)))
.insert(*visibil)
.insert(match visibil.get() {
true => Visibility::Visible,
false => Visibility::Hidden,
});
.insert(Mesh3d(meshes.add(mesh)));
}
}
@@ -322,8 +244,7 @@ pub struct Lines {
data: Mutex<Vec<Line>>,
gen_mesh: AtomicBool,
entity: OnceLock<EntityHandle>,
bounds: Mutex<Option<Aabb>>,
setup_complete: Notify,
bounds: Mutex<Aabb>,
}
impl Lines {
pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
@@ -332,19 +253,10 @@ impl Lines {
.unwrap()
.bounding_box_calc
.set(|node| {
Box::pin(async {
let Ok(lines) = node.get_aspect::<Lines>() else {
return Default::default();
};
let bounds = *lines.bounds.lock();
match bounds {
Some(aabb) => aabb,
None => {
lines.setup_complete.notified().await;
lines.bounds.lock().unwrap_or_default()
}
}
})
node.get_aspect::<Lines>()
.ok()
.map(|v| *v.bounds.lock())
.unwrap_or_default()
});
let lines = LINES_REGISTRY.add(Lines {
@@ -352,8 +264,7 @@ impl Lines {
data: Mutex::new(lines),
gen_mesh: AtomicBool::new(true),
entity: OnceLock::new(),
bounds: Mutex::new(Some(Aabb::default())),
setup_complete: Notify::new(),
bounds: Mutex::new(Aabb::default()),
});
node.add_aspect_raw(lines.clone());

View File

@@ -1,6 +1,5 @@
pub mod lines;
pub mod model;
pub mod sky;
pub mod text;
use self::{lines::Lines, model::Model, text::Text};

View File

@@ -25,7 +25,6 @@ use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock, Weak};
use tokio::sync::Notify;
static LOAD_MODEL: BevyChannel<(Arc<Model>, PathBuf)> = BevyChannel::new();
@@ -55,7 +54,11 @@ impl Plugin for ModelNodePlugin {
app.init_resource::<MaterialRegistry>();
app.add_systems(
Update,
(load_models, gen_model_parts, apply_materials)
(
load_models,
gen_model_parts.after(TransformSystem::TransformPropagate),
apply_materials,
)
.chain()
.in_set(ModelNodeSystemSet),
);
@@ -64,14 +67,7 @@ impl Plugin for ModelNodePlugin {
// No extra data needed for a simple holdout
#[derive(Default, Asset, AsBindGroup, TypePath, Debug, Clone)]
#[data(50, u32, binding_array(101))]
#[bindless(index_table(range(50..51), binding(100)))]
pub struct HoldoutExtension {}
impl From<&HoldoutExtension> for u32 {
fn from(_: &HoldoutExtension) -> Self {
0
}
}
impl MaterialExtension for HoldoutExtension {
fn fragment_shader() -> ShaderRef {
HOLDOUT_SHADER_HANDLE.into()
@@ -105,13 +101,9 @@ fn load_models(
SceneRoot(handle),
ModelNode(Arc::downgrade(&model)),
SpatialNode(Arc::downgrade(&model.spatial)),
Visibility::Hidden,
))
.id();
model
.bevy_scene_entity
.set(EntityHandle::new(entity))
.unwrap();
model.bevy_scene_entity.set(entity.into()).unwrap();
}
}
@@ -148,7 +140,7 @@ fn apply_materials(
for (param_name, param) in model_part.pending_material_parameters.lock().drain() {
let mut new_mat = materials.get(&mesh_mat.0).unwrap().clone();
param.apply_to_material(
&model_part.spatial.node().unwrap().get_client().unwrap(),
&model_part.space.node().unwrap().get_client().unwrap(),
&mut new_mat,
&param_name,
&asset_server,
@@ -197,7 +189,7 @@ fn gen_model_parts(
.unwrap_or_else(|| name.to_string());
let parent_spatial = parent
.as_ref()
.map(|p| p.spatial.clone())
.map(|p| p.space.clone())
.unwrap_or_else(|| model.spatial.clone());
let client = model.spatial.node()?.get_client()?;
let (spatial, model_part) =
@@ -207,7 +199,7 @@ fn gen_model_parts(
client.scenegraph.add_node(Node::generate(&client, false));
let spatial = Spatial::add_to(
&node,
Some(parent_spatial.clone()),
Some(parent_spatial),
transform.compute_matrix(),
false,
);
@@ -215,7 +207,8 @@ fn gen_model_parts(
entity: OnceLock::new(),
mesh_entity: OnceLock::new(),
path,
spatial: spatial.clone(),
space: spatial.clone(),
_model: Arc::downgrade(&model),
pending_material_parameters: Mutex::default(),
pending_material_replacement: Mutex::default(),
holdout: AtomicBool::new(false),
@@ -225,8 +218,8 @@ fn gen_model_parts(
(spatial, model_part)
}
Some(part) => {
part.spatial.set_spatial_parent(&parent_spatial).unwrap();
(part.spatial.clone(), part.clone())
part.space.set_spatial_parent(&parent_spatial).unwrap();
(part.space.clone(), part.clone())
}
};
let aabb = Aabb::enclosing(
@@ -243,17 +236,13 @@ fn gen_model_parts(
)
.unwrap_or_default();
_ = spatial.bounding_box_calc.set(move |n| {
Box::pin(async {
n.get_aspect::<ModelPart>()
.ok()
.and_then(|v| v.bounds.get().copied())
.unwrap_or_default()
})
n.get_aspect::<ModelPart>()
.ok()
.and_then(|v| v.bounds.get().copied())
.unwrap_or_default()
});
let _ = spatial.set_spatial_parent(&parent_spatial);
spatial.set_local_transform(transform.compute_matrix());
let entity_handle = EntityHandle::new(entity);
spatial.set_entity(entity_handle.clone());
cmds.entity(entity)
.insert(SpatialNode(Arc::downgrade(&spatial)));
let mesh_entity = children_query
@@ -262,20 +251,14 @@ fn gen_model_parts(
.flat_map(|v| v.iter())
.find(|e| has_mesh.get(*e).unwrap_or(false))?;
_ = model_part.bounds.set(aabb);
_ = model_part.entity.set(entity_handle);
_ = model_part.mesh_entity.set(EntityHandle::new(mesh_entity));
_ = model_part.entity.set(entity.into());
_ = model_part.mesh_entity.set(mesh_entity.into());
parts.push(model_part.clone());
Some(model_part)
},
);
}
_ = model.parts.set(parts);
model.pre_bound_parts.lock().clear();
model
.spatial
.set_entity(model.bevy_scene_entity.get().unwrap().clone());
model.setup_complete.store(true, Ordering::Relaxed);
model.setup_complete_notify.notify_waiters();
}
}
@@ -332,6 +315,8 @@ impl HashedPbrMaterial {
mat.emissive_texture.hash(state);
mat.metallic_roughness_texture.hash(state);
mat.occlusion_texture.hash(state);
// should always be the same, TODO: make the spherical harmonics buffer a per mesh instance thing
// mat.spherical_harmonics.hash(state);
}
}
fn hash_color<H: Hasher>(color: Color, state: &mut H) {
@@ -469,7 +454,8 @@ pub struct ModelPart {
entity: OnceLock<EntityHandle>,
mesh_entity: OnceLock<EntityHandle>,
path: String,
spatial: Arc<Spatial>,
space: Arc<Spatial>,
_model: Weak<Model>,
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
pending_material_replacement: Mutex<Option<Handle<BevyMaterial>>>,
holdout: AtomicBool,
@@ -539,8 +525,6 @@ pub struct Model {
bevy_scene_entity: OnceLock<EntityHandle>,
parts: OnceLock<Vec<Arc<ModelPart>>>,
pre_bound_parts: Mutex<Vec<Arc<ModelPart>>>,
setup_complete: AtomicBool,
setup_complete_notify: Notify,
}
impl Model {
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<Model>> {
@@ -557,18 +541,6 @@ impl Model {
bevy_scene_entity: OnceLock::new(),
pre_bound_parts: Mutex::default(),
parts: OnceLock::new(),
setup_complete_notify: Notify::new(),
setup_complete: AtomicBool::new(false),
});
_ = model.spatial.bounding_box_calc.set(|n| {
Box::pin(async {
if let Ok(model) = n.get_aspect::<Model>()
&& !model.setup_complete.load(Ordering::Relaxed)
{
model.setup_complete_notify.notified().await;
}
Aabb::default()
})
});
LOAD_MODEL
.send((model.clone(), pending_model_path))
@@ -598,6 +570,7 @@ impl Model {
);
}
None => {
// TODO: this could be a denail of service vector
let client = self.spatial.node().unwrap().get_client().unwrap();
let part_node = client.scenegraph.add_node(Node::generate(&client, false));
let spatial = Spatial::add_to(
@@ -610,7 +583,8 @@ impl Model {
entity: OnceLock::new(),
mesh_entity: OnceLock::new(),
path: part_path,
spatial,
space: spatial,
_model: Arc::downgrade(self),
pending_material_parameters: Mutex::default(),
pending_material_replacement: Mutex::default(),
holdout: AtomicBool::new(false),
@@ -635,7 +609,7 @@ impl ModelAspect for Model {
let model = node.get_aspect::<Model>()?;
let part = model.get_model_part(part_path)?;
Alias::create_with_id(
&part.spatial.node().unwrap(),
&part.space.node().unwrap(),
&calling_client,
id,
MODEL_PART_ASPECT_ALIAS_INFO.clone(),
@@ -646,16 +620,6 @@ impl ModelAspect for Model {
}
impl Drop for Model {
fn drop(&mut self) {
for p in self.parts.get().iter().flat_map(|v| v.iter()) {
if let Some(node) = p.spatial.node() {
node.destroy();
}
}
for p in self.pre_bound_parts.lock().iter() {
if let Some(node) = p.spatial.node() {
node.destroy();
}
}
MODEL_REGISTRY.remove(self);
}
}

View File

@@ -1,72 +0,0 @@
use bevy::{
app::{Plugin, Update},
color::Color,
core_pipeline::{Skybox, core_3d::Camera3d},
ecs::{
entity::Entity,
query::With,
system::{Commands, Query, ResMut},
},
pbr::{AmbientLight, environment_map::EnvironmentMapLight},
};
use bevy_equirect::EquirectManager;
use glam::Quat;
pub struct SkyPlugin;
impl Plugin for SkyPlugin {
fn build(&self, app: &mut bevy::app::App) {
app.add_systems(Update, apply_sky);
}
}
// TODO: make this work with cameras spawned after setting the sky texture
fn apply_sky(
mut equirect: ResMut<EquirectManager>,
cameras: Query<Entity, With<Camera3d>>,
mut cmds: Commands,
) {
if let Some(tex) = super::QUEUED_SKYTEX.lock().take() {
if let Some(path) = tex {
let image_handle = equirect.load_equirect_as_cubemap(path, 1024);
for cam in cameras {
cmds.entity(cam).insert(Skybox {
image: image_handle.clone(),
brightness: 1000.0,
rotation: Quat::IDENTITY,
});
}
} else {
for cam in cameras {
cmds.entity(cam).remove::<Skybox>();
}
}
}
if let Some(light) = super::QUEUED_SKYLIGHT.lock().take() {
if let Some(path) = light {
let image_handle = equirect.load_equirect_as_cubemap(path, 1024);
for cam in cameras {
cmds.entity(cam)
.insert(EnvironmentMapLight {
diffuse_map: image_handle.clone(),
// we might want to use the SkyTex for this?
specular_map: image_handle.clone(),
intensity: 1000.0,
rotation: Quat::IDENTITY,
affects_lightmapped_mesh_diffuse: false,
})
.remove::<AmbientLight>();
}
} else {
for cam in cameras {
cmds.entity(cam)
.insert(AmbientLight {
color: Color::WHITE,
brightness: 1000.0,
affects_lightmapped_meshes: true,
})
.remove::<EnvironmentMapLight>();
}
}
}
}

View File

@@ -11,17 +11,18 @@ use crate::{
},
nodes::{
Node,
drawable::{TextFit, XAlign},
drawable::XAlign,
spatial::{Spatial, SpatialNode},
},
};
use bevy::{platform::collections::HashMap, prelude::*};
use bevy::{platform::collections::HashMap, prelude::*, render::mesh::MeshAabb};
use bevy_mesh_text_3d::{
Align, Attrs, HorizontalAnchorPoint, MeshTextPlugin, Settings as FontSettings, VerticalAlign,
VerticalAnchorPoint, generate_meshes,
Align, Attrs, MeshTextPlugin, Settings as FontSettings, generate_meshes,
text_glyphs::TextGlyphs,
};
use color_eyre::eyre::eyre;
use core::f32;
use cosmic_text::Metrics;
use parking_lot::Mutex;
use std::{ffi::OsStr, mem, path::PathBuf, sync::Arc};
@@ -32,7 +33,8 @@ pub struct TextNodePlugin;
impl Plugin for TextNodePlugin {
fn build(&self, app: &mut App) {
// Text init stuff
app.add_plugins(MeshTextPlugin);
// 1.0 for font size in meters
app.add_plugins(MeshTextPlugin::new(1.0));
app.world_mut()
.resource_mut::<FontSettings>()
.font_system
@@ -70,33 +72,21 @@ fn spawn_text(
super::XAlign::Center => Align::Center,
super::XAlign::Right => Align::Left,
});
let vertical_alignment = Some(match style.text_align_y {
super::YAlign::Top => VerticalAlign::Top,
super::YAlign::Center => VerticalAlign::Middle,
super::YAlign::Bottom => VerticalAlign::Bottom,
});
let text_string = text.text.lock().clone();
let mut text_glyphs = TextGlyphs::new(
Metrics {
font_size: style.character_height,
line_height: style.character_height,
},
[(text_string.as_str(), attrs.clone())],
&attrs,
&mut font_settings.font_system,
alignment,
);
let max_width = style.bounds.as_ref().map(|v| v.bounds.x);
let max_height = style.bounds.as_ref().map(|v| v.bounds.y);
let horizontal_anchor_point = style
.bounds
.as_ref()
.map(|v| match v.anchor_align_x {
XAlign::Left => HorizontalAnchorPoint::Left,
XAlign::Center => HorizontalAnchorPoint::Middle,
XAlign::Right => HorizontalAnchorPoint::Right,
})
.unwrap_or(HorizontalAnchorPoint::Middle);
let vertical_anchor_point = style
.bounds
.as_ref()
.map(|v| match v.anchor_align_y {
YAlign::Top => VerticalAnchorPoint::Top,
YAlign::Center => VerticalAnchorPoint::Middle,
YAlign::Bottom => VerticalAnchorPoint::Bottom,
})
.unwrap_or(VerticalAnchorPoint::Middle);
let wrap = matches!(style.bounds.as_ref().map(|v| v.fit), Some(TextFit::Wrap));
let max_height = style.bounds.as_ref().map(|v| v.bounds.x);
let (width, _height) =
text_glyphs.measure(max_width, max_height, &mut font_settings.font_system);
let char_meshes = generate_meshes(
bevy_mesh_text_3d::InputText::Simple {
text: text_string,
@@ -106,7 +96,8 @@ fn spawn_text(
emissive: Color::WHITE.to_linear(),
metallic: 0.0,
perceptual_roughness: 1.0,
alpha_mode: AlphaMode::Premultiplied,
// If alpha is supported on text we need to change this
alpha_mode: AlphaMode::Opaque,
double_sided: false,
..default()
},
@@ -118,30 +109,55 @@ fn spawn_text(
bevy_mesh_text_3d::Parameters {
extrusion_depth: 0.0,
font_size: style.character_height,
line_height: style.character_height * 1.1,
line_height: style.character_height,
alignment,
max_width: wrap.then_some(0).and(max_width),
max_height: wrap.then_some(0).and(max_height),
vertical_alignment,
horizontal_anchor_point,
vertical_anchor_point,
max_width,
max_height,
},
&mut meshes,
);
if let Some(db) = old_db {
mem::swap(font_settings.font_system.db_mut(), db);
}
let Ok((char_meshes, _text_size)) =
let Ok(char_meshes) =
char_meshes.inspect_err(|err| error!("unable to create text meshes: {err}"))
else {
continue;
};
let dist = char_meshes.iter().fold(f32::MAX, |dist, v| {
dist.min(
v.transform.translation.x
- meshes
.get(&v.mesh)
.unwrap()
.compute_aabb()
.unwrap_or_default()
.half_extents
.x,
)
});
// TODO: text align
let letters = char_meshes
.into_iter()
.map(|v| {
cmds.spawn((Mesh3d(v.mesh), MeshMaterial3d(v.material), v.transform))
.id()
cmds.spawn((
Mesh3d(v.mesh),
MeshMaterial3d(v.material),
// rotation is sus, might be related to the gltf coordinate system
Transform::from_rotation(Quat::from_rotation_y(f32::consts::PI))
* Transform::from_xyz(
-dist
+ match style.bounds.as_ref().map(|v| v.anchor_align_x) {
Some(XAlign::Center) => width * -0.5,
Some(XAlign::Right) => -width,
Some(XAlign::Left) => 0.0,
None => 0.0,
},
0.0,
0.0,
) * v.transform,
))
.id()
})
.collect::<Vec<_>>();
let entity = cmds
@@ -151,9 +167,7 @@ fn spawn_text(
))
.add_children(&letters)
.id();
let entity = EntityHandle::new(entity);
text.entity.lock().replace(entity.clone());
text.spatial.set_entity(entity);
text.entity.lock().replace(EntityHandle(entity));
}
}
@@ -171,7 +185,7 @@ impl FontDatabaseRegistry {
}
}
use super::{TextAspect, TextStyle, YAlign, model::MaterialRegistry};
use super::{TextAspect, TextStyle, model::MaterialRegistry};
static TEXT_REGISTRY: Registry<Text> = Registry::new();

View File

@@ -137,7 +137,6 @@ impl InputMethod {
self.capture_attempts.remove(handler);
}
#[tracing::instrument(level = "trace", skip(self, handler))]
pub(super) fn serialize(&self, alias_id: u64, handler: &Arc<InputHandler>) -> InputData {
let mut input = self.data.lock().clone();
input.transform(self, handler);

View File

@@ -138,10 +138,10 @@ pub fn process_input() {
if !handler_node.enabled() {
continue;
}
if let Some(handler_field_node) = handler.field.spatial.node()
&& !handler_field_node.enabled()
{
continue;
if let Some(handler_field_node) = handler.field.spatial.node() {
if !handler_field_node.enabled() {
continue;
}
};
let ser_span = debug_span!("serializing input").entered();

View File

@@ -77,12 +77,12 @@ impl CameraItem {
_message: Message,
response: MethodResponseSender,
) {
response.wrap(move || {
response.wrap_sync(move || {
let ItemType::Camera(_camera) = &node.get_aspect::<Item>().unwrap().specialization
else {
bail!("Wrong item type?");
};
Ok(serialize(())?)
Ok(serialize(())?.into())
});
}

View File

@@ -1,14 +1,13 @@
use super::camera::CameraItemAcceptor;
use super::{create_item_acceptor_flex, register_item_ui_flex};
use crate::bail;
use crate::nodes::{
Aspect, AspectIdentifier,
items::{ITEM_ACCEPTOR_ASPECT_ALIAS_INFO, ITEM_ASPECT_ALIAS_INFO, ITEM_UI_ASPECT_ALIAS_INFO},
};
use crate::core::error::Result;
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
use crate::nodes::{Aspect, AspectIdentifier};
use crate::{
core::{
client::{Client, INTERNAL_CLIENT, get_env, state},
error::Result,
registry::Registry,
},
nodes::{

View File

@@ -126,16 +126,18 @@ impl Node {
pub fn enabled(&self) -> bool {
self.enabled.load(Ordering::Relaxed)
&& if let Ok(spatial) = self.get_aspect::<Spatial>() {
spatial.visible()
spatial
.global_transform()
.to_scale_rotation_translation()
.0
.length_squared()
> 0.0
} else {
true
}
}
pub fn set_enabled(&self, enabled: bool) {
self.enabled.store(enabled, Ordering::Relaxed);
if let Ok(spatial) = self.get_aspect::<Spatial>() {
spatial.mark_dirty();
}
self.enabled.store(enabled, Ordering::Relaxed)
}
pub fn destroy(&self) {
if let Some(client) = self.get_client() {
@@ -207,11 +209,11 @@ impl Node {
) {
if let Ok(alias) = self.get_aspect::<Alias>() {
if !alias.info.server_methods.contains(&method) {
response.send_err(ScenegraphError::MemberNotFound);
response.send(Err(ScenegraphError::MemberNotFound));
return;
}
let Some(alias) = alias.original.upgrade() else {
response.send_err(ScenegraphError::BrokenAlias);
response.send(Err(ScenegraphError::BrokenAlias));
return;
};
alias.execute_local_method(
@@ -226,7 +228,7 @@ impl Node {
)
} else {
let Some(aspect) = self.aspects.0.get(&aspect_id).map(|v| v.clone()) else {
response.send_err(ScenegraphError::AspectNotFound);
response.send(Err(ScenegraphError::AspectNotFound));
return;
};
aspect.run_method(calling_client, self.clone(), method, message, response);
@@ -301,6 +303,7 @@ pub trait AspectIdentifier: Aspect {
const ID: u64;
}
pub trait Aspect: Any + Send + Sync + 'static {
fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync + 'static>;
fn run_signal(
&self,
calling_client: Arc<Client>,
@@ -334,6 +337,7 @@ impl Aspects {
.get(&A::ID)
// .cloned doesn't work for some reason
.map(|v| v.clone())
.map(|a| a.as_any())
.and_then(|a| Arc::downcast(a).ok())
.ok_or(ServerError::NoAspect(TypeId::of::<A>()))
}

View File

@@ -6,21 +6,18 @@ use super::fields::{Field, FieldTrait};
use super::{Aspect, AspectIdentifier};
use crate::bail;
use crate::core::client::Client;
use crate::core::entity_handle::EntityHandle;
use crate::core::error::Result;
use crate::core::registry::Registry;
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
use bevy::ecs::entity::EntityHashMap;
use bevy::prelude::Transform as BevyTransform;
use bevy::prelude::*;
use bevy::render::primitives::Aabb;
use color_eyre::eyre::OptionExt;
use glam::{Mat4, Quat, Vec3, vec3a};
use mint::Vector3;
use parking_lot::{Mutex, RwLock};
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::fmt::Debug;
use std::pin::Pin;
use std::sync::atomic::Ordering;
use std::sync::{Arc, OnceLock, Weak};
use std::{f32, ptr};
@@ -30,60 +27,68 @@ impl Plugin for SpatialNodePlugin {
fn build(&self, app: &mut App) {
app.add_systems(
PostUpdate,
(spawn_spatial_nodes, update_spatial_nodes)
.chain()
.before(TransformSystem::TransformPropagate),
update_spatial_nodes.before(TransformSystem::TransformPropagate),
);
}
}
fn spawn_spatial_nodes(mut cmds: Commands) {
for spatial in SPATIAL_REGISTRY
.get_valid_contents()
.into_iter()
.filter(|v| v.entity.read().is_none())
{
let entity = cmds
.spawn((SpatialNode(Arc::downgrade(&spatial)), Name::new("Spatial")))
.id();
spatial.set_entity(EntityHandle::new(entity));
}
}
fn update_spatial_nodes(
mut query: Query<(&mut BevyTransform, &mut Visibility, Option<&ChildOf>)>,
mut cmds: Commands,
mut query: Query<(
&mut BevyTransform,
&SpatialNode,
Option<&ChildOf>,
&mut Visibility,
)>,
parent_query: Query<&GlobalTransform>,
) {
for (entity, (transform, parent_entity)) in UPDATED_SPATIALS_NODES.lock().drain() {
let _span = debug_span!("updating spatial node").entered();
let Ok((mut bevy_transform, mut vis, parent)) = query.get_mut(entity) else {
continue;
};
// Set visibility based on node enabled state
if let Some(transform) = transform {
*vis = Visibility::Inherited;
*bevy_transform = transform;
} else {
*vis = Visibility::Hidden;
}
if parent.map(|v| v.0) != parent_entity {
match parent_entity {
Some(e) => cmds.entity(entity).insert(ChildOf(e)),
None => cmds.entity(entity).remove::<ChildOf>(),
query
.par_iter_mut()
.for_each(|(mut transform, spatial_node, child_of, mut vis)| {
let _span = debug_span!("updating spatial node").entered();
let Some(spatial) = spatial_node.0.upgrade() else {
return;
};
}
}
if spatial
.node()
.is_some_and(|v| !v.enabled.load(Ordering::Relaxed))
{
if !matches!(*vis, Visibility::Hidden) {
*vis = Visibility::Hidden;
}
return;
}
let mat4 =
debug_span!("getting global transform").in_scope(|| spatial.global_transform());
let (scale, _, _) = mat4.to_scale_rotation_translation();
match (*vis, scale == Vec3::ZERO) {
(Visibility::Inherited | Visibility::Visible, true) => {
*vis = Visibility::Hidden;
}
(Visibility::Hidden, false) => {
*vis = Visibility::Inherited;
}
_ => {}
}
match child_of {
Some(child_of) => {
let Ok(parent) = parent_query.get(child_of.0) else {
warn!("SpatialNode bevy Parent doesn't have global transform");
return;
};
*transform =
BevyTransform::from_matrix(parent.compute_matrix().inverse() * mat4);
}
None => {
*transform = BevyTransform::from_matrix(mat4);
}
}
});
}
static SPATIAL_REGISTRY: Registry<Spatial> = Registry::new();
#[derive(Clone, Component, Debug)]
#[require(BevyTransform, Visibility)]
pub struct SpatialNode(pub Weak<Spatial>);
const EPSILON: f32 = 0.00001;
stardust_xr_server_codegen::codegen_spatial_protocol!();
impl Transform {
pub fn to_mat4(&self, position: bool, rotation: bool, scale: bool) -> Mat4 {
@@ -95,16 +100,9 @@ impl Transform {
.then_some(self.rotation)
.flatten()
.unwrap_or_else(|| Quat::IDENTITY.into());
// Zero scale values break everything
let scale = scale
.then_some(self.scale)
.flatten()
.map(|s| Vector3 {
x: if s.x == 0.0 { EPSILON } else { s.x },
y: if s.y == 0.0 { EPSILON } else { s.y },
z: if s.z == 0.0 { EPSILON } else { s.z },
})
.unwrap_or_else(|| Vector3::from([1.0; 3]));
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
@@ -125,40 +123,25 @@ static ZONEABLE_REGISTRY: Registry<Spatial> = Registry::new();
pub struct Spatial {
pub node: Weak<Node>,
entity: RwLock<Option<EntityHandle>>,
parent: RwLock<Option<Arc<Spatial>>>,
old_parent: RwLock<Option<Arc<Spatial>>>,
transform: RwLock<Mat4>,
zone: RwLock<Weak<Zone>>,
parent: Mutex<Option<Arc<Spatial>>>,
old_parent: Mutex<Option<Arc<Spatial>>>,
transform: Mutex<Mat4>,
zone: Mutex<Weak<Zone>>,
children: Registry<Spatial>,
pub bounding_box_calc:
OnceLock<for<'a> fn(&'a Node) -> Pin<Box<dyn Future<Output = Aabb> + 'a + Send + Sync>>>,
pub bounding_box_calc: OnceLock<fn(&Node) -> Aabb>,
}
impl Spatial {
pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> {
let spatial = SPATIAL_REGISTRY.add(Spatial {
Arc::new(Spatial {
node,
entity: RwLock::new(None),
parent: RwLock::new(parent),
old_parent: RwLock::new(None),
transform: RwLock::new(transform),
zone: RwLock::new(Weak::new()),
parent: Mutex::new(parent),
old_parent: Mutex::new(None),
transform: Mutex::new(transform),
zone: Mutex::new(Weak::new()),
children: Registry::new(),
bounding_box_calc: OnceLock::default(),
});
spatial.mark_dirty();
spatial
}
pub fn set_entity(&self, entity: EntityHandle) {
self.entity.write().replace(entity);
self.mark_dirty();
for child in self.children.get_valid_contents() {
child.mark_dirty();
}
}
pub fn get_entity(&self) -> Option<Entity> {
self.entity.read().as_ref().map(|v| v.get())
})
}
pub fn add_to(
node: &Arc<Node>,
@@ -189,17 +172,18 @@ impl Spatial {
}
// the output bounds are probably way bigger than they need to be
pub async fn get_bounding_box(&self) -> Aabb {
pub fn get_bounding_box(&self) -> Aabb {
let Some(node) = self.node() else {
return Aabb::default();
};
let mut bounds = match self.bounding_box_calc.get() {
Some(f) => f(&node).await,
None => Aabb::default(),
};
let mut bounds = self
.bounding_box_calc
.get()
.map(|b| (b)(&node))
.unwrap_or_default();
for child in self.children.get_valid_contents() {
let mat = child.local_transform();
let child_aabb = Box::pin(child.get_bounding_box()).await;
let child_aabb = child.get_bounding_box();
bounds = Aabb::enclosing([
bounds.min().into(),
bounds.max().into(),
@@ -210,47 +194,9 @@ impl Spatial {
}
bounds
}
pub(super) fn mark_dirty(&self) {
let Some(entity) = self.entity.read().as_ref().map(|v| v.get()) else {
return;
};
let enabled = self
.node()
.is_none_or(|n| n.enabled.load(Ordering::Relaxed))
&& self.local_visible();
let transform = enabled.then(|| BevyTransform::from_matrix(self.local_transform()));
let parent = self
.get_parent()
.and_then(|v| v.entity.read().as_ref().map(|v| v.get()));
UPDATED_SPATIALS_NODES
.lock()
.insert(entity, (transform, parent));
}
pub fn local_transform(&self) -> Mat4 {
*self.transform.read()
}
fn local_visible(&self) -> bool {
// Check our own scale by looking at matrix column lengths
let mat = self.local_transform();
let x_scale = mat.x_axis.length_squared();
let y_scale = mat.y_axis.length_squared();
let z_scale = mat.z_axis.length_squared();
x_scale >= EPSILON.powi(2) || y_scale >= EPSILON.powi(2) || z_scale >= EPSILON.powi(2)
}
/// Check if this node or any ancestor has zero scale (for visibility culling)
pub fn visible(&self) -> bool {
// Check parent chain
if let Some(parent) = self.get_parent()
&& !parent.visible()
{
return false;
}
// Check our own scale by looking at matrix column lengths
self.local_visible()
*self.transform.lock()
}
pub fn global_transform(&self) -> Mat4 {
let parent_transform = self
@@ -261,8 +207,7 @@ impl Spatial {
parent_transform * self.local_transform()
}
pub fn set_local_transform(&self, transform: Mat4) {
*self.transform.write() = transform;
self.mark_dirty();
*self.transform.lock() = transform;
}
pub fn set_local_transform_components(
&self,
@@ -270,7 +215,9 @@ impl Spatial {
transform: Transform,
) {
if reference_space == Some(self) {
self.set_local_transform(transform.to_mat4(true, true, true) * self.local_transform());
self.set_local_transform(
parse_transform(transform, true, true, true) * self.local_transform(),
);
return;
}
let reference_to_parent_transform = reference_space
@@ -321,7 +268,7 @@ impl Spatial {
}
fn get_parent(&self) -> Option<Arc<Spatial>> {
self.parent.read().clone()
self.parent.lock().clone()
}
fn set_parent(self: &Arc<Self>, new_parent: &Arc<Spatial>) {
if let Some(parent) = self.get_parent() {
@@ -329,8 +276,7 @@ impl Spatial {
}
new_parent.children.add_raw(self);
*self.parent.write() = Some(new_parent.clone());
self.mark_dirty();
*self.parent.lock() = Some(new_parent.clone());
}
pub fn set_spatial_parent(self: &Arc<Self>, parent: &Arc<Spatial>) -> Result<()> {
@@ -354,15 +300,13 @@ impl Spatial {
pub(self) fn zone_distance(&self) -> f32 {
self.zone
.read()
.lock()
.upgrade()
.map(|zone| zone.field.clone())
.map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0)))
.unwrap_or(f32::NEG_INFINITY)
}
}
static UPDATED_SPATIALS_NODES: Mutex<EntityHashMap<(Option<BevyTransform>, Option<Entity>)>> =
Mutex::new(EntityHashMap::new());
impl AspectIdentifier for Spatial {
impl_aspect_for_spatial_aspect_id! {}
}
@@ -452,7 +396,6 @@ impl Drop for Spatial {
fn drop(&mut self) {
zone::release(self);
ZONEABLE_REGISTRY.remove(self);
SPATIAL_REGISTRY.remove(self);
}
}
@@ -469,7 +412,7 @@ impl SpatialRefAspect for SpatialRef {
_calling_client: Arc<Client>,
) -> Result<BoundingBox> {
let this_spatial = node.get_aspect::<Spatial>()?;
let bounds = this_spatial.get_bounding_box().await;
let bounds = this_spatial.get_bounding_box();
Ok(BoundingBox {
center: Vec3::from(bounds.center).into(),
@@ -485,7 +428,7 @@ impl SpatialRefAspect for SpatialRef {
let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
let mat = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial));
let bb = this_spatial.get_bounding_box().await;
let bb = this_spatial.get_bounding_box();
let bounds = Aabb::enclosing([
mat.transform_point3(bb.min().into()),
mat.transform_point3(bb.max().into()),
@@ -520,6 +463,23 @@ impl SpatialRefAspect for SpatialRef {
}
}
pub fn parse_transform(transform: Transform, position: bool, rotation: bool, scale: bool) -> Mat4 {
let position = position
.then_some(transform.translation)
.flatten()
.unwrap_or_else(|| Vector3::from([0.0; 3]));
let rotation = rotation
.then_some(transform.rotation)
.flatten()
.unwrap_or_else(|| Quat::IDENTITY.into());
let scale = scale
.then_some(transform.scale)
.flatten()
.unwrap_or_else(|| Vector3::from([1.0; 3]));
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into())
}
impl InterfaceAspect for Interface {
fn create_spatial(
_node: Arc<Node>,
@@ -530,7 +490,7 @@ impl InterfaceAspect for Interface {
zoneable: bool,
) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, true);
let transform = parse_transform(transform, true, true, true);
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, zoneable);
Ok(())
@@ -544,7 +504,7 @@ impl InterfaceAspect for Interface {
field: Arc<Node>,
) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?;
let transform = transform.to_mat4(true, true, false);
let transform = parse_transform(transform, true, true, false);
let field = field.get_aspect::<Field>()?;
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;

View File

@@ -21,8 +21,8 @@ pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
}
release(spatial);
*spatial.old_parent.write() = spatial.get_parent();
*spatial.zone.write() = Arc::downgrade(zone);
*spatial.old_parent.lock() = spatial.get_parent();
*spatial.zone.lock() = Arc::downgrade(zone);
let Some(zone_node) = zone.spatial.node.upgrade() else {
return;
};
@@ -45,11 +45,11 @@ pub fn release(spatial: &Spatial) {
};
let spatial = spatial_node.get_aspect::<Spatial>().unwrap();
let Some(old_parent) = spatial.old_parent.read().clone() else {
let Some(old_parent) = spatial.old_parent.lock().take() else {
return;
};
let _ = spatial.set_spatial_parent_in_place(&old_parent);
let mut spatial_zone = spatial.zone.write();
let mut spatial_zone = spatial.zone.lock();
if let Some(spatial_zone) = spatial_zone.upgrade() {
spatial_zone.captured.remove_aspect(spatial.as_ref());

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use bevy::prelude::*;
use bevy_mod_openxr::{
helper_traits::{ToQuat as _, ToVec3 as _},
resources::{OxrFrameState, Pipelined},
resources::OxrFrameState,
session::OxrSession,
};
use bevy_mod_xr::{
@@ -12,7 +12,7 @@ use bevy_mod_xr::{
};
use openxr::SpaceLocationFlags;
use crate::{DbusConnection, PreFrameWait, get_time, nodes::spatial::Spatial};
use crate::{DbusConnection, PreFrameWait, nodes::spatial::Spatial};
use super::{ObjectHandle, SpatialRef, input::mouse_pointer::FlatscreenCam};
@@ -68,7 +68,6 @@ fn update_xr(
ref_space: Option<Res<XrPrimaryReferenceSpace>>,
hmd: Res<Hmd>,
state: Option<Res<OxrFrameState>>,
pipelined: Option<Res<Pipelined>>,
) {
let (Some(session), Some(view), Some(ref_space), Some(state)) =
(session, hmd.space, ref_space, state)
@@ -81,9 +80,9 @@ fn update_xr(
// });
return;
};
let time = get_time(pipelined.is_some(), &state);
// this won't be correct with pipelined rendering
let location = session
.locate_space(&view, &ref_space, time)
.locate_space(&view, &ref_space, state.predicted_display_time)
.inspect_err(|err| error!("Error while Locating OpenXR Stage Space {err}"));
if let Ok(location) = location {
let is_tracked = location

View File

@@ -20,13 +20,14 @@ pub struct CaptureManager {
}
impl CaptureManager {
pub fn update_capture(&mut self, method: &InputMethod) {
if let Some(capture) = &self.capture.upgrade()
&& !method
if let Some(capture) = &self.capture.upgrade() {
if !method
.capture_attempts
.get_valid_contents()
.contains(capture)
{
self.capture = Weak::new();
{
self.capture = Weak::new();
}
}
}
pub fn set_new_capture(

View File

@@ -1,7 +1,7 @@
use super::{CaptureManager, DistanceCalculator, get_sorted_handlers};
use crate::{
DbusConnection, ObjectRegistryRes,
core::{client::INTERNAL_CLIENT, task},
core::client::INTERNAL_CLIENT,
nodes::{
Node, OwnedNode,
fields::{EXPORTED_FIELDS, Field, FieldTrait, Ray},
@@ -9,7 +9,6 @@ use crate::{
items::panel::KEYMAPS,
spatial::Spatial,
},
objects::FieldRef,
};
use bevy::{
input::{
@@ -21,43 +20,22 @@ use bevy::{
window::PrimaryWindow,
};
use color_eyre::eyre::Result;
use dashmap::DashMap;
use glam::{Mat4, Vec3, vec3};
use mint::Vector2;
use rustc_hash::{FxHashMap, FxHasher};
use serde::{Deserialize, Serialize};
use slotmap::{DefaultKey, Key as SlotKey};
use stardust_xr::{
schemas::dbus::{
ObjectInfo,
interfaces::FieldRefProxy,
list_query::{ListEvent, ObjectListQuery},
object_registry::ObjectRegistry,
query::{ObjectQuery, QueryContext, QueryEvent},
},
schemas::dbus::{interfaces::FieldRefProxy, object_registry::ObjectRegistry},
values::Datamap,
};
use std::sync::{Arc, Weak};
use tokio::sync::{Notify, mpsc, watch};
use tokio::task::{AbortHandle, JoinSet};
use std::sync::Arc;
use tokio::task::JoinSet;
use tokio::time::{Duration, timeout};
use xkbcommon_rs::{Context, Keymap, KeymapFormat, xkb_keymap::CompileFlags};
use zbus::{Connection, names::OwnedInterfaceName};
#[derive(Clone)]
struct HandlerInfo {
handler: ObjectInfo,
field_ref: Arc<Field>,
keyboard_proxy: KeyboardHandlerProxy<'static>,
}
#[derive(Debug, Clone)]
struct InputEvent {
key: u32,
pressed: bool,
}
pub struct FlatscreenInputPlugin;
impl Plugin for FlatscreenInputPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup);
@@ -70,9 +48,9 @@ impl Plugin for FlatscreenInputPlugin {
#[require(Camera3d)]
pub struct FlatscreenCam;
fn setup(mut cmds: Commands, object_registry: Res<ObjectRegistryRes>) {
let Ok(pointer) = MousePointer::new(object_registry.0.clone())
.inspect_err(|err| error!("unable to create mouse pointer: {err}"))
fn setup(mut cmds: Commands) {
let Ok(pointer) =
MousePointer::new().inspect_err(|err| error!("unable to create mouse pointer: {err}"))
else {
return;
};
@@ -181,14 +159,6 @@ trait KeyboardHandler {
async fn reset(&self) -> zbus::Result<()>;
}
// Make KeyboardHandlerProxy queryable
stardust_xr::schemas::impl_queryable_for_proxy!(KeyboardHandlerProxy);
// Query context for keyboard handlers
#[derive(Debug, Clone)]
struct KeyboardQueryContext;
impl QueryContext for KeyboardQueryContext {}
#[derive(Resource)]
pub struct MousePointer {
node: OwnedNode,
@@ -197,16 +167,9 @@ pub struct MousePointer {
pointer: Arc<InputMethod>,
capture_manager: CaptureManager,
mouse_datamap: MouseEvent,
// Task management
focus_task_abort_handle: AbortHandle,
input_delivery_task_abort_handle: AbortHandle,
// Channels
input_event_tx: mpsc::UnboundedSender<InputEvent>,
// Notification for focus recalculation
focus_notify: Arc<Notify>,
}
impl MousePointer {
pub fn new(object_registry: Arc<ObjectRegistry>) -> Result<Self> {
pub fn new() -> Result<Self> {
let node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
let pointer = InputMethod::add_to(
@@ -223,39 +186,6 @@ impl MousePointer {
.unwrap(),
);
// Create channels and notification
let (focused_handler_tx, focused_handler_rx) = watch::channel::<Option<HandlerInfo>>(None);
let (input_event_tx, input_event_rx) = mpsc::unbounded_channel::<InputEvent>();
let focus_notify = Arc::new(Notify::new());
// Spawn input delivery task
info!("Creating input delivery task");
let input_delivery_task_abort_handle = task::new(
|| "Mouse pointer input delivery task",
Self::input_delivery_task(
object_registry.get_connection().clone(),
focused_handler_rx,
input_event_rx,
keymap.data().as_ffi(),
),
)?
.abort_handle();
info!("Input delivery task created successfully");
// Spawn focus tracking task
info!("Creating focus tracking task");
let focus_task_abort_handle = task::new(
|| "Mouse pointer focus task",
Self::focus_tracking_task(
object_registry,
focus_notify.clone(),
spatial.clone(),
pointer.clone(),
focused_handler_tx,
),
)?
.abort_handle();
info!("Focus tracking task created successfully");
Ok(MousePointer {
node,
spatial,
@@ -263,10 +193,6 @@ impl MousePointer {
capture_manager: CaptureManager::default(),
mouse_datamap: Default::default(),
keymap,
focus_task_abort_handle,
input_delivery_task_abort_handle,
input_event_tx,
focus_notify,
})
}
pub fn update(
@@ -321,28 +247,7 @@ impl MousePointer {
*self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap();
}
self.target_pointer_input();
// Send keyboard input events via channel
for event in keyboard_input_events.read() {
if let Some(key) = map_key(event.key_code) {
let input_event = InputEvent {
key,
pressed: matches!(event.state, ButtonState::Pressed),
};
info!(
"Sending keyboard input event: key={}, pressed={}",
key, input_event.pressed
);
if let Err(e) = self.input_event_tx.send(input_event) {
error!("Failed to send keyboard input event: {}", e);
}
} else {
warn!("Unable to map key code: {:?}", event.key_code);
}
}
// Notify focus tracking task to recalculate focus
self.focus_notify.notify_waiters();
self.send_keyboard_input(dbus_connection, object_registry, keyboard_input_events);
}
fn target_pointer_input(&mut self) {
let distance_calculator: DistanceCalculator = |space, data, field| {
@@ -379,145 +284,96 @@ impl MousePointer {
);
}
async fn focus_tracking_task(
object_registry: Arc<ObjectRegistry>,
focus_notify: Arc<Notify>,
spatial: Arc<Spatial>,
pointer: Arc<InputMethod>,
focused_handler_tx: watch::Sender<Option<HandlerInfo>>,
pub fn send_keyboard_input(
&mut self,
dbus_connection: &Connection,
object_registry: &ObjectRegistry,
mut keyboard_input_events: EventReader<KeyboardInput>,
) {
info!("Focus tracking task started");
let keyboard_handlers = object_registry.get_objects("org.stardustxr.XKBv1");
let events = keyboard_input_events
.read()
.filter_map(|e| Some((map_key(e.key_code)?, e.state)))
.collect::<Vec<_>>();
// Spawn async task to handle keyboard input
tokio::spawn({
let keyboard_handlers = keyboard_handlers.clone();
let spatial = self.spatial.clone();
let keymap_id = self.keymap.data().as_ffi();
let dbus_connection = dbus_connection.clone();
// Create keyboard handler query inside the task
let mut keyboard_query = ObjectQuery::<
(FieldRefProxy<'static>, KeyboardHandlerProxy<'static>),
_,
>::new(object_registry.clone(), ());
let (keyboard_handlers, mapper) = keyboard_query.to_list_query();
task::new(
|| "Focus tracking mapper",
mapper.init(async |ev| match ev {
ListEvent::NewMatch((field_ref, keyboard_proxy)) => {
info!("New keyboard handler found");
let uid = timeout(Duration::from_millis(100), field_ref.uid())
async move {
let mut closest_handler = None;
let mut closest_distance = f32::MAX;
let mut join_set = JoinSet::new();
for handler in &keyboard_handlers {
let handler = handler.clone();
let dbus_connection = dbus_connection.clone();
join_set.spawn(async move {
// TODO: refactor the whole thing so picking the keyboardhandler to send input to is separate from sending
timeout(Duration::from_millis(10), async {
let field_ref = handler
.to_typed_proxy::<FieldRefProxy>(&dbus_connection)
.await
.ok()?;
let uid = field_ref.uid().await.ok()?;
Some((handler, uid))
})
.await
.ok()?
.ok()?;
let field_node = EXPORTED_FIELDS.lock().get(&uid)?.upgrade()?;
let field = field_node.get_aspect::<Field>();
Some((field, keyboard_proxy))
}
ListEvent::Modified((field_ref, keyboard_proxy)) => {
let uid = timeout(Duration::from_millis(100), field_ref.uid())
.await
.ok()?
.ok()?;
let field_node = EXPORTED_FIELDS.lock().get(&uid)?.upgrade()?;
let field = field_node.get_aspect::<Field>();
Some((field, keyboard_proxy))
}
_ => None,
}),
);
// Main focus calculation loop
loop {
let mut closest_handler = None;
let mut closest_distance = f32::MAX;
// Find closest handler
for (handler, (field_ref, keyboard_proxy)) in &*keyboard_handlers.iter().await {
let Ok(field_ref) = field_ref else {
continue;
};
let result = field_ref.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0),
space: spatial.clone(),
});
if result.deepest_point_distance > 0.0
&& result.min_distance < 0.05
&& result.deepest_point_distance < closest_distance
{
closest_distance = result.deepest_point_distance;
closest_handler = Some(HandlerInfo {
handler: handler.clone(),
field_ref: field_ref.clone(),
keyboard_proxy: keyboard_proxy.clone(),
.ok()
.flatten()
});
}
}
while let Some(Ok(Some((handler, field_ref_id)))) = join_set.join_next().await {
let exported_fields = EXPORTED_FIELDS.lock();
let Some(field_ref_node) =
exported_fields.get(&field_ref_id).and_then(|f| f.upgrade())
else {
println!("didn't find a thing :(");
continue;
};
// println!("still sendin stuff :)");
let Ok(field_ref) = field_ref_node.get_aspect::<Field>() else {
continue;
};
drop(exported_fields);
// Update focused handler
if let Some(ref handler_info) = closest_handler {
info!(
"Focus tracking task: Focused on handler at distance {}",
closest_distance
);
} else {
debug!("Focus tracking task: No handler in focus");
}
let _ = focused_handler_tx.send(closest_handler);
let result = field_ref.ray_march(Ray {
origin: vec3(0.0, 0.0, 0.0),
direction: vec3(0.0, 0.0, -1.0),
space: spatial.clone(),
});
// Wait for next frame signal
focus_notify.notified().await;
}
}
if result.deepest_point_distance > 0.0
&& result.min_distance < 0.05
&& result.deepest_point_distance < closest_distance
{
closest_distance = result.deepest_point_distance;
closest_handler = Some(handler);
}
}
async fn input_delivery_task(
dbus_connection: Connection,
mut focused_handler_rx: watch::Receiver<Option<HandlerInfo>>,
mut input_event_rx: mpsc::UnboundedReceiver<InputEvent>,
keymap_id: u64,
) {
info!("Input delivery task started");
loop {
// Handle input events
while let Some(input_event) = input_event_rx.recv().await {
info!(
"Input delivery task: Received input event key={}, pressed={}",
input_event.key, input_event.pressed
);
// Get current focused handler
let current_handler = focused_handler_rx.borrow().clone();
let Some(handler_info) = current_handler else {
continue;
let Some(handler) = closest_handler else {
return;
};
let Ok(keyboard_handler) = handler
.to_typed_proxy::<KeyboardHandlerProxy>(&dbus_connection)
.await
else {
return;
};
// Send input to handler using cached proxy
info!("Input delivery task: Sending to handler");
let keyboard_handler = &handler_info.keyboard_proxy;
// Register keymap first
if let Err(e) = keyboard_handler.keymap(keymap_id).await {
warn!("Input delivery task: Failed to register keymap: {}", e);
}
let _ = keyboard_handler.keymap(keymap_id).await;
// Send key state
if let Err(e) = keyboard_handler
.key_state(input_event.key + 8, input_event.pressed)
.await
{
error!("Input delivery task: Failed to send key state: {}", e);
} else {
info!(
"Input delivery task: Successfully sent key {} (pressed={})",
input_event.key + 8,
input_event.pressed
);
// Send key states
for (key, state) in events.iter() {
let pressed = matches!(state, ButtonState::Pressed);
let _ = keyboard_handler.key_state(key + 8, pressed).await;
}
}
}
}
}
impl Drop for MousePointer {
fn drop(&mut self) {
// Abort the persistent tasks when MousePointer is dropped
self.focus_task_abort_handle.abort();
self.input_delivery_task_abort_handle.abort();
});
}
}
@@ -529,11 +385,8 @@ fn map_key(key: KeyCode) -> Option<u32> {
Key::Tab => Some(input_event_codes::KEY_TAB!()),
Key::Enter => Some(input_event_codes::KEY_ENTER!()),
Key::ShiftLeft => Some(input_event_codes::KEY_LEFTSHIFT!()),
Key::ShiftRight => Some(input_event_codes::KEY_RIGHTSHIFT!()),
Key::ControlLeft => Some(input_event_codes::KEY_LEFTCTRL!()),
Key::ControlRight => Some(input_event_codes::KEY_RIGHTCTRL!()),
Key::AltLeft => Some(input_event_codes::KEY_LEFTALT!()),
Key::AltRight => Some(input_event_codes::KEY_RIGHTALT!()),
Key::CapsLock => Some(input_event_codes::KEY_CAPSLOCK!()),
Key::Escape => Some(input_event_codes::KEY_ESC!()),
Key::Space => Some(input_event_codes::KEY_SPACE!()),
@@ -599,25 +452,13 @@ fn map_key(key: KeyCode) -> Option<u32> {
Key::F3 => Some(input_event_codes::KEY_F3!()),
Key::F4 => Some(input_event_codes::KEY_F4!()),
Key::F5 => Some(input_event_codes::KEY_F5!()),
Key::F6 => Some(input_event_codes::KEY_F6!()),
Key::F7 => Some(input_event_codes::KEY_F7!()),
Key::F8 => Some(input_event_codes::KEY_F8!()),
// Key::F6 => Some(input_event_codes::KEY_F6!()),
// Key::F7 => Some(input_event_codes::KEY_F7!()),
// Key::F8 => Some(input_event_codes::KEY_F8!()),
Key::F9 => Some(input_event_codes::KEY_F9!()),
Key::F10 => Some(input_event_codes::KEY_F10!()),
Key::F11 => Some(input_event_codes::KEY_F11!()),
Key::F12 => Some(input_event_codes::KEY_F12!()),
Key::F13 => Some(input_event_codes::KEY_F13!()),
Key::F14 => Some(input_event_codes::KEY_F14!()),
Key::F15 => Some(input_event_codes::KEY_F15!()),
Key::F16 => Some(input_event_codes::KEY_F16!()),
Key::F17 => Some(input_event_codes::KEY_F17!()),
Key::F18 => Some(input_event_codes::KEY_F18!()),
Key::F19 => Some(input_event_codes::KEY_F19!()),
Key::F20 => Some(input_event_codes::KEY_F20!()),
Key::F21 => Some(input_event_codes::KEY_F21!()),
Key::F22 => Some(input_event_codes::KEY_F22!()),
Key::F23 => Some(input_event_codes::KEY_F23!()),
Key::F24 => Some(input_event_codes::KEY_F24!()),
Key::Comma => Some(input_event_codes::KEY_COMMA!()),
Key::Period => Some(input_event_codes::KEY_DOT!()),
Key::Slash => Some(input_event_codes::KEY_SLASH!()),
@@ -636,34 +477,6 @@ fn map_key(key: KeyCode) -> Option<u32> {
Key::NumpadSubtract => Some(input_event_codes::KEY_MINUS!()),
Key::NumpadDecimal => Some(input_event_codes::KEY_DOT!()),
Key::NumpadDivide => Some(input_event_codes::KEY_SLASH!()),
Key::ContextMenu => Some(input_event_codes::KEY_CONTEXT_MENU!()),
Key::Help => Some(input_event_codes::KEY_HELP!()),
Key::NumLock => Some(input_event_codes::KEY_NUMLOCK!()),
Key::NumpadBackspace => Some(input_event_codes::KEY_BACKSPACE!()),
Key::NumpadClear => Some(input_event_codes::KEY_CLEAR!()),
Key::NumpadClearEntry => Some(input_event_codes::KEY_CLEAR!()),
Key::NumpadComma => Some(input_event_codes::KEY_COMMA!()),
Key::NumpadEnter => Some(input_event_codes::KEY_ENTER!()),
Key::NumpadEqual => Some(input_event_codes::KEY_EQUAL!()),
Key::NumpadHash => Some(input_event_codes::KEY_NUMERIC_POUND!()),
Key::NumpadStar => Some(input_event_codes::KEY_KPASTERISK!()),
Key::Fn => Some(input_event_codes::KEY_FN!()),
Key::ScrollLock => Some(input_event_codes::KEY_SCROLLLOCK!()),
Key::Pause => Some(input_event_codes::KEY_PAUSE!()),
Key::Power => Some(input_event_codes::KEY_POWER!()),
Key::Sleep => Some(input_event_codes::KEY_SLEEP!()),
Key::Suspend => Some(input_event_codes::KEY_SUSPEND!()),
Key::Again => Some(input_event_codes::KEY_AGAIN!()),
Key::Copy => Some(input_event_codes::KEY_COPY!()),
Key::Cut => Some(input_event_codes::KEY_CUT!()),
Key::Find => Some(input_event_codes::KEY_FIND!()),
Key::Open => Some(input_event_codes::KEY_OPEN!()),
Key::Paste => Some(input_event_codes::KEY_PASTE!()),
Key::Props => Some(input_event_codes::KEY_PROPS!()),
Key::Select => Some(input_event_codes::KEY_SELECT!()),
Key::Undo => Some(input_event_codes::KEY_UNDO!()),
Key::Hiragana => Some(input_event_codes::KEY_HIRAGANA!()),
Key::Katakana => Some(input_event_codes::KEY_KATAKANA!()),
_ => None,
}
}

View File

@@ -2,7 +2,6 @@ use super::{CaptureManager, get_sorted_handlers};
use crate::{
DbusConnection, PreFrameWait,
core::client::INTERNAL_CLIENT,
get_time,
nodes::{
Node, OwnedNode,
drawable::{
@@ -13,14 +12,14 @@ use crate::{
input::{INPUT_HANDLER_REGISTRY, InputDataType, InputHandler, InputMethod, Tip},
spatial::Spatial,
},
objects::{AsyncTracked, ObjectHandle, SpatialRef, Tracked},
objects::{ObjectHandle, SpatialRef, Tracked},
};
use bevy::{asset::Handle, ecs::resource::Resource};
use bevy::{math::Affine3, prelude::*};
use bevy_mod_openxr::{
action_binding::{OxrSendActionBindings, OxrSuggestActionBinding},
helper_traits::{ToIsometry3d, ToVec2},
resources::{OxrFrameState, OxrInstance, Pipelined},
resources::{OxrFrameState, OxrInstance},
session::OxrSession,
};
use bevy_mod_xr::{
@@ -148,7 +147,6 @@ fn update(
session: Option<Res<OxrSession>>,
ref_space: Option<Res<XrPrimaryReferenceSpace>>,
state: Option<Res<OxrFrameState>>,
pipelined: Option<Res<Pipelined>>,
) {
let (Some(session), Some(state), Some(ref_space)) = (session, state, ref_space) else {
controllers.left.set_enabled(false);
@@ -160,7 +158,8 @@ fn update(
.sync_actions(&[ActiveActionSet::new(&actions.set)])
.unwrap();
});
let time = get_time(pipelined.is_some(), &state);
let time = state.predicted_display_time;
// stupid bevy gltf loading issue (rotated 180 degrees on the y axis)
controllers
.left
.update(&session, &actions, time, ref_space.0);
@@ -269,9 +268,8 @@ pub struct OxrControllerInput {
model_part: Arc<ModelPart>,
capture_manager: CaptureManager,
datamap: ControllerDatamap,
tracked: AsyncTracked,
tracked: ObjectHandle<Tracked>,
space: Option<XrSpace>,
_model_node: OwnedNode,
}
impl OxrControllerInput {
fn new(connection: &Connection, side: HandSide) -> Result<Self> {
@@ -281,19 +279,11 @@ impl OxrControllerInput {
HandSide::Right => "right",
};
let (spatial, object_handle) = SpatialRef::create(connection, &path);
let tracked = AsyncTracked::new(connection, &path);
let tracked = Tracked::new(connection, &path);
let tip = InputDataType::Tip(Tip::default());
let node = spatial.node().unwrap();
node.set_enabled(false);
let model_node = Arc::new(Node::generate(&INTERNAL_CLIENT, true));
let model_spatial = Spatial::add_to(
&model_node,
Some(spatial.clone()),
Mat4::from_scale(Vec3::splat(0.02)),
false,
);
let model =
Model::add_to(&model_node, ResourceID::Direct(CURSOR_MODEL_PATH.into())).unwrap();
let model = Model::add_to(&node, ResourceID::Direct(CURSOR_MODEL_PATH.into())).unwrap();
let model_part = model.get_model_part("Cursor".to_string()).unwrap();
let input = InputMethod::add_to(
&node,
@@ -310,7 +300,6 @@ impl OxrControllerInput {
datamap: Default::default(),
tracked,
space: None,
_model_node: OwnedNode(model_node),
})
}
#[instrument(level = "debug", skip(self))]
@@ -318,7 +307,13 @@ impl OxrControllerInput {
if let Some(node) = self.input.spatial.node() {
node.set_enabled(enabled);
}
self.tracked.set_tracked(enabled);
tokio::spawn({
// this is suboptimal since it probably allocates a fresh string every frame
let handle = self.tracked.clone();
async move {
handle.set_tracked(enabled).await;
}
});
}
fn update(
&mut self,
@@ -360,7 +355,9 @@ impl OxrControllerInput {
1.0,
)),
);
self.input.spatial.set_local_transform(world_transform);
self.input
.spatial
.set_local_transform(world_transform * Mat4::from_scale(Vec3::splat(0.02)));
}
let path = session
.instance()
@@ -369,13 +366,13 @@ impl OxrControllerInput {
HandSide::Right => "/user/hand/right",
})
.unwrap();
if let Ok(path) = session.current_interaction_profile(path)
&& let Ok(path) = session.instance().path_to_string(path)
&& path == "/interaction_profiles/khr/simple_controller"
{
self.set_enabled(false);
if let Ok(path) = session.current_interaction_profile(path) {
if session.instance().path_to_string(path).unwrap()
== "/interaction_profiles/khr/simple_controller"
{
self.set_enabled(false);
}
}
fn get<T: openxr::ActionInput + Default>(
session: &OxrSession,
path: openxr::Path,

View File

@@ -7,15 +7,15 @@ use crate::nodes::{
input::{Hand, InputMethod, Joint},
spatial::Spatial,
};
use crate::objects::{AsyncTracked, ObjectHandle, SpatialRef, Tracked};
use crate::{BevyMaterial, DbusConnection, ObjectRegistryRes, PreFrameWait, get_time};
use crate::objects::{ObjectHandle, SpatialRef, Tracked};
use crate::{BevyMaterial, DbusConnection, ObjectRegistryRes, PreFrameWait};
use bevy::prelude::Transform as BevyTransform;
use bevy::prelude::*;
use bevy_mod_openxr::helper_traits::{ToQuat, ToVec3};
use bevy_mod_openxr::resources::{OxrFrameState, Pipelined};
use bevy_mod_openxr::resources::OxrFrameState;
use bevy_mod_openxr::session::OxrSession;
use bevy_mod_xr::hands::{HandBone, HandSide, XrHandBoneEntities, XrHandBoneRadius};
use bevy_mod_xr::session::{XrPreDestroySession, XrSessionCreated, session_available};
use bevy_mod_xr::session::{XrPreDestroySession, XrSessionCreated};
use bevy_mod_xr::spaces::{XrPrimaryReferenceSpace, XrSpaceLocationFlags};
use bevy_sk::hand::GRADIENT_TEXTURE_HANDLE;
use color_eyre::eyre::Result;
@@ -34,11 +34,8 @@ impl Plugin for HandPlugin {
app.add_systems(PreFrameWait, update_hands.run_if(resource_exists::<Hands>));
app.add_systems(XrSessionCreated, create_trackers);
app.add_systems(XrPreDestroySession, destroy_trackers);
app.add_systems(
PostUpdate,
update_hand_material.run_if(resource_exists::<Hands>),
);
app.add_systems(Startup, setup.run_if(session_available));
app.add_systems(PostUpdate, update_hand_material);
app.add_systems(Startup, setup);
}
}
fn update_hands(
@@ -53,22 +50,30 @@ fn update_hands(
&mut XrHandBoneRadius,
)>,
joints_query: Query<&XrHandBoneEntities>,
pipelined: Option<Res<Pipelined>>,
) {
let (Some(session), Some(state), Some(ref_space)) = (session, state, ref_space) else {
hands.left.tracked.set_tracked(false);
hands.right.tracked.set_tracked(false);
tokio::task::spawn({
let left = hands.left.tracked.clone();
let right = hands.right.tracked.clone();
async move {
left.set_tracked(false);
right.set_tracked(false);
}
});
return;
};
let get_joints = |hand: &mut OxrHandInput| -> Option<openxr::HandJointLocations> {
let Some(tracker) = hand.tracker.as_ref() else {
hand.input.spatial.node().unwrap().set_enabled(false);
hand.tracked.set_tracked(false);
let handle = hand.tracked.clone();
tokio::task::spawn(async move {
handle.set_tracked(false);
});
return None;
};
let time = get_time(pipelined.is_some(), &state);
// this won't be correct with pipelined rendering
session
.locate_hand_joints(tracker, &ref_space, time)
.locate_hand_joints(tracker, &ref_space, state.predicted_display_time)
.inspect_err(|err| error!("Error while locating hand joints"))
.ok()
.flatten()
@@ -177,7 +182,7 @@ pub struct OxrHandInput {
input: Arc<InputMethod>,
capture_manager: CaptureManager,
datamap: HandDatamap,
tracked: AsyncTracked,
tracked: ObjectHandle<Tracked>,
tracker: Option<openxr::HandTracker>,
captured: bool,
material: Handle<BevyMaterial>,
@@ -196,7 +201,7 @@ impl OxrHandInput {
HandSide::Right => "right",
} + "/palm"),
);
let tracked = AsyncTracked::new(
let tracked = Tracked::new(
connection,
&("/org/stardustxr/Hand/".to_string()
+ match side {
@@ -238,7 +243,13 @@ impl OxrHandInput {
if let Some(node) = self.input.spatial.node() {
node.set_enabled(enabled);
}
self.tracked.set_tracked(enabled);
tokio::spawn({
// this is suboptimal since it probably allocates a fresh string every frame
let handle = self.tracked.clone();
async move {
handle.set_tracked(enabled).await;
}
});
}
fn update(
&mut self,

View File

@@ -20,7 +20,6 @@ use std::{
marker::PhantomData,
sync::{Arc, atomic::Ordering},
};
use tokio::{sync::mpsc, task::AbortHandle};
use zbus::{Connection, interface, object_server::Interface, zvariant::OwnedObjectPath};
pub mod hmd;
@@ -44,48 +43,6 @@ impl<I: Interface> Drop for ObjectHandle<I> {
}
}
/// A wrapper around ObjectHandle<Tracked> that batches async updates
/// instead of spawning a tokio task for each state change
pub struct AsyncTracked {
pub sender: mpsc::UnboundedSender<bool>,
pub _handle: ObjectHandle<Tracked>,
pub _abort_handle: AbortHandle,
}
impl AsyncTracked {
pub fn new(connection: &Connection, path: &str) -> Self {
let handle = Tracked::new(connection, path);
let (sender, mut receiver) = mpsc::unbounded_channel::<bool>();
// Spawn a single long-running task that processes state updates
let task = tokio::task::spawn({
let handle = handle.clone();
async move {
while let Some(is_tracked) = receiver.recv().await {
let _ = handle.set_tracked(is_tracked).await;
}
}
});
Self {
sender,
_handle: handle,
_abort_handle: task.abort_handle(),
}
}
pub fn set_tracked(&self, is_tracked: bool) {
// Just send over channel instead of spawning a task
let _ = self.sender.send(is_tracked);
}
}
impl Drop for AsyncTracked {
fn drop(&mut self) {
self._abort_handle.abort();
}
}
pub struct SpatialRef(u64, OwnedNode);
impl SpatialRef {
pub fn create(connection: &Connection, path: &str) -> (Arc<Spatial>, ObjectHandle<SpatialRef>) {

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use bevy::prelude::*;
use bevy_mod_openxr::{
helper_traits::{ToQuat, ToVec3},
resources::{OxrFrameState, Pipelined},
resources::OxrFrameState,
session::OxrSession,
};
use bevy_mod_xr::{
@@ -14,9 +14,9 @@ use openxr::SpaceLocationFlags;
use parking_lot::RwLock;
use zbus::{Connection, ObjectServer, interface};
use crate::{DbusConnection, PreFrameWait, get_time, nodes::spatial::Spatial};
use crate::{DbusConnection, PreFrameWait, nodes::spatial::Spatial};
use super::{AsyncTracked, ObjectHandle, SpatialRef, Tracked};
use super::{ObjectHandle, SpatialRef, Tracked};
pub struct PlaySpacePlugin;
impl Plugin for PlaySpacePlugin {
@@ -31,7 +31,7 @@ impl Plugin for PlaySpacePlugin {
fn setup(connection: Res<DbusConnection>, mut cmds: Commands) {
let (spatial, spatial_handle) = SpatialRef::create(&connection, "/org/stardustxr/PlaySpace");
// the OpenXR session might not exist quite yet
let tracked = AsyncTracked::new(&connection, "/org/stardustxr/PlaySpace");
let tracked = Tracked::new(&connection, "/org/stardustxr/PlaySpace");
let dbus_connection = connection.clone();
let play_space_data = Arc::new(RwLock::default());
tokio::task::spawn({
@@ -75,22 +75,26 @@ fn update(
ref_space: Option<Res<XrPrimaryReferenceSpace>>,
play_space: Res<PlaySpace>,
state: Option<Res<OxrFrameState>>,
pipelined: Option<Res<Pipelined>>,
) {
let (Some(session), Some(stage), Some(ref_space), Some(state)) =
(session, stage, ref_space, state)
else {
play_space.bounds.write().drain(..);
play_space.tracked_handle.set_tracked(false);
tokio::task::spawn({
let handle = play_space.tracked_handle.clone();
async move {
handle.set_tracked(false).await;
}
});
play_space
.spatial
.set_local_transform(Mat4::from_translation(vec3(0.0, -1.65, 0.0)));
return;
};
let time = get_time(pipelined.is_some(), &state);
// this won't be correct with pipelined rendering
let location = session
.locate_space(&stage.0, &ref_space, time)
.locate_space(&stage.0, &ref_space, state.predicted_display_time)
.inspect_err(|err| error!("Error while Locating OpenXR Stage Space {err}"));
if let Ok(location) = location {
let is_tracked = location.location_flags.contains(
@@ -99,7 +103,12 @@ fn update(
| SpaceLocationFlags::ORIENTATION_VALID
| SpaceLocationFlags::ORIENTATION_TRACKED,
);
play_space.tracked_handle.set_tracked(is_tracked);
tokio::task::spawn({
let handle = play_space.tracked_handle.clone();
async move {
handle.set_tracked(is_tracked).await;
}
});
if is_tracked {
play_space
.spatial
@@ -131,7 +140,7 @@ fn update(
pub struct PlaySpace {
spatial: Arc<Spatial>,
_spatial_handle: ObjectHandle<SpatialRef>,
tracked_handle: AsyncTracked,
tracked_handle: ObjectHandle<Tracked>,
bounds: Arc<RwLock<Vec<(f64, f64)>>>,
}
pub struct PlaySpaceBounds(Arc<RwLock<Vec<(f64, f64)>>>);

View File

@@ -5,7 +5,6 @@ use crate::wayland::WAYLAND_DISPLAY;
use crate::{CliArgs, STARDUST_INSTANCE};
use directories::ProjectDirs;
use rustc_hash::FxHashMap;
use std::ffi::OsStr;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::process::{Child, Command, Stdio};
@@ -59,7 +58,6 @@ pub fn restore_session(session_dir: &Path, debug_launched_clients: bool) -> Vec<
};
clients
.filter_map(Result::ok)
.filter(|c| c.path().extension() == Some(OsStr::new("toml")))
.filter_map(|c| ClientStateParsed::from_file(&c.path()))
.filter_map(ClientStateParsed::launch_command)
.filter_map(|c| run_client(c, debug_launched_clients))

View File

@@ -1,39 +0,0 @@
use bevy::{
app::Plugin,
core_pipeline::core_3d::Camera3d,
ecs::{
component::Component,
system::{Commands, Res},
},
render::camera::{PerspectiveProjection, Projection},
transform::components::Transform,
};
use bevy_mod_openxr::session::OxrSession;
use bevy_mod_xr::session::XrSessionCreated;
use openxr::ReferenceSpaceType;
pub struct SpectatorCameraPlugin;
impl Plugin for SpectatorCameraPlugin {
fn build(&self, app: &mut bevy::app::App) {
app.add_systems(XrSessionCreated, create);
}
}
#[derive(Component)]
#[require(Camera3d)]
struct SpectatorCam;
fn create(session: Res<OxrSession>, mut cmds: Commands) {
cmds.spawn((
SpectatorCam,
session
.create_reference_space(ReferenceSpaceType::VIEW, Transform::IDENTITY)
.unwrap()
.0,
Projection::Perspective(PerspectiveProjection {
fov: 100f32.to_radians(),
..Default::default()
}),
));
}

View File

@@ -1,5 +1,5 @@
use crate::wayland::Message;
use crate::wayland::dmabuf::buffer_backing::DmabufBacking;
use crate::wayland::{Client, Message, WaylandResult};
use crate::wayland::{MessageSink, core::shm_buffer_backing::ShmBufferBacking, util::ClientExt};
use bevy::{
asset::{Assets, Handle},
@@ -8,9 +8,11 @@ use bevy::{
use bevy_dmabuf::import::ImportedDmatexs;
use mint::Vector2;
use std::sync::Arc;
use waynest::ObjectId;
pub use waynest_protocols::server::core::wayland::wl_buffer::*;
use waynest_server::{Client as _, RequestDispatcher};
pub use waynest::server::protocol::core::wayland::wl_buffer::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug)]
pub struct BufferUsage {
@@ -39,8 +41,7 @@ pub enum BufferBacking {
Dmabuf(DmabufBacking),
}
#[derive(Debug, RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct Buffer {
pub id: ObjectId,
backing: BufferBacking,
@@ -48,12 +49,8 @@ pub struct Buffer {
impl Buffer {
#[tracing::instrument(level = "debug", skip_all)]
pub fn new(
client: &mut Client,
id: ObjectId,
backing: BufferBacking,
) -> WaylandResult<Arc<Self>> {
Ok(client.insert(id, Self { id, backing })?)
pub fn new(client: &mut Client, id: ObjectId, backing: BufferBacking) -> Arc<Self> {
client.insert(id, Self { id, backing })
}
/// Returns the tex if it was updated
@@ -65,20 +62,11 @@ impl Buffer {
) -> Option<Handle<Image>> {
tracing::debug!("Updating texture for buffer {:?}", self.id);
match &self.backing {
BufferBacking::Shm(backing) => backing.update_tex(dmatexes, images),
BufferBacking::Shm(backing) => backing.update_tex(images),
BufferBacking::Dmabuf(backing) => backing.update_tex(dmatexes, images),
}
}
#[tracing::instrument(level = "debug", skip_all)]
pub fn on_commit(&self) {
tracing::debug!("running on_commit for buffer {:?}", self.id);
match &self.backing {
BufferBacking::Shm(backing) => backing.on_commit(),
BufferBacking::Dmabuf(_backing) => {}
}
}
pub fn is_transparent(&self) -> bool {
match &self.backing {
BufferBacking::Shm(backing) => backing.is_transparent(),
@@ -93,19 +81,13 @@ impl Buffer {
}
}
pub fn uses_buffer_usage(&self) -> bool {
matches!(
self.backing,
BufferBacking::Dmabuf(_) | BufferBacking::Shm(_)
)
matches!(self.backing, BufferBacking::Dmabuf(_))
}
}
impl WlBuffer for Buffer {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_buffer:request:destroy
async fn destroy(&self, client: &mut Client, _sender_id: ObjectId) -> WaylandResult<()> {
client.remove(self.id);
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
tracing::info!("Destroying buffer {:?}", self.id);
Ok(())
}

View File

@@ -1,11 +1,10 @@
use waynest::ObjectId;
pub use waynest_protocols::server::core::wayland::wl_callback::*;
use waynest_server::RequestDispatcher;
pub use waynest::server::protocol::core::wayland::wl_callback::*;
use waynest::{
server::{Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug, RequestDispatcher, Clone)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher, Clone)]
pub struct Callback(pub ObjectId);
/// https://wayland.app/protocols/wayland#wl_callback
impl WlCallback for Callback {
type Connection = crate::wayland::Client;
}
impl WlCallback for Callback {}

View File

@@ -1,25 +1,25 @@
use super::surface::WL_SURFACE_REGISTRY;
use crate::wayland::{WaylandError, WaylandResult};
use crate::wayland::{core::surface::Surface, util::ClientExt};
use waynest::ObjectId;
use waynest_protocols::server::core::wayland::wl_surface::WlSurface;
pub use waynest_protocols::server::core::wayland::{wl_compositor::*, wl_region::*};
use waynest_server::{Client as _, RequestDispatcher};
pub use waynest::server::protocol::core::wayland::wl_compositor::*;
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::core::wayland::{wl_region::WlRegion, wl_surface::WlSurface},
},
wire::ObjectId,
};
#[derive(Debug, waynest_server::RequestDispatcher, Default)]
#[waynest(error = WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher, Default)]
pub struct Compositor;
impl WlCompositor for Compositor {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_compositor:request:create_surface
async fn create_surface(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> WaylandResult<()> {
let surface = client.insert(id, Surface::new(client, id))?;
) -> Result<()> {
let surface = client.insert(id, Surface::new(client, id));
if let Some(output) = client.display().output.get() {
surface.enter(client, id, output.id).await?;
}
@@ -31,56 +31,46 @@ impl WlCompositor for Compositor {
/// https://wayland.app/protocols/wayland#wl_compositor:request:create_region
async fn create_region(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> WaylandResult<()> {
client.insert(id, Region { id })?;
) -> Result<()> {
client.insert(id, Region::default());
Ok(())
}
}
#[derive(Debug, RequestDispatcher)]
#[waynest(error = WaylandError, connection = crate::wayland::Client)]
pub struct Region {
id: ObjectId,
}
#[derive(Debug, Dispatcher, Default)]
pub struct Region {}
impl WlRegion for Region {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_region:request:add
async fn add(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_region:request:subtract
async fn subtract(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_region:request:destroy
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

View File

@@ -1,26 +1,27 @@
use crate::wayland::{Client, WaylandResult};
use std::os::fd::OwnedFd;
use waynest::ObjectId;
use waynest_protocols::server::core::wayland::{
wl_data_device::*, wl_data_device_manager::*, wl_data_offer::WlDataOffer, wl_data_source::*,
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::core::wayland::{
wl_data_device::*, wl_data_device_manager::*, wl_data_offer::WlDataOffer,
wl_data_source::*,
},
},
wire::ObjectId,
};
use waynest_server::Client as _;
// TODO: actually implement this
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct DataDeviceManager;
impl WlDataDeviceManager for DataDeviceManager {
type Connection = Client;
async fn create_data_source(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> WaylandResult<()> {
client.insert(id, DataSource { id })?;
) -> Result<()> {
client.insert(id, DataSource);
Ok(())
}
@@ -30,137 +31,116 @@ impl WlDataDeviceManager for DataDeviceManager {
_sender_id: ObjectId,
id: ObjectId,
_seat: ObjectId,
) -> WaylandResult<()> {
client.insert(id, DataDevice)?;
) -> Result<()> {
client.insert(id, DataDevice);
Ok(())
}
}
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct DataSource {
id: ObjectId,
}
#[derive(Debug, Dispatcher)]
pub struct DataSource;
impl WlDataSource for DataSource {
type Connection = Client;
async fn send(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_mime_type: String,
_fd: OwnedFd,
) -> Result<()> {
Ok(())
}
async fn offer(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_mime_type: String,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn set_actions(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_dnd_actions: DndAction,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
}
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct DataDevice;
impl WlDataDevice for DataDevice {
type Connection = Client;
async fn start_drag(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_source: Option<ObjectId>,
_origin: ObjectId,
_icon: Option<ObjectId>,
_serial: u32,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
async fn set_selection(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_source: Option<ObjectId>,
_serial: u32,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
async fn release(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct DataOffer {
id: ObjectId,
}
#[derive(Debug, Dispatcher)]
pub struct DataOffer;
impl WlDataOffer for DataOffer {
type Connection = Client;
async fn accept(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_serial: u32,
_mime_type: Option<String>,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
async fn receive(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_mime_type: String,
_fd: OwnedFd,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn finish(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn finish(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn set_actions(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_dnd_actions: DndAction,
_preferred_action: DndAction,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
}

View File

@@ -1,6 +1,6 @@
use crate::{
nodes::items::panel::KEYMAPS,
wayland::{Client, WaylandResult, core::surface::Surface, util::ClientExt},
wayland::{core::surface::Surface, util::ClientExt},
};
use dashmap::{DashMap, DashSet};
use memfd::MemfdOptions;
@@ -9,14 +9,17 @@ use std::{
collections::HashSet,
io::Write,
os::{
fd::{AsFd, IntoRawFd},
fd::IntoRawFd,
unix::io::{FromRawFd, OwnedFd},
},
sync::{Arc, Weak},
};
use tokio::sync::Mutex;
use waynest::ObjectId;
pub use waynest_protocols::server::core::wayland::wl_keyboard::*;
pub use waynest::server::protocol::core::wayland::wl_keyboard::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Default)]
struct ModifierState {
@@ -53,8 +56,7 @@ impl ModifierState {
input_event_codes::KEY_LEFTCTRL!() | input_event_codes::KEY_RIGHTCTRL!() => {
mods |= 4
}
input_event_codes::KEY_LEFTALT!() => mods |= 8,
input_event_codes::KEY_RIGHTALT!() => mods |= 128,
input_event_codes::KEY_LEFTALT!() | input_event_codes::KEY_RIGHTALT!() => mods |= 8,
input_event_codes::KEY_LEFTMETA!() | input_event_codes::KEY_RIGHTMETA!() => {
mods |= 64
}
@@ -67,8 +69,7 @@ impl ModifierState {
}
}
#[derive(waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Dispatcher)]
pub struct Keyboard {
pub id: ObjectId,
focused_surface: Mutex<Weak<Surface>>,
@@ -88,9 +89,10 @@ impl Keyboard {
}
}
async fn send_keymap(&self, client: &mut Client, keymap: &[u8]) -> WaylandResult<()> {
async fn send_keymap(&self, client: &mut Client, keymap: &[u8]) -> Result<()> {
let mut file = MemfdOptions::default()
.create("stardust-keymap")?
.create("stardust-keymap")
.map_err(|e| waynest::server::Error::Custom(e.to_string()))?
.into_file();
file.set_len(keymap.len() as u64)?;
file.write_all(keymap)?;
@@ -103,7 +105,7 @@ impl Keyboard {
client,
self.id,
KeymapFormat::XkbV1,
fd.as_fd(),
fd,
keymap.len() as u32,
)
.await?;
@@ -119,7 +121,7 @@ impl Keyboard {
keymap_id: u64,
key: u32,
pressed: bool,
) -> WaylandResult<()> {
) -> Result<()> {
// KEYMAP UPDATES
{
let mut old_keymap_id = self.current_keymap_id.lock().await;
@@ -222,7 +224,7 @@ impl Keyboard {
Ok(())
}
pub async fn reset(&self, client: &mut Client) -> WaylandResult<()> {
pub async fn reset(&self, client: &mut Client) -> Result<()> {
let mut modifier_state = self.modifier_state.lock().await;
modifier_state.pressed_keys.clear();
modifier_state.mods_depressed = 0;
@@ -245,14 +247,8 @@ impl Keyboard {
}
impl WlKeyboard for Keyboard {
type Connection = Client;
/// https://wayland.app/protocols/wayland#wl_keyboard:request:release
async fn release(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

View File

@@ -1,15 +1,17 @@
use crate::wayland::{Client, WaylandResult};
use waynest::ObjectId;
pub use waynest_protocols::server::core::wayland::wl_output::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub use waynest::server::protocol::core::wayland::wl_output::*;
#[derive(Debug, Dispatcher)]
pub struct Output {
pub id: ObjectId,
pub version: u32,
}
impl Output {
pub async fn advertise_outputs(&self, client: &mut Client) -> WaylandResult<()> {
pub async fn advertise_outputs(&self, client: &mut Client) -> Result<()> {
self.geometry(
client,
self.id,
@@ -24,16 +26,6 @@ impl Output {
)
.await?;
if self.version >= 4 {
self.name(client, self.id, "Stardust Virtual Display".to_string())
.await?;
self.description(
client,
self.id,
"I needed this to account for dumb clients".to_string(),
)
.await?;
}
self.mode(client, self.id, Mode::Current, 2048, 2048, i32::MAX)
.await?;
@@ -44,14 +36,8 @@ impl Output {
}
}
impl WlOutput for Output {
type Connection = Client;
/// https://wayland.app/protocols/wayland#wl_output:request:release
async fn release(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

View File

@@ -1,24 +1,20 @@
use super::surface::SurfaceRole;
use crate::nodes::items::panel::Geometry;
use crate::wayland::core::surface::Surface;
use crate::wayland::{Client, WaylandResult};
use crate::wayland::core::{seat::fixed_from_f32, surface::Surface};
use mint::Vector2;
use std::sync::Arc;
use std::sync::Weak;
use tokio::sync::Mutex;
use tracing;
use waynest::ObjectId;
use waynest_server::Client as _;
pub use waynest::server::protocol::core::wayland::wl_pointer::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
pub use waynest_protocols::server::core::wayland::wl_pointer::*;
#[derive(waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Dispatcher)]
pub struct Pointer {
pub id: ObjectId,
version: u32,
focused_surface: Mutex<Weak<Surface>>,
cursor_surface: Mutex<Option<Arc<Surface>>>,
}
impl Pointer {
pub fn new(id: ObjectId, version: u32) -> Self {
@@ -26,7 +22,6 @@ impl Pointer {
id,
version,
focused_surface: Mutex::new(Weak::new()),
cursor_surface: Mutex::new(None),
}
}
@@ -35,7 +30,7 @@ impl Pointer {
client: &mut Client,
surface: Arc<Surface>,
position: Vector2<f32>,
) -> WaylandResult<()> {
) -> Result<()> {
tracing::debug!(
"Handling pointer motion at ({}, {})",
position.x,
@@ -65,8 +60,8 @@ impl Pointer {
self.id,
serial,
surface.id,
(position.x as f64).into(),
(position.y as f64).into(),
fixed_from_f32(position.x),
fixed_from_f32(position.y),
)
.await?;
@@ -80,8 +75,8 @@ impl Pointer {
client,
self.id,
0, // time
(position.x as f64).into(),
(position.y as f64).into(),
fixed_from_f32(position.x),
fixed_from_f32(position.y),
)
.await?;
if self.version >= 5 {
@@ -97,7 +92,7 @@ impl Pointer {
surface: Arc<Surface>,
button: u32,
pressed: bool,
) -> WaylandResult<()> {
) -> Result<()> {
tracing::debug!(
"Handling pointer button {} {} on surface {:?}",
button,
@@ -126,7 +121,7 @@ impl Pointer {
_surface: Arc<Surface>,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) -> WaylandResult<()> {
) -> Result<()> {
tracing::debug!(
"Handling pointer scroll: distance={:?}, steps={:?}",
scroll_distance,
@@ -138,7 +133,7 @@ impl Pointer {
self.id,
0, // time
Axis::HorizontalScroll,
(distance.x as f64).into(),
fixed_from_f32(distance.x),
)
.await?;
self.axis(
@@ -146,44 +141,23 @@ impl Pointer {
self.id,
0, // time
Axis::VerticalScroll,
(distance.y as f64).into(),
)
.await?;
}
if self.version < 8
&& self.version >= 5
&& let Some(steps) = scroll_steps
{
self.axis_discrete(client, self.id, Axis::HorizontalScroll, steps.x as i32)
.await?;
self.axis_discrete(client, self.id, Axis::VerticalScroll, steps.y as i32)
.await?;
}
if self.version >= 8
&& let Some(steps) = scroll_steps
{
self.axis_value120(
client,
self.id,
Axis::HorizontalScroll,
(steps.x * 120.) as i32,
)
.await?;
self.axis_value120(
client,
self.id,
Axis::VerticalScroll,
(steps.y * 120.) as i32,
fixed_from_f32(distance.y),
)
.await?;
}
if self.version >= 5 {
if let Some(steps) = scroll_steps {
self.axis_discrete(client, self.id, Axis::HorizontalScroll, steps.x as i32)
.await?;
self.axis_discrete(client, self.id, Axis::VerticalScroll, steps.y as i32)
.await?;
}
self.frame(client, self.id).await?;
}
Ok(())
}
pub async fn reset(&self, client: &mut Client) -> WaylandResult<()> {
pub async fn reset(&self, client: &mut Client) -> Result<()> {
let mut focused = self.focused_surface.lock().await;
if let Some(old_surface) = focused.upgrade() {
let serial = client.next_event_serial();
@@ -193,60 +167,24 @@ impl Pointer {
*focused = Weak::new();
Ok(())
}
pub async fn cursor_surface(&self) -> Option<Arc<Surface>> {
self.cursor_surface.lock().await.clone()
}
}
impl WlPointer for Pointer {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_pointer:request:set_cursor
async fn set_cursor(
&self,
client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_serial: u32,
surface: Option<ObjectId>,
hotspot_x: i32,
hotspot_y: i32,
) -> WaylandResult<()> {
if let Some(focused_surface) = self.focused_surface.lock().await.upgrade()
&& let Some(panel_item) = focused_surface.panel_item.lock().upgrade()
{
panel_item.set_cursor(surface.and_then(|s| client.get::<Surface>(s)).map(|s| {
let size = s
.current_state()
.buffer
.map(|b| b.buffer.size())
.unwrap_or([16; 2].into());
Geometry {
origin: [hotspot_x, hotspot_y].into(),
size: [size.x as u32, size.y as u32].into(),
}
}));
}
let Some(surface) = surface else {
return Ok(());
};
let Some(surface) = client.get::<Surface>(surface) else {
return Ok(());
};
surface
.try_set_role(SurfaceRole::Cursor, Error::Role)
.await?;
self.cursor_surface.lock().await.replace(surface);
_surface: Option<ObjectId>,
_hotspot_x: i32,
_hotspot_y: i32,
) -> Result<()> {
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_pointer:request:release
async fn release(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

View File

@@ -1,12 +1,10 @@
use crate::wayland::Client;
use crate::wayland::WaylandResult;
use crate::wayland::core::{keyboard::Keyboard, pointer::Pointer, surface::Surface, touch::Touch};
use mint::Vector2;
use std::sync::Arc;
use std::sync::OnceLock;
use waynest::ObjectId;
pub use waynest_protocols::server::core::wayland::wl_seat::*;
use waynest_server::Client as _;
pub use waynest::server::protocol::core::wayland::wl_seat::*;
use waynest::server::{Client, Dispatcher, Result};
use waynest::wire::{Fixed, ObjectId};
#[derive(Debug)]
pub enum SeatMessage {
@@ -45,8 +43,11 @@ pub enum SeatMessage {
Reset,
}
#[derive(Default, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub fn fixed_from_f32(f: f32) -> Fixed {
unsafe { Fixed::from_raw((f * 256.0).round() as u32) }
}
#[derive(Default, Dispatcher)]
pub struct Seat {
version: u32,
pointer: OnceLock<Arc<Pointer>>,
@@ -55,7 +56,7 @@ pub struct Seat {
}
impl Seat {
pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> WaylandResult<Self> {
pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> Result<Self> {
let seat = Self {
version,
pointer: OnceLock::new(),
@@ -75,11 +76,7 @@ impl Seat {
Ok(seat)
}
pub async fn handle_message(
&self,
client: &mut Client,
message: SeatMessage,
) -> WaylandResult<()> {
pub async fn handle_message(&self, client: &mut Client, message: SeatMessage) -> Result<()> {
match message {
SeatMessage::PointerMotion { surface, position } => {
if let Some(pointer) = self.pointer.get() {
@@ -157,22 +154,16 @@ impl Seat {
}
Ok(())
}
pub async fn cursor_surface(&self) -> Option<Arc<Surface>> {
self.pointer.get()?.cursor_surface().await
}
}
impl WlSeat for Seat {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_seat:request:get_pointer
async fn get_pointer(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> WaylandResult<()> {
let pointer = client.insert(id, Pointer::new(id, self.version))?;
) -> Result<()> {
let pointer = client.insert(id, Pointer::new(id, self.version));
let _ = self.pointer.set(pointer);
Ok(())
}
@@ -180,12 +171,12 @@ impl WlSeat for Seat {
/// https://wayland.app/protocols/wayland#wl_seat:request:get_keyboard
async fn get_keyboard(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> WaylandResult<()> {
) -> Result<()> {
tracing::info!("Getting keyboard");
let keyboard = client.insert(id, Keyboard::new(id))?;
let keyboard = client.insert(id, Keyboard::new(id));
let _ = self.keyboard.set(keyboard);
Ok(())
}
@@ -193,21 +184,17 @@ impl WlSeat for Seat {
/// https://wayland.app/protocols/wayland#wl_seat:request:get_touch
async fn get_touch(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> WaylandResult<()> {
let touch = client.insert(id, Touch(id))?;
) -> Result<()> {
let touch = client.insert(id, Touch(id));
let _ = self.touch.set(touch);
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_seat:request:release
async fn release(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

View File

@@ -1,18 +1,16 @@
use crate::wayland::{Client, WaylandResult, core::shm_pool::ShmPool};
use std::os::fd::OwnedFd;
use waynest::ObjectId;
pub use waynest_protocols::server::core::wayland::wl_shm::*;
use waynest_server::Client as _;
#[derive(Debug, waynest_server::RequestDispatcher, Default)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
use crate::wayland::core::shm_pool::ShmPool;
pub use waynest::server::protocol::core::wayland::wl_shm::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug, Dispatcher, Default)]
pub struct Shm;
impl Shm {
pub async fn advertise_formats(
&self,
client: &mut Client,
sender_id: ObjectId,
) -> WaylandResult<()> {
pub async fn advertise_formats(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
self.format(client, sender_id, Format::Argb8888).await?;
self.format(client, sender_id, Format::Xrgb8888).await?;
@@ -20,28 +18,22 @@ impl Shm {
}
}
impl WlShm for Shm {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_shm:request:create_pool
async fn create_pool(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
pool_id: ObjectId,
fd: OwnedFd,
size: i32,
) -> WaylandResult<()> {
client.insert(pool_id, ShmPool::new(fd, size, pool_id)?)?;
) -> Result<()> {
client.insert(pool_id, ShmPool::new(fd, size)?);
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_shm:request:release
async fn release(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

View File

@@ -1,64 +1,20 @@
use super::shm_pool::ShmPool;
use crate::wayland::{RENDER_DEVICE, vulkano_data::VULKANO_CONTEXT};
use bevy::{
asset::{Assets, Handle},
asset::{Assets, Handle, RenderAssetUsages},
image::Image,
render::render_resource::{Extent3d, TextureDimension, TextureFormat},
};
use bevy_dmabuf::{
dmatex::{Dmatex, DmatexPlane, Resolution},
import::{DmatexUsage, DropCallback, ImportedDmatexs, ImportedTexture, import_texture},
};
use drm_fourcc::DrmFourcc;
use mint::Vector2;
use parking_lot::Mutex;
use std::{
os::fd::OwnedFd,
sync::{Arc, OnceLock},
};
use tracing::debug_span;
use vulkano::{
buffer::BufferUsage,
command_buffer::{
AutoCommandBufferBuilder, CommandBufferUsage, CopyBufferToImageInfo,
PrimaryCommandBufferAbstract,
},
image::{
ImageAspect, ImageCreateFlags, ImageCreateInfo, ImageMemory, ImageTiling, ImageUsage,
sys::RawImage,
},
memory::{
DedicatedAllocation, DeviceMemory, ExternalMemoryHandleType, MemoryAllocateInfo,
ResourceMemory,
allocator::{AllocationCreateInfo, MemoryTypeFilter},
},
sync::GpuFuture,
};
use waynest_protocols::server::core::wayland::wl_shm::Format;
use std::sync::Arc;
use waynest::server::protocol::core::wayland::wl_shm::Format;
/// Parameters for a shared memory buffer
#[derive(Debug)]
pub struct ShmBufferBacking {
pool: Arc<ShmPool>,
offset: usize,
stride: usize,
size: Vector2<usize>,
wl_format: Format,
image: Arc<vulkano::image::Image>,
tex: OnceLock<Handle<Image>>,
pending_imported_dmatex: Mutex<Option<ImportedTexture>>,
}
impl std::fmt::Debug for ShmBufferBacking {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ShmBufferBacking")
.field("pool", &self.pool)
.field("offset", &self.offset)
.field("stride", &self.stride)
.field("size", &self.size)
.field("wl_format", &self.wl_format)
.field("image", &self.image)
.field("tex", &self.tex)
.finish()
}
format: Format,
}
impl ShmBufferBacking {
@@ -67,203 +23,70 @@ impl ShmBufferBacking {
offset: usize,
stride: usize,
size: Vector2<usize>,
wl_format: Format,
format: Format,
) -> Self {
let vk = VULKANO_CONTEXT.wait();
let format = match wl_format {
Format::Argb8888 | Format::Xrgb8888 => vulkano::format::Format::B8G8R8A8_SRGB,
_ => unimplemented!(),
};
let modifiers = vk
.phys_dev
.format_properties(format)
.unwrap()
.drm_format_modifier_properties
.into_iter()
.filter_map(|v| {
(v.drm_format_modifier_plane_count == 1).then_some(v.drm_format_modifier)
})
.collect();
let raw_image = RawImage::new(
vk.dev.clone(),
ImageCreateInfo {
flags: ImageCreateFlags::empty(),
image_type: vulkano::image::ImageType::Dim2d,
format,
extent: [size.x as u32, size.y as u32, 1],
tiling: ImageTiling::DrmFormatModifier,
usage: ImageUsage::TRANSFER_DST,
drm_format_modifiers: modifiers,
external_memory_handle_types: ExternalMemoryHandleType::DmaBuf.into(),
..Default::default()
},
)
.unwrap();
let (modifier, num_planes) = raw_image.drm_format_modifier().unwrap();
let mem_reqs = raw_image.memory_requirements()[0];
let index = vk
.phys_dev
.memory_properties()
.memory_types
.iter()
.enumerate()
.map(|(i, _v)| i as u32)
.find(|i| mem_reqs.memory_type_bits & (1 << i) != 0)
.expect("no valid memory type");
let mem = ResourceMemory::new_dedicated(
DeviceMemory::allocate(
vk.dev.clone(),
MemoryAllocateInfo {
allocation_size: mem_reqs.layout.size(),
memory_type_index: index,
dedicated_allocation: Some(DedicatedAllocation::Image(&raw_image)),
export_handle_types: ExternalMemoryHandleType::DmaBuf.into(),
..Default::default()
},
)
.unwrap(),
);
let Ok(image) = raw_image.bind_memory([mem]) else {
panic!("unable to bind memory")
};
let image = Arc::new(image);
let ImageMemory::Normal(mem) = image.memory() else {
unreachable!()
};
let [mem] = mem.as_slice() else {
unreachable!()
};
let fd = OwnedFd::from(
mem.device_memory()
.export_fd(ExternalMemoryHandleType::DmaBuf)
.unwrap(),
);
let planes = (0..num_planes)
.filter_map(|i| {
Some(match i {
0 => ImageAspect::MemoryPlane0,
1 => ImageAspect::MemoryPlane1,
2 => ImageAspect::MemoryPlane2,
3 => ImageAspect::MemoryPlane3,
_ => return None,
})
})
.map(|aspect| {
let plane_layout = image.subresource_layout(aspect, 0, 0).unwrap();
DmatexPlane {
dmabuf_fd: fd.try_clone().unwrap().into(),
modifier,
offset: plane_layout.offset as u32,
stride: plane_layout.row_pitch as i32,
}
})
.collect::<Vec<_>>();
let dmatex = Dmatex {
planes,
res: Resolution {
x: size.x as u32,
y: size.y as u32,
},
format: DrmFourcc::Argb8888 as u32,
flip_y: false,
srgb: true,
};
let imported_dmatex = import_texture(
RENDER_DEVICE.wait(),
dmatex,
DropCallback(None),
DmatexUsage::Sampling,
)
.unwrap();
Self {
pool,
offset,
stride,
size,
wl_format,
image,
pending_imported_dmatex: Mutex::new(Some(imported_dmatex)),
tex: OnceLock::new(),
format,
}
}
pub fn on_commit(&self) {
let vk = VULKANO_CONTEXT.wait();
let data_len = self.size.x * self.size.y * 4;
let gpu_buffer = vulkano::buffer::Buffer::new_slice::<u8>(
vk.alloc.clone(),
vulkano::buffer::BufferCreateInfo {
usage: BufferUsage::TRANSFER_SRC,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
data_len as u64,
)
.unwrap();
{
let _span = debug_span!("copy to gpu buffer").entered();
let shm_data_lock = self.pool.data_lock();
let mut gpu_slice = gpu_buffer.write().unwrap();
for (shm_offset, gpu_offset) in
(0..self.size.y).map(|v| (self.offset + (v * self.stride), (v * (self.size.x * 4))))
{
let line_slice = &shm_data_lock[shm_offset..(shm_offset + (self.size.x * 4))];
let gpu_subslice = &mut gpu_slice[gpu_offset..(gpu_offset + (self.size.x * 4))];
gpu_subslice.copy_from_slice(line_slice);
}
}
let mut command_buffer = AutoCommandBufferBuilder::primary(
vk.command_buffer_alloc.clone(),
vk.queue.queue_family_index(),
CommandBufferUsage::OneTimeSubmit,
)
.unwrap();
command_buffer
.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(
gpu_buffer.clone(),
self.image.clone(),
))
.unwrap();
let command_buffer = command_buffer.build().unwrap();
command_buffer
.execute(vk.queue.clone())
.unwrap()
.then_signal_fence_and_flush()
.unwrap()
.wait(None)
.unwrap();
}
#[tracing::instrument("debug", skip_all)]
pub fn update_tex(
&self,
dmatexes: &ImportedDmatexs,
images: &mut Assets<Image>,
) -> Option<Handle<Image>> {
self.pending_imported_dmatex
.lock()
.take()
.map(|tex| dmatexes.insert_imported_dmatex(images, tex))
.inspect(|handle| {
_ = self.tex.set(handle.clone());
});
self.tex.get().cloned()
pub fn update_tex(&self, images: &mut Assets<Image>) -> Option<Handle<Image>> {
let src_data_lock = self.pool.data_lock();
let mut src_cursor = self.offset;
// Calculate maximum cursor position needed - stride is already in bytes
let max_cursor = self.offset + (self.size.y * self.stride);
// Check if we have enough data
if max_cursor > src_data_lock.len() {
return None;
}
let data_len = self.size.x * self.size.y * 4;
if src_data_lock.len() != data_len {
return None;
}
let mut dst_cursor = 0;
let mut dst_data = vec![0u8; data_len];
for _y in 0..self.size.y {
for _x in 0..self.size.x {
match self.format {
Format::Argb8888 | Format::Xrgb8888 => {
dst_data[dst_cursor] = src_data_lock[src_cursor + 2]; // Red is byte 2
dst_data[dst_cursor + 1] = src_data_lock[src_cursor + 1]; // Green is byte 1
dst_data[dst_cursor + 2] = src_data_lock[src_cursor]; // Blue is byte 0
dst_data[dst_cursor + 3] = src_data_lock[src_cursor + 3]; // Alpha is byte 3
}
_ => panic!("Unsupported format {:?}", self.format),
}
src_cursor += 4;
dst_cursor += 4;
}
src_cursor += self.stride - (self.size.x * 4);
}
let image = Image::new(
Extent3d {
width: self.size().x as u32,
height: self.size().y as u32,
depth_or_array_layers: 1,
},
TextureDimension::D2,
dst_data,
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::RENDER_WORLD,
);
Some(images.add(image))
}
pub fn is_transparent(&self) -> bool {
match self.wl_format {
match self.format {
Format::Xrgb8888 => false,
Format::Argb8888 => true,
_ => true,

View File

@@ -1,52 +1,48 @@
use super::shm_buffer_backing::ShmBufferBacking;
use crate::wayland::{
Client, WaylandResult,
core::buffer::{Buffer, BufferBacking},
};
use memmap2::{MmapOptions, RemapOptions};
use parking_lot::{Mutex, MutexGuard, RawMutex, lock_api::MappedMutexGuard};
use std::os::fd::{AsRawFd, OwnedFd};
use waynest::ObjectId;
use waynest_protocols::server::core::wayland::wl_shm::Format;
pub use waynest_protocols::server::core::wayland::wl_shm_pool::*;
use waynest_server::Client as _;
use std::os::fd::{IntoRawFd, OwnedFd};
use waynest::{
server::{Client, Dispatcher, Result, protocol::core::wayland::wl_shm::Format},
wire::ObjectId,
};
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
use crate::wayland::core::buffer::{Buffer, BufferBacking};
pub use waynest::server::protocol::core::wayland::wl_shm_pool::*;
use super::shm_buffer_backing::ShmBufferBacking;
#[derive(Debug, Dispatcher)]
pub struct ShmPool {
inner: Mutex<memmap2::MmapMut>,
id: ObjectId,
}
impl ShmPool {
#[tracing::instrument(level = "debug", skip_all)]
pub fn new(fd: OwnedFd, size: i32, id: ObjectId) -> WaylandResult<Self> {
pub fn new(fd: OwnedFd, size: i32) -> Result<Self> {
let map = unsafe {
MmapOptions::new()
.len(size as usize)
.map_mut(fd.as_raw_fd())?
.map_mut(fd.into_raw_fd())?
};
Ok(Self {
inner: Mutex::new(map),
id,
})
}
#[tracing::instrument(level = "debug", skip_all)]
pub fn data_lock(&self) -> MappedMutexGuard<'_, RawMutex, [u8]> {
pub fn data_lock(&self) -> MappedMutexGuard<RawMutex, [u8]> {
MutexGuard::map(self.inner.lock(), |i| i.as_mut())
}
}
impl WlShmPool for ShmPool {
type Connection = Client;
/// https://wayland.app/protocols/wayland#wl_shm_pool:request:create_buffer
#[tracing::instrument(level = "debug", skip_all)]
async fn create_buffer(
&self,
client: &mut Self::Connection,
client: &mut Client,
sender_id: ObjectId,
id: ObjectId,
offset: i32,
@@ -54,7 +50,7 @@ impl WlShmPool for ShmPool {
height: i32,
stride: i32,
format: Format,
) -> WaylandResult<()> {
) -> Result<()> {
let params = ShmBufferBacking::new(
client.get::<ShmPool>(sender_id).unwrap(),
offset as usize,
@@ -63,18 +59,13 @@ impl WlShmPool for ShmPool {
format,
);
Buffer::new(client, id, BufferBacking::Shm(params))?;
Buffer::new(client, id, BufferBacking::Shm(params));
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_shm_pool:request:resize
#[tracing::instrument(level = "debug", skip_all)]
async fn resize(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
size: i32,
) -> WaylandResult<()> {
async fn resize(&self, _client: &mut Client, _sender_id: ObjectId, size: i32) -> Result<()> {
let mut inner = self.inner.lock();
unsafe { inner.remap(size as usize, RemapOptions::new().may_move(true))? };
Ok(())
@@ -82,12 +73,7 @@ impl WlShmPool for ShmPool {
/// https://wayland.app/protocols/wayland#wl_shm_pool:request:destroy
#[tracing::instrument(level = "debug", skip_all)]
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

View File

@@ -2,16 +2,13 @@ use super::{buffer::Buffer, callback::Callback};
use crate::{
BevyMaterial,
core::registry::Registry,
nodes::{
drawable::model::ModelPart,
items::panel::{Geometry, PanelItem, SurfaceId},
},
nodes::{drawable::model::ModelPart, items::panel::Geometry},
wayland::{
Client, Message, MessageSink, WaylandError, WaylandResult,
Message, MessageSink,
core::buffer::BufferUsage,
presentation::{MonotonicTimestamp, PresentationFeedback},
util::{ClientExt, DoubleBuffer},
xdg::backend::XdgBackend,
xdg::{popup::Popup, toplevel::Toplevel},
},
};
use bevy::{
@@ -22,35 +19,24 @@ use bevy::{
use bevy_dmabuf::import::ImportedDmatexs;
use mint::Vector2;
use parking_lot::Mutex;
use std::{
fmt::Display,
sync::{Arc, OnceLock, Weak},
use std::sync::{Arc, OnceLock};
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::{
core::wayland::{wl_output::Transform, wl_surface::*},
stable::presentation_time::wp_presentation_feedback::{Kind, WpPresentationFeedback},
},
},
wire::ObjectId,
};
use waynest::ObjectId;
use waynest_protocols::server::{
core::wayland::{wl_output::Transform, wl_surface::*},
stable::presentation_time::wp_presentation_feedback::{Kind, WpPresentationFeedback},
};
use waynest_server::Client as _;
pub static WL_SURFACE_REGISTRY: Registry<Surface> = Registry::new();
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone)]
pub enum SurfaceRole {
Cursor,
Subsurface,
XdgToplevel,
XdgPopup,
}
impl Display for SurfaceRole {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SurfaceRole::Cursor => f.write_str("SurfaceRole::Cursor"),
SurfaceRole::Subsurface => f.write_str("SurfaceRole::Subsurface"),
SurfaceRole::XdgToplevel => f.write_str("SurfaceRole::XdgToplevel"),
SurfaceRole::XdgPopup => f.write_str("SurfaceRole::XdgPopup"),
}
}
XdgToplevel(Arc<Toplevel>),
XDGPopup(Arc<Popup>),
}
#[derive(Debug, Clone)]
@@ -80,27 +66,18 @@ impl Default for SurfaceState {
}
}
}
impl SurfaceState {
pub fn has_valid_buffer(&self) -> bool {
self.buffer
.as_ref()
.is_some_and(|b| b.buffer.size().x > 0 && b.buffer.size().y > 0)
}
}
// if returning false, don't run this callback again... just remove it
pub type OnCommitCallback = Box<dyn FnMut(&Surface, &SurfaceState) -> bool + Send + Sync>;
#[derive(waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub type OnCommitCallback = Box<dyn Fn(&Surface, &SurfaceState) -> bool + Send + Sync>;
#[derive(Dispatcher)]
pub struct Surface {
pub id: ObjectId,
pub surface_id: OnceLock<SurfaceId>,
state: Mutex<DoubleBuffer<SurfaceState>>,
pub message_sink: MessageSink,
pub role: OnceLock<SurfaceRole>,
pub panel_item: Mutex<Weak<PanelItem<XdgBackend>>>,
// TODO: This should probably be a OnceLock? wayland doesn't support changing the surface role
pub role: Mutex<Option<SurfaceRole>>,
on_commit_handlers: Mutex<Vec<OnCommitCallback>>,
frame_callbacks: Mutex<Vec<Arc<Callback>>>,
material: OnceLock<Handle<BevyMaterial>>,
pending_material_applications: Registry<ModelPart>,
presentation_feedback: Mutex<Vec<Arc<PresentationFeedback>>>,
@@ -108,8 +85,6 @@ pub struct Surface {
impl std::fmt::Debug for Surface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Surface")
.field("id", &self.id)
.field("surface_id", &self.surface_id)
.field("state", &self.state)
.field("message_sink", &self.message_sink)
.field("role", &self.role)
@@ -117,8 +92,6 @@ impl std::fmt::Debug for Surface {
"on_commit_handlers",
&format!("<{} handlers>", self.on_commit_handlers.lock().len()),
)
.field("material", &self.material)
.field("presentation_feedback", &self.presentation_feedback)
.finish()
}
}
@@ -127,50 +100,23 @@ impl Surface {
pub fn new(client: &Client, id: ObjectId) -> Self {
Surface {
id,
surface_id: OnceLock::new(),
state: Default::default(),
message_sink: client.message_sink(),
role: OnceLock::new(),
panel_item: Mutex::new(Weak::default()),
role: Mutex::new(None),
on_commit_handlers: Mutex::new(Vec::new()),
frame_callbacks: Mutex::new(Vec::new()),
material: OnceLock::new(),
pending_material_applications: Registry::new(),
presentation_feedback: Mutex::default(),
}
}
pub async fn try_set_role(
&self,
role: SurfaceRole,
role_error: impl Into<u32>,
) -> WaylandResult<()> {
match self.role.get().cloned() {
Some(current_role) => {
if current_role == role {
Ok(())
} else {
Err(WaylandError::Fatal {
object_id: self.id,
code: role_error.into(),
message: "Surface has an incomparible role",
})
}
}
None => {
let _ = self.role.set(role);
Ok(())
}
}
}
#[tracing::instrument(level = "debug", skip_all)]
pub fn state_lock(&self) -> parking_lot::MutexGuard<'_, DoubleBuffer<SurfaceState>> {
pub fn pending_state(&self) -> parking_lot::MutexGuard<'_, DoubleBuffer<SurfaceState>> {
self.state.lock()
}
#[tracing::instrument(level = "debug", skip_all)]
pub fn add_commit_handler<F: FnMut(&Surface, &SurfaceState) -> bool + Send + Sync + 'static>(
pub fn add_commit_handler<F: Fn(&Surface, &SurfaceState) -> bool + Send + Sync + 'static>(
&self,
handler: F,
) {
@@ -241,9 +187,8 @@ impl Surface {
}
#[tracing::instrument(level = "debug", skip_all)]
pub fn frame_event(&self) {
let callbacks = std::mem::take(&mut *self.frame_callbacks.lock());
if !callbacks.is_empty() {
let _ = self.message_sink.send(Message::Frame(callbacks));
for callback in self.state.lock().current.frame_callbacks.drain(..) {
let _ = self.message_sink.send(Message::Frame(callback));
}
}
// pub fn size(&self) -> Option<Vector2<u32>> {
@@ -255,7 +200,7 @@ impl Surface {
// .map(|b| [b.size.x as u32, b.size.y as u32].into())
// }
// pub async fn release_old_buffer(&self, client: &mut Self::Connection) -> Result<()> {
// pub async fn release_old_buffer(&self, client: &mut Client) -> Result<()> {
// let (old_buffer, object) = {
// let lock = self.state.lock();
@@ -298,16 +243,20 @@ impl Surface {
client: &mut Client,
display_timestamp: MonotonicTimestamp,
refresh_cycle: u64,
) -> WaylandResult<()> {
) -> Result<()> {
let feedbacks = self
.presentation_feedback
.lock()
.drain(..)
.collect::<Vec<_>>();
for feedback in feedbacks {
if let Some(display_id) = client.display().output.get().map(|display| display.id) {
feedback.sync_output(client, feedback.0, display_id).await?;
}
feedback
.sync_output(
client,
feedback.0,
client.display().output.get().unwrap().id,
)
.await?;
let cycle_lo = refresh_cycle as u32;
let cycle_hi = (refresh_cycle >> 32) as u32;
feedback
@@ -328,18 +277,16 @@ impl Surface {
}
}
impl WlSurface for Surface {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_surface:request:attach
#[tracing::instrument(level = "debug", skip_all)]
async fn attach(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
buffer: Option<ObjectId>,
_x: i32,
_y: i32,
) -> WaylandResult<()> {
) -> Result<()> {
self.state.lock().pending.buffer = buffer.and_then(|b| {
let buffer = client.get::<Buffer>(b)?;
let mut usage = Some(BufferUsage::new(client, &buffer));
@@ -355,13 +302,13 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)]
async fn damage(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
@@ -369,11 +316,11 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)]
async fn frame(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
callback_id: ObjectId,
) -> WaylandResult<()> {
let callback = client.insert(callback_id, Callback(callback_id))?;
) -> Result<()> {
let callback = client.insert(callback_id, Callback(callback_id));
self.state.lock().pending.frame_callbacks.push(callback);
Ok(())
}
@@ -382,10 +329,10 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)]
async fn set_opaque_region(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_region: Option<ObjectId>,
) -> WaylandResult<()> {
) -> Result<()> {
// nothing we can really do to repaint behind this so ignore it
Ok(())
}
@@ -394,42 +341,22 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)]
async fn set_input_region(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_region: Option<ObjectId>,
) -> WaylandResult<()> {
) -> Result<()> {
// too complicated to implement this for now so who the hell cares
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:commit
#[tracing::instrument(level = "debug", skip_all)]
async fn commit(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
// we want the upload to complete before we give the image to bevy
let buffer_option = self
.state_lock()
.pending
.buffer
.as_ref()
.map(|b| b.buffer.clone());
if let Some(buffer) = buffer_option {
tokio::task::spawn_blocking(move || buffer.on_commit())
.await
.unwrap();
}
async fn commit(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
self.state.lock().apply();
self.state.lock().pending.frame_callbacks.clear();
let current_state = self.current_state();
self.frame_callbacks
.lock()
.extend(current_state.frame_callbacks.iter().cloned());
let mut handlers = self.on_commit_handlers.lock();
handlers.retain_mut(|f| (f)(self, &current_state));
handlers.retain(|f| (f)(self, &current_state));
Ok(())
}
@@ -437,10 +364,10 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)]
async fn set_buffer_transform(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_transform: Transform,
) -> WaylandResult<()> {
) -> Result<()> {
// we just don't have the output transform or fullscreen at all so this optimization is never needed
Ok(())
}
@@ -449,10 +376,10 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)]
async fn set_buffer_scale(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
scale: i32,
) -> WaylandResult<()> {
) -> Result<()> {
self.state.lock().pending.density = scale as f32;
Ok(())
}
@@ -461,13 +388,13 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)]
async fn damage_buffer(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
@@ -475,27 +402,23 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)]
async fn offset(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:destroy
#[tracing::instrument(level = "debug", skip_all)]
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}
impl Drop for Surface {
fn drop(&mut self) {
self.role.take();
self.role.lock().take();
}
}

View File

@@ -1,11 +1,15 @@
use crate::wayland::{Client, WaylandResult, core::surface::Surface};
use crate::wayland::core::surface::Surface;
use mint::Vector2;
use std::sync::Arc;
use waynest::ObjectId;
pub use waynest_protocols::server::core::wayland::wl_touch::*;
pub use waynest::server::protocol::core::wayland::wl_touch::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
use super::seat::fixed_from_f32;
#[derive(Debug, Dispatcher)]
pub struct Touch(pub ObjectId);
impl Touch {
pub async fn handle_touch_down(
@@ -14,7 +18,7 @@ impl Touch {
surface: Arc<Surface>,
id: u32,
position: Vector2<f32>,
) -> WaylandResult<()> {
) -> Result<()> {
let serial = client.next_event_serial();
self.down(
client,
@@ -23,8 +27,8 @@ impl Touch {
0,
surface.id,
id as i32,
(position.x as f64).into(),
(position.y as f64).into(),
fixed_from_f32(position.x),
fixed_from_f32(position.y),
)
.await?;
self.frame(client, self.0).await
@@ -35,39 +39,37 @@ impl Touch {
client: &mut Client,
id: u32,
position: Vector2<f32>,
) -> WaylandResult<()> {
) -> Result<()> {
self.motion(
client,
self.0,
0,
id as i32,
(position.x as f64).into(),
(position.y as f64).into(),
fixed_from_f32(position.x),
fixed_from_f32(position.y),
)
.await?;
self.frame(client, self.0).await
}
pub async fn handle_touch_up(&self, client: &mut Client, id: u32) -> WaylandResult<()> {
pub async fn handle_touch_up(&self, client: &mut Client, id: u32) -> Result<()> {
let serial = client.next_event_serial();
self.up(client, self.0, serial, 0, id as i32).await?;
self.frame(client, self.0).await
}
pub async fn reset(&self, client: &mut Client) -> WaylandResult<()> {
pub async fn reset(&self, client: &mut Client) -> Result<()> {
self.frame(client, self.0).await
}
}
impl WlTouch for Touch {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_touch:request:release
async fn release(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
_client: &mut waynest::server::Client,
_sender_id: waynest::wire::ObjectId,
) -> Result<()> {
Ok(())
}
}

View File

@@ -1,7 +1,7 @@
#![allow(unused)]
use crate::wayland::{
MessageSink, WaylandResult,
MessageSink,
core::{
callback::{Callback, WlCallback},
output::Output,
@@ -14,12 +14,13 @@ use std::{
sync::{Arc, OnceLock},
time::Instant,
};
use waynest::ObjectId;
pub use waynest_protocols::server::core::wayland::wl_display::*;
use waynest_server::Client as _;
pub use waynest::server::protocol::core::wayland::wl_display::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Dispatcher)]
pub struct Display {
pub message_sink: MessageSink,
pub pid: Option<i32>,
@@ -44,15 +45,13 @@ impl Display {
}
}
impl WlDisplay for Display {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_display:request:sync
async fn sync(
&self,
client: &mut Self::Connection,
client: &mut Client,
sender_id: ObjectId,
callback_id: ObjectId,
) -> WaylandResult<()> {
) -> Result<()> {
let serial = client.next_event_serial();
Callback(callback_id)
.done(client, callback_id, serial)
@@ -66,11 +65,11 @@ impl WlDisplay for Display {
/// https://wayland.app/protocols/wayland#wl_display:request:get_registry
async fn get_registry(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
registry_id: ObjectId,
) -> WaylandResult<()> {
let registry = client.insert(registry_id, Registry)?;
) -> Result<()> {
let registry = client.insert(registry_id, Registry);
registry.advertise_globals(client, registry_id).await?;

View File

@@ -6,15 +6,14 @@ use bevy::{
};
use bevy_dmabuf::{
dmatex::{Dmatex, Resolution},
import::{
DmatexUsage, DropCallback, ImportError, ImportedDmatexs, ImportedTexture, import_texture,
},
import::{DropCallback, ImportError, ImportedDmatexs, ImportedTexture, import_texture},
};
use drm_fourcc::DrmFourcc;
use mint::Vector2;
use parking_lot::Mutex;
use std::sync::{Arc, OnceLock};
use waynest_protocols::server::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::Flags;
use tracing::info;
use waynest::server::protocol::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::Flags;
/// Parameters for a shared memory buffer
pub struct DmabufBacking {
@@ -47,7 +46,6 @@ impl DmabufBacking {
dev,
dmatex,
DropCallback(None),
DmatexUsage::Sampling,
)?)),
})
}
@@ -84,14 +82,14 @@ impl DmabufBacking {
dmatexes: &ImportedDmatexs,
images: &mut Assets<Image>,
) -> Option<Handle<Image>> {
info!("updating dmabuf tex");
self.pending_imported_dmatex
.lock()
.take()
.map(|tex| dmatexes.insert_imported_dmatex(images, tex))
.inspect(|handle| {
_ = self.tex.set(handle.clone());
});
self.tex.get().cloned()
})
}
pub fn is_transparent(&self) -> bool {

View File

@@ -1,6 +1,5 @@
use super::buffer_backing::DmabufBacking;
use crate::wayland::{
Client, WaylandError, WaylandResult,
core::buffer::{Buffer, BufferBacking},
util::ClientExt,
};
@@ -9,19 +8,22 @@ use drm_fourcc::DrmFourcc;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::os::fd::{AsRawFd, OwnedFd};
use waynest::ObjectId;
use waynest_protocols::server::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::{
Error, Flags, ZwpLinuxBufferParamsV1,
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::{
Error, Flags, ZwpLinuxBufferParamsV1,
},
},
wire::ObjectId,
};
use waynest_server::Client as _;
/// Parameters for creating a DMA-BUF-based wl_buffer
///
/// This is a temporary object that collects dmabufs and other parameters
/// that together form a single logical buffer. The object may eventually
/// create one wl_buffer unless cancelled by destroying it.
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct BufferParams {
pub id: ObjectId,
pub(super) planes: Mutex<FxHashMap<u32, DmatexPlane>>,
@@ -39,13 +41,7 @@ impl BufferParams {
}
impl ZwpLinuxBufferParamsV1 for BufferParams {
type Connection = Client;
async fn destroy(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
tracing::info!("Destroying BufferParams {:?}", self.id);
Ok(())
}
@@ -53,7 +49,7 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
#[tracing::instrument(level = "debug", skip_all)]
async fn add(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
fd: OwnedFd,
plane_idx: u32,
@@ -61,7 +57,7 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
stride: u32,
modifier_hi: u32,
modifier_lo: u32,
) -> WaylandResult<()> {
) -> Result<()> {
let fd_num = fd.as_raw_fd();
tracing::info!(
"Adding plane {} with fd {} to BufferParams {:?}",
@@ -79,7 +75,7 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
plane_idx,
self.id
);
return Err(crate::wayland::WaylandError::MissingObject(self.id));
return Err(waynest::server::Error::MissingObject(self.id));
}
// Create plane with the provided parameters
@@ -98,13 +94,13 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
#[tracing::instrument(level = "debug", skip_all)]
async fn create(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
width: i32,
height: i32,
format: u32,
flags: Flags,
) -> WaylandResult<()> {
) -> Result<()> {
tracing::info!("Creating buffer from BufferParams {:?}", self.id);
// Create the buffer with DMA-BUF backing using self as the backing
let size = [width as u32, height as u32].into();
@@ -121,7 +117,7 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
});
match buffer {
Ok(buffer) => self.created(client, self.id, buffer?.id).await,
Ok(buffer) => self.created(client, self.id, buffer.id).await,
Err(_) => {
client.remove(self.id);
self.failed(client, self.id).await
@@ -132,14 +128,14 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
#[tracing::instrument(level = "debug", skip_all)]
async fn create_immed(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
client: &mut Client,
sender_id: ObjectId,
buffer_id: ObjectId,
width: i32,
height: i32,
format: u32,
flags: Flags,
) -> WaylandResult<()> {
) -> Result<()> {
// TODO: terminate client on fail, or send a fail event or something
// Create the buffer with DMA-BUF backing using self as the backing
match DmabufBacking::from_params(
@@ -149,15 +145,18 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
flags,
) {
Ok(backing) => {
Buffer::new(client, buffer_id, BufferBacking::Dmabuf(backing))?;
Buffer::new(client, buffer_id, BufferBacking::Dmabuf(backing));
}
Err(e) => {
client
.protocol_error(
sender_id,
buffer_id,
Error::Incomplete as u32,
format!("Failed to import dmabuf because {e}"),
)
.await?;
tracing::error!("Failed to import dmabuf because {e}");
return Err(WaylandError::Fatal {
object_id: buffer_id,
code: Error::Incomplete as u32,
message: "Failed to import dmabuf",
});
}
}
Ok(())

View File

@@ -1,22 +1,26 @@
use super::Dmabuf;
use crate::wayland::{Client, WaylandResult, vulkano_data::VULKANO_CONTEXT};
use crate::wayland::vulkano_data::VULKANO_CONTEXT;
use memfd::MemfdOptions;
use std::{
io::Write,
os::fd::{AsFd as _, FromRawFd, IntoRawFd, OwnedFd},
os::fd::{FromRawFd, IntoRawFd, OwnedFd},
sync::Arc,
};
use waynest::ObjectId;
use waynest_protocols::server::stable::linux_dmabuf_v1::zwp_linux_dmabuf_feedback_v1::{
TrancheFlags, ZwpLinuxDmabufFeedbackV1,
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::stable::linux_dmabuf_v1::zwp_linux_dmabuf_feedback_v1::{
TrancheFlags, ZwpLinuxDmabufFeedbackV1,
},
},
wire::ObjectId,
};
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct DmabufFeedback(pub Arc<Dmabuf>);
impl DmabufFeedback {
#[tracing::instrument(level = "debug", skip_all)]
pub async fn send_params(&self, client: &mut Client, sender_id: ObjectId) -> WaylandResult<()> {
pub async fn send_params(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
let num_formats = self.0.formats.len();
// Send format table first
self.send_format_table(client, sender_id).await?;
@@ -59,18 +63,16 @@ impl DmabufFeedback {
}
#[tracing::instrument(level = "debug", skip_all)]
pub async fn send_format_table(
&self,
client: &mut Client,
sender_id: ObjectId,
) -> WaylandResult<()> {
pub async fn send_format_table(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
// Format + modifier pair (16 bytes):
// - format: u32
// - padding: 4 bytes
// - modifier: u64
let size = self.0.formats.len() as u32 * 16u32;
// Create a temporary file for the format table
let mfd = MemfdOptions::default().create("stardustxr-format-table")?;
let mfd = MemfdOptions::default()
.create("stardustxr-format-table")
.map_err(|e| waynest::server::Error::Custom(e.to_string()))?;
mfd.as_file().set_len(size as u64)?;
@@ -81,21 +83,20 @@ impl DmabufFeedback {
mfd.as_file().write_all(&0_u32.to_ne_bytes())?;
mfd.as_file().write_all(&modifier.to_ne_bytes())?;
}
let fd = unsafe { OwnedFd::from_raw_fd(mfd.into_raw_fd()) };
self.format_table(client, sender_id, fd.as_fd(), size)
.await?;
self.format_table(
client,
sender_id,
unsafe { OwnedFd::from_raw_fd(mfd.into_raw_fd()) },
size,
)
.await?;
Ok(())
}
}
impl ZwpLinuxDmabufFeedbackV1 for DmabufFeedback {
type Connection = crate::wayland::Client;
async fn destroy(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

View File

@@ -2,24 +2,23 @@ pub mod buffer_backing;
pub mod buffer_params;
pub mod feedback;
use super::vulkano_data::VULKANO_CONTEXT;
use crate::{
core::registry::Registry,
wayland::{Client, WaylandError, WaylandResult},
};
use bevy_dmabuf::{
format_mapping::{drm_fourcc_to_vk_format, vk_format_to_srgb},
wgpu_init::vulkan_to_wgpu,
};
use std::sync::LazyLock;
use super::{util::ClientExt, vulkano_data::VULKANO_CONTEXT};
use crate::core::registry::Registry;
use bevy_dmabuf::{format_mapping::drm_fourcc_to_vk_format, wgpu_init::vulkan_to_wgpu};
use buffer_params::BufferParams;
use drm_fourcc::DrmFourcc;
use feedback::DmabufFeedback;
use rustc_hash::FxHashSet;
use std::sync::LazyLock;
use vulkano::format::FormatFeatures;
use waynest::ObjectId;
use waynest_protocols::server::stable::linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1;
use waynest_server::Client as _;
use waynest::{
server::{
Client, Dispatcher, Error, Result,
protocol::stable::linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
},
wire::ObjectId,
};
pub static DMABUF_FORMATS: LazyLock<Vec<(DrmFourcc, u64)>> = LazyLock::new(|| {
let vk = VULKANO_CONTEXT.wait();
@@ -29,7 +28,6 @@ pub static DMABUF_FORMATS: LazyLock<Vec<(DrmFourcc, u64)>> = LazyLock::new(|| {
.copied()
.filter_map(|f| Some((f, drm_fourcc_to_vk_format(f)?)))
.filter(|(_, vk_format)| vulkan_to_wgpu(*vk_format).is_some())
.filter(|(_, vk_format)| vk_format_to_srgb(*vk_format).is_some())
.filter_map(|(f, vk_format)| {
Some((
f,
@@ -74,8 +72,7 @@ pub static DMABUF_FORMATS: LazyLock<Vec<(DrmFourcc, u64)>> = LazyLock::new(|| {
/// - Coherency for read access in dmabuf data
/// - Proper lifetime management of dmabuf file descriptors
/// - Safe handling of buffer attachments
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct Dmabuf {
// Track supported formats and modifiers
// formats: Mutex<FxHashSet<DrmFormat>>,
@@ -87,7 +84,7 @@ pub struct Dmabuf {
impl Dmabuf {
/// Create a new DMA-BUF interface instance
pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> WaylandResult<Self> {
pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> Result<Self> {
let dmabuf = Self {
active_params: Registry::new(),
version,
@@ -121,56 +118,53 @@ impl Dmabuf {
}
impl ZwpLinuxDmabufV1 for Dmabuf {
type Connection = crate::wayland::Client;
async fn destroy(
&self,
_client: &mut Self::Connection,
sender_id: ObjectId,
) -> WaylandResult<()> {
async fn destroy(&self, _client: &mut Client, sender_id: ObjectId) -> Result<()> {
self.remove_params(sender_id);
Ok(())
}
async fn create_params(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
params_id: ObjectId,
) -> WaylandResult<()> {
) -> Result<()> {
// Create new buffer parameters object
let params = client.insert(params_id, BufferParams::new(params_id))?;
let params = client.insert(params_id, BufferParams::new(params_id));
self.active_params.add_raw(&params);
Ok(())
}
async fn get_default_feedback(
&self,
client: &mut Self::Connection,
client: &mut Client,
sender_id: ObjectId,
id: ObjectId,
) -> WaylandResult<()> {
) -> Result<()> {
if self.version < 3 {
return Err(WaylandError::Fatal {
object_id: id,
code: 71,
message: "Can't call get_default_feedback on version < 4 of dmabuf",
});
client
.protocol_error(
sender_id,
id,
71,
"Can't call get_default_feedback on version < 4 of dmabuf".into(),
)
.await?;
return Err(Error::Custom("Protocol error".into()));
}
// Create feedback object for default (non-surface-specific) settings
let feedback =
client.insert(id, DmabufFeedback(client.get::<Dmabuf>(sender_id).unwrap()))?;
let feedback = client.insert(id, DmabufFeedback(client.get::<Dmabuf>(sender_id).unwrap()));
feedback.send_params(client, id).await?;
Ok(())
}
async fn get_surface_feedback(
&self,
client: &mut Self::Connection,
client: &mut Client,
sender_id: ObjectId,
id: ObjectId,
_surface: ObjectId,
) -> WaylandResult<()> {
) -> Result<()> {
// Create feedback object for surface-specific settings
// Note: Surface-specific feedback could be optimized based on the surface's
// requirements, but for now we use the same feedback as default

View File

@@ -1,5 +1,4 @@
use crate::wayland::{
Client, WaylandResult,
core::buffer::{Buffer, BufferBacking},
dmabuf::{DMABUF_FORMATS, buffer_backing::DmabufBacking},
vulkano_data::VULKANO_CONTEXT,
@@ -7,16 +6,17 @@ use crate::wayland::{
use bevy_dmabuf::dmatex::{Dmatex, DmatexPlane, Resolution};
use rustc_hash::FxHashSet;
use std::os::fd::OwnedFd;
use waynest::ObjectId;
use waynest_protocols::server::mesa::drm::wl_drm::*;
use waynest::{
server::{Client, Dispatcher, Result, protocol::external::drm::wl_drm::*},
wire::ObjectId,
};
#[derive(Debug, waynest_server::RequestDispatcher, Default)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher, Default)]
pub struct MesaDrm {
version: u32,
}
impl MesaDrm {
pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> WaylandResult<MesaDrm> {
pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> Result<MesaDrm> {
let drm = MesaDrm { version };
let path = {
@@ -46,20 +46,13 @@ impl MesaDrm {
}
}
impl WlDrm for MesaDrm {
type Connection = Client;
async fn authenticate(
&self,
client: &mut Self::Connection,
sender_id: ObjectId,
_id: u32,
) -> WaylandResult<()> {
async fn authenticate(&self, client: &mut Client, sender_id: ObjectId, _id: u32) -> Result<()> {
self.authenticated(client, sender_id).await
}
async fn create_buffer(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_id: ObjectId,
_name: u32,
@@ -67,14 +60,14 @@ impl WlDrm for MesaDrm {
_height: i32,
_stride: u32,
_format: u32,
) -> WaylandResult<()> {
) -> Result<()> {
tracing::error!("Tried to create non-prime wl_drm buffer!");
Ok(())
}
async fn create_planar_buffer(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_id: ObjectId,
_name: u32,
@@ -87,14 +80,14 @@ impl WlDrm for MesaDrm {
_stride1: i32,
_offset2: i32,
_stride2: i32,
) -> WaylandResult<()> {
) -> Result<()> {
tracing::error!("Tried to create non-prime wl_drm buffer!");
Ok(())
}
async fn create_prime_buffer(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
buffer_id: ObjectId,
name: OwnedFd,
@@ -107,7 +100,7 @@ impl WlDrm for MesaDrm {
_stride1: i32,
_offset2: i32,
_stride2: i32,
) -> WaylandResult<()> {
) -> Result<()> {
// TODO: actual error checking
let _ = DmabufBacking::new(Dmatex {

View File

@@ -5,19 +5,21 @@ mod mesa_drm;
mod presentation;
mod registry;
mod util;
mod viewporter;
mod vulkano_data;
mod xdg;
use crate::core::error::ServerError;
use crate::core::registry::OwnedRegistry;
use crate::get_time;
use crate::nodes::drawable::model::ModelNodeSystemSet;
use crate::wayland::core::seat::SeatMessage;
use crate::wayland::core::surface::Surface;
use crate::wayland::presentation::MonotonicTimestamp;
use crate::wayland::util::ClientExt;
use crate::{BevyMaterial, core::task};
use crate::{
BevyMaterial,
core::{
error::{Result, ServerError},
task,
},
};
use bevy::app::{App, Plugin, Update};
use bevy::ecs::schedule::IntoScheduleConfigs;
use bevy::ecs::system::{Local, Res, ResMut};
@@ -27,152 +29,46 @@ use bevy::render::{Render, RenderApp};
use bevy::{asset::Assets, ecs::resource::Resource, image::Image};
use bevy_dmabuf::import::ImportedDmatexs;
use bevy_mod_openxr::render::end_frame;
use bevy_mod_openxr::resources::{OxrFrameState, OxrInstance, Pipelined};
use bevy_mod_xr::session::XrRenderSet;
use cluFlock::{FlockLock, ToFlock};
use core::buffer::BufferUsage;
use core::{buffer::Buffer, callback::Callback, surface::WL_SURFACE_REGISTRY};
use display::Display;
use mint::Vector2;
use pin_project_lite::pin_project;
use std::fs::File;
use std::io::ErrorKind;
use std::mem::MaybeUninit;
use std::time::Duration;
use std::{
io,
fs::{self, OpenOptions},
io::{self, ErrorKind},
os::unix::fs::OpenOptionsExt,
path::PathBuf,
sync::{Arc, OnceLock},
};
use tokio::{net::UnixStream, sync::mpsc, task::AbortHandle};
use tokio_stream::{Stream, StreamExt};
use tokio_stream::StreamExt;
use tracing::{debug_span, instrument};
use vulkano_data::setup_vulkano_context;
use waynest::{Connection, Socket};
use waynest::{ObjectId, ProtocolError};
use waynest_protocols::server::core::wayland::wl_display::WlDisplay;
use waynest_server::{Client as _, Listener, Store, StoreError};
use waynest::{
server::{
self,
protocol::{
core::wayland::{wl_buffer::WlBuffer, wl_callback::WlCallback, wl_display::WlDisplay},
stable::xdg_shell::xdg_toplevel::XdgToplevel,
},
},
wire::{DecodeError, ObjectId},
};
use xdg::toplevel::Toplevel;
pub static WAYLAND_DISPLAY: OnceLock<PathBuf> = OnceLock::new();
#[derive(thiserror::Error, Debug)]
pub enum WaylandError {
// #[error("Listener error: {0}")]
// Listener(#[from] waynest_server::ListenerError),
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("Decode error: {0}")]
DecodeError(#[from] waynest::ProtocolError),
#[error("Client requested unknown global: {0}")]
UnknownGlobal(u32),
#[error("No object found with ID {0}")]
MissingObject(ObjectId),
#[error("Fatal error on object {object_id} with code {code}: {message}")]
Fatal {
object_id: ObjectId,
code: u32,
message: &'static str,
},
#[error("Memfd error: {0}")]
MemfdError(#[from] memfd::Error),
#[error("Dmabuf import error: {0}")]
DmabufImport(#[from] bevy_dmabuf::import::ImportError),
#[error("Server error: {0}")]
Server(#[from] ServerError),
#[error("Failed to Insert Object")]
FailedToInsertObject,
}
impl<T: Clone> From<StoreError<T>> for WaylandError {
fn from(_value: StoreError<T>) -> Self {
Self::FailedToInsertObject
impl From<waynest::server::Error> for ServerError {
fn from(err: waynest::server::Error) -> Self {
ServerError::WaylandError(err)
}
}
pin_project! {
pub struct Client {
store: Store<Client, WaylandError>,
#[pin]
connection: Socket,
next_event_serial: u32,
}
}
impl Connection for Client {
type Error = WaylandError;
fn fd(&mut self) -> Result<std::os::unix::prelude::OwnedFd, <Self as Connection>::Error> {
Ok(self.connection.fd()?)
}
}
impl Stream for Client {
type Item = <Socket as Stream>::Item;
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
// <Socket as Stream>::poll_next(self.project().connection, cx)
self.project().connection.poll_next(cx)
}
}
impl futures_sink::Sink<waynest::Message> for Client {
type Error = ProtocolError;
fn poll_ready(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.project().connection.poll_ready(cx)
}
fn start_send(
self: std::pin::Pin<&mut Self>,
item: waynest::Message,
) -> Result<(), Self::Error> {
self.project().connection.start_send(item)
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.project().connection.poll_flush(cx)
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.project().connection.poll_close(cx)
}
}
impl Client {
fn new(unix_stream: UnixStream) -> tokio::io::Result<Self> {
Ok(Self {
store: Store::new(),
connection: Socket::new(unix_stream.into_std()?)?,
next_event_serial: 0,
})
}
pub fn next_event_serial(&mut self) -> u32 {
let prev = self.next_event_serial;
self.next_event_serial = self.next_event_serial.wrapping_add(1);
prev
}
}
impl waynest_server::Client for Client {
type Store = Store<Client, WaylandError>;
fn store(&self) -> &Self::Store {
&self.store
}
fn store_mut(&mut self) -> &mut Self::Store {
&mut self.store
}
}
pub fn get_free_wayland_socket_path() -> Option<(PathBuf, File)> {
pub fn get_free_wayland_socket_path() -> Option<(PathBuf, FlockLock<File>)> {
// Use XDG runtime directory for secure, user-specific sockets
let base_dirs = directories::BaseDirs::new()?;
let runtime_dir = base_dirs.runtime_dir()?;
@@ -182,12 +78,21 @@ pub fn get_free_wayland_socket_path() -> Option<(PathBuf, File)> {
let socket_path = runtime_dir.join(format!("wayland-{display}"));
let socket_lock_path = runtime_dir.join(format!("wayland-{display}.lock"));
let Ok(lock) = File::create(&socket_lock_path) else {
// Open lock file without truncation to preserve existing locks
let Ok(lock) = OpenOptions::new()
.create(true)
.truncate(false) // Prevent destroying other processes' locks
.read(true)
.write(true)
.mode(0o660) // Match Wayland-compositor permissions
.open(&socket_lock_path)
else {
continue;
};
if lock.try_lock().is_err() {
continue;
// Atomic mutual exclusion: fail if another process holds the lock\
let Ok(lock) = lock.try_exclusive_lock() else {
continue; // Lock held by active compositor
};
// Check for zombie sockets (file exists but nothing listening)
@@ -196,7 +101,7 @@ pub fn get_free_wayland_socket_path() -> Option<(PathBuf, File)> {
Ok(_) => continue, // Active compositor found - skip
Err(e) if e.kind() == ErrorKind::ConnectionRefused => {
// Stale socket - safe to remove since we hold the lock
let _ = std::fs::remove_file(&socket_path);
let _ = fs::remove_file(&socket_path);
}
Err(_) => continue, // Transient error - conservative skip
}
@@ -209,17 +114,15 @@ pub fn get_free_wayland_socket_path() -> Option<(PathBuf, File)> {
None // Exhausted all conventional display numbers
}
pub type WaylandResult<T, E = WaylandError> = std::result::Result<T, E>;
pub enum Message {
Frame(Vec<Arc<Callback>>),
Disconnect,
Frame(Arc<Callback>),
ReleaseBuffer(Arc<Buffer>),
CloseToplevel(Arc<Toplevel>),
ResizeToplevel {
toplevel: Arc<Toplevel>,
size: Option<Vector2<u32>>,
},
ReconfigureToplevel(Arc<Toplevel>),
SetToplevelVisualActive {
toplevel: Arc<Toplevel>,
active: bool,
@@ -239,92 +142,87 @@ struct WaylandClient {
abort_handle: AbortHandle,
}
impl WaylandClient {
pub fn from_stream(socket: UnixStream) -> WaylandResult<Self> {
pub fn from_stream(socket: UnixStream) -> Result<Self> {
let pid = socket.peer_cred().ok().and_then(|c| c.pid());
let exe = pid.and_then(|pid| std::fs::read_link(format!("/proc/{pid}/exe")).ok());
let mut client = Client::new(socket)?;
let mut client = server::Client::new(socket)?;
let (message_sink, message_source) = mpsc::unbounded_channel();
client.insert(ObjectId::DISPLAY, Display::new(message_sink, pid))?;
let pid_printable = pid
.map(|pid| pid.to_string())
.unwrap_or_else(|| "??".to_string());
let exe_printable = exe
.and_then(|exe| {
exe.file_name()
.and_then(|exe| exe.to_str())
.map(|exe| exe.to_string())
})
.unwrap_or_else(|| "??".to_string());
client.insert(ObjectId::DISPLAY, Display::new(message_sink, pid));
let abort_handle = task::new(
|| format!("Wayland client \"{exe_printable}\" dispatch, pid={pid_printable}"),
Self::dispatch_loop(client, message_source),
|| "wayland client",
Self::handle_client_messages(client, message_source),
)?
.abort_handle();
Ok(WaylandClient { abort_handle })
}
async fn dispatch_loop(
mut client: Client,
async fn handle_client_messages(
mut client: server::Client,
mut render_message_rx: mpsc::UnboundedReceiver<Message>,
) -> WaylandResult<()> {
) -> Result<()> {
loop {
tokio::select! {
biased;
// send all queued up messages
msg = render_message_rx.recv() => {
let Some(msg) = msg else {
// Render message channel closed, end the dispatch loop
return Ok(());
};
Self::handle_render_message(&mut client, msg).await?;
}
// handle the next message
msg = client.try_next() => {
let Some(mut msg) = msg? else {
// Client disconnected, end the dispatch loop
return Ok(());
};
if let Err(e) = client
.get_raw(msg.object_id())
.ok_or(WaylandError::MissingObject(msg.object_id()))?
.dispatch_request(&mut client, msg.object_id(), &mut msg)
.await
{
if let WaylandError::Fatal { object_id, code, message } = e {
client.display().error(&mut client, ObjectId::DISPLAY, object_id, code, message.to_string()).await?;
}
tracing::error!("Wayland: {e}");
return Err(e);
if let Some(msg) = msg {
Self::handle_render_message(&mut client, msg).await?;
}
}
};
// handle the next message
msg = client.next_message() => {
match msg {
Ok(Some(mut msg)) => {
if let Err(e) = client.handle_message(&mut msg).await {
tracing::error!("Wayland: Error handling message: {:?}", e);
break;
}
}
Err(e) => {
// wayland clients really aren't nice when disconnecting properly, are they? :p
if let server::Error::Decode(DecodeError::IoError(e)) = &e && e.kind() == io::ErrorKind::ConnectionReset {
if let Some(pid) = client.get::<Display>(ObjectId::DISPLAY).and_then(|d| d.pid) {
tracing::info!("Wayland: Client with pid: {pid} disconnected from server");
} else {
tracing::info!("Wayland: Unknown client disconnected from server");
}
break;
}
tracing::error!("Wayland: Error reading message: {:?}", e);
break;
}
Ok(None) => {
if let Some(pid) = client.get::<Display>(ObjectId::DISPLAY).and_then(|d| d.pid) {
tracing::info!("Wayland: Client with pid: {pid} disconnected from server");
} else {
tracing::info!("Wayland: Unknown client disconnected from server");
}
// Message stream ended
break;
}
}
}
}
}
Ok(())
}
async fn handle_render_message(client: &mut Client, message: Message) -> WaylandResult<()> {
use waynest_protocols::server::core::wayland::wl_buffer::WlBuffer;
use waynest_protocols::server::core::wayland::wl_callback::WlCallback;
use waynest_protocols::server::core::wayland::wl_display::WlDisplay;
use waynest_protocols::server::stable::xdg_shell::xdg_toplevel::XdgToplevel;
async fn handle_render_message(
client: &mut server::Client,
message: Message,
) -> Result<bool, waynest::server::Error> {
match message {
Message::Frame(callbacks) => {
Message::Disconnect => return Ok(true),
Message::Frame(callback) => {
let now = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic);
let now = Duration::new(now.tv_sec as u64, now.tv_nsec as u32);
let ms = (now.as_millis() % (u32::MAX as u128)) as u32;
for callback in callbacks {
callback.done(client, callback.0, ms).await?;
client
.get::<Display>(ObjectId::DISPLAY)
.unwrap()
.delete_id(client, ObjectId::DISPLAY, callback.0.as_raw())
.await?;
client.remove(callback.0);
}
callback.done(client, callback.0, ms).await?;
client
.get::<Display>(ObjectId::DISPLAY)
.unwrap()
.delete_id(client, ObjectId::DISPLAY, callback.0.as_raw())
.await?;
client.remove(callback.0);
}
Message::ReleaseBuffer(buffer) => {
buffer.release(client, buffer.id).await?;
@@ -336,9 +234,6 @@ impl WaylandClient {
toplevel.set_size(size);
toplevel.reconfigure(client).await?;
}
Message::ReconfigureToplevel(toplevel) => {
toplevel.reconfigure(client).await?;
}
Message::SetToplevelVisualActive { toplevel, active } => {
toplevel.set_activated(active);
toplevel.reconfigure(client).await?;
@@ -358,7 +253,7 @@ impl WaylandClient {
.await?;
}
}
Ok(())
Ok(false)
}
}
impl Drop for WaylandClient {
@@ -369,32 +264,30 @@ impl Drop for WaylandClient {
#[derive(Debug, Resource)]
pub struct Wayland {
_lockfile: File,
_lockfile: FlockLock<File>,
abort_handle: AbortHandle,
}
impl Wayland {
pub fn new() -> color_eyre::eyre::Result<Self> {
let (socket_path, _lockfile) = get_free_wayland_socket_path().ok_or(WaylandError::Io(
std::io::ErrorKind::AddrNotAvailable.into(),
))?;
pub fn new() -> Result<Self> {
let (socket_path, _lockfile) =
get_free_wayland_socket_path().ok_or(ServerError::WaylandError(
waynest::server::Error::IoError(std::io::ErrorKind::AddrNotAvailable.into()),
))?;
let _ = WAYLAND_DISPLAY.set(socket_path.clone());
let listener = waynest_server::Listener::new_with_path(&socket_path)?;
let _ = WAYLAND_DISPLAY.set(listener.socket_path().to_path_buf());
let listener =
server::Listener::new_with_path(&socket_path).map_err(ServerError::WaylandError)?;
let abort_handle = task::new(
|| "Wayland socket accept loop",
Self::handle_wayland_loop(listener),
)?
.abort_handle();
let abort_handle =
task::new(|| "wayland loop", Self::handle_wayland_loop(listener))?.abort_handle();
Ok(Self {
_lockfile,
abort_handle,
})
}
async fn handle_wayland_loop(mut listener: Listener) -> WaylandResult<()> {
async fn handle_wayland_loop(mut listener: server::Listener) -> Result<()> {
let mut clients = Vec::new();
loop {
if let Ok(Some(stream)) = listener.try_next().await {
@@ -487,39 +380,11 @@ fn update_graphics(
}
#[instrument(level = "debug", name = "Wayland frame", skip_all)]
fn submit_frame_timings(
mut frame_count: Local<u64>,
instance: Option<Res<OxrInstance>>,
frame_state: Option<Res<OxrFrameState>>,
pipelined: Option<Res<Pipelined>>,
) {
fn submit_frame_timings(mut frame_count: Local<u64>) {
*frame_count += 1;
let display_timestamp = frame_state
.and_then(|state| Some((state, instance?)))
.and_then(|(state, instance)| {
instance
.exts()
.khr_convert_timespec_time
.and_then(|v| unsafe {
let mut out = MaybeUninit::uninit();
let result = (v.convert_time_to_timespec_time)(
instance.as_raw(),
get_time(pipelined.is_some(), &state),
out.as_mut_ptr(),
);
if result != openxr::sys::Result::SUCCESS {
return None;
}
let v = out.assume_init();
Some(rustix::time::Timespec {
tv_sec: v.tv_sec,
tv_nsec: v.tv_nsec,
})
})
})
.unwrap_or_else(|| rustix::time::clock_gettime(rustix::time::ClockId::Monotonic))
.into();
for surface in WL_SURFACE_REGISTRY.get_valid_contents() {
let display_timestamp =
rustix::time::clock_gettime(rustix::time::ClockId::Monotonic).into();
surface.submit_presentation_feedback(display_timestamp, *frame_count);
}
}

View File

@@ -1,13 +1,16 @@
use crate::wayland::WaylandResult;
use crate::wayland::core::surface::Surface;
use rustix::fs::Timespec;
use waynest::ObjectId;
use waynest_protocols::server::stable::presentation_time::{
wp_presentation::*, wp_presentation_feedback::*,
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::stable::presentation_time::{
wp_presentation::WpPresentation, wp_presentation_feedback::WpPresentationFeedback,
},
},
wire::ObjectId,
};
use waynest_server::Client as _;
#[derive(Clone, Copy, Debug)]
use crate::wayland::core::surface::Surface;
pub struct MonotonicTimestamp {
secs: u64,
subsec_nanos: u32,
@@ -33,49 +36,31 @@ impl From<Timespec> for MonotonicTimestamp {
}
}
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Presentation {
id: ObjectId,
}
impl Presentation {
pub fn new(id: ObjectId) -> Self {
Self { id }
}
}
#[derive(Debug, Dispatcher)]
pub struct Presentation;
impl WpPresentation for Presentation {
type Connection = crate::wayland::Client;
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn feedback(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
surface: ObjectId,
id: ObjectId,
) -> WaylandResult<()> {
) -> Result<()> {
let Some(surface) = client.get::<Surface>(surface) else {
tracing::error!("unable to get surface#{surface}");
return Ok(());
};
let feedback = client.insert(id, PresentationFeedback(id))?;
let feedback = client.insert(id, PresentationFeedback(id));
surface.add_presentation_feedback(feedback);
Ok(())
}
}
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct PresentationFeedback(pub ObjectId);
impl WpPresentationFeedback for PresentationFeedback {
type Connection = crate::wayland::Client;
}
impl WpPresentationFeedback for PresentationFeedback {}

View File

@@ -1,6 +1,4 @@
use crate::wayland::{Client, WaylandResult};
use crate::wayland::{
WaylandError,
core::{
compositor::{Compositor, WlCompositor},
data_device::DataDeviceManager,
@@ -12,20 +10,22 @@ use crate::wayland::{
mesa_drm::MesaDrm,
presentation::Presentation,
util::ClientExt,
viewporter::Viewporter,
xdg::wm_base::{WmBase, XdgWmBase},
};
use waynest::{NewId, ObjectId};
use waynest_protocols::server::{
core::wayland::{wl_data_device_manager::WlDataDeviceManager, wl_registry::*},
mesa::drm::wl_drm::WlDrm,
stable::{
linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
presentation_time::wp_presentation::WpPresentation,
viewporter::wp_viewporter::WpViewporter,
use waynest::{
server::{
Client, Dispatcher, Error, Result,
protocol::{
core::wayland::{wl_data_device_manager::WlDataDeviceManager, wl_registry::*},
external::drm::wl_drm::WlDrm,
stable::{
linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
presentation_time::wp_presentation::WpPresentation,
},
},
},
wire::{NewId, ObjectId},
};
use waynest_server::Client as _;
struct RegistryGlobals;
impl RegistryGlobals {
@@ -38,19 +38,13 @@ impl RegistryGlobals {
pub const DMABUF: u32 = 6;
pub const WL_DRM: u32 = 7;
pub const PRESENTATION: u32 = 8;
pub const VIEWPORTER: u32 = 9;
}
#[derive(Debug, waynest_server::RequestDispatcher, Default)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher, Default)]
pub struct Registry;
impl Registry {
pub async fn advertise_globals(
&self,
client: &mut Client,
sender_id: ObjectId,
) -> WaylandResult<()> {
pub async fn advertise_globals(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
self.global(
client,
sender_id,
@@ -132,57 +126,48 @@ impl Registry {
)
.await?;
self.global(
client,
sender_id,
RegistryGlobals::VIEWPORTER,
Viewporter::INTERFACE.to_string(),
Viewporter::VERSION,
)
.await?;
Ok(())
}
}
impl WlRegistry for Registry {
type Connection = crate::wayland::Client;
async fn bind(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
name: u32,
new_id: NewId,
) -> WaylandResult<()> {
) -> Result<()> {
match name {
RegistryGlobals::COMPOSITOR => {
tracing::info!("Binding compositor");
client.insert(new_id.object_id, Compositor)?;
client.insert(new_id.object_id, Compositor);
}
RegistryGlobals::SHM => {
tracing::info!("Binding SHM");
let shm = client.insert(new_id.object_id, Shm)?;
let shm = client.insert(new_id.object_id, Shm);
shm.advertise_formats(client, new_id.object_id).await?;
}
RegistryGlobals::WM_BASE => {
tracing::info!("Binding WM_BASE");
client.insert(
new_id.object_id,
WmBase::new(new_id.object_id, new_id.version),
)?;
WmBase {
version: new_id.version,
},
);
}
RegistryGlobals::SEAT => {
tracing::info!("Binding seat with id {}", new_id.object_id);
let seat = Seat::new(client, new_id.object_id, new_id.version).await?;
let seat = client.insert(new_id.object_id, seat)?;
let seat = client.insert(new_id.object_id, seat);
let _ = client.display().seat.set(seat.clone());
tracing::info!("Seat capabilities advertised");
}
RegistryGlobals::DATA_DEVICE_MANAGER => {
tracing::info!("Binding data device manager");
client.insert(new_id.object_id, DataDeviceManager)?;
client.insert(new_id.object_id, DataDeviceManager);
}
RegistryGlobals::OUTPUT => {
tracing::info!("Binding output");
@@ -192,7 +177,7 @@ impl WlRegistry for Registry {
id: new_id.object_id,
version: new_id.version,
},
)?;
);
let _ = client.display().output.set(output.clone());
output.advertise_outputs(client).await?;
}
@@ -200,27 +185,22 @@ impl WlRegistry for Registry {
tracing::info!("Binding dmabuf");
let dmabuf = Dmabuf::new(client, new_id.object_id, new_id.version).await?;
client.insert(new_id.object_id, dmabuf)?;
client.insert(new_id.object_id, dmabuf);
}
RegistryGlobals::WL_DRM => {
tracing::info!("Binding wl_drm");
let drm = MesaDrm::new(client, new_id.object_id, new_id.version).await?;
client.insert(new_id.object_id, drm)?;
client.insert(new_id.object_id, drm);
}
RegistryGlobals::PRESENTATION => {
tracing::info!("Binding wp_presentation");
client.insert(new_id.object_id, Presentation::new(new_id.object_id))?;
}
RegistryGlobals::VIEWPORTER => {
tracing::info!("Binding wp_viewporter");
client.insert(new_id.object_id, Viewporter::new(new_id.object_id))?;
client.insert(new_id.object_id, Presentation);
}
id => {
tracing::error!(id, "Wayland: failed to bind to registry global");
return Err(WaylandError::UnknownGlobal(name));
return Err(Error::MissingObject(unsafe { ObjectId::from_raw(name) }));
}
}

View File

@@ -1,16 +1,22 @@
#![allow(unused)]
use super::{Message, MessageSink, display::Display};
use crate::wayland::{Client, WaylandError, WaylandResult};
use std::{fmt::Debug, sync::Arc};
use waynest::ObjectId;
use waynest_protocols::server::core::wayland::wl_display::WlDisplay;
use waynest_server::{Client as _, RequestDispatcher};
use waynest::{
server::{Client, Result, protocol::core::wayland::wl_display::WlDisplay},
wire::ObjectId,
};
pub trait ClientExt {
fn message_sink(&self) -> MessageSink;
fn display(&self) -> Arc<Display>;
fn try_get<D: RequestDispatcher>(&self, id: ObjectId) -> WaylandResult<Arc<D>>;
async fn protocol_error(
&mut self,
sender_id: ObjectId,
object_id: ObjectId,
code: u32,
message: String,
) -> Result<()>;
}
impl ClientExt for Client {
fn message_sink(&self) -> MessageSink {
@@ -24,8 +30,19 @@ impl ClientExt for Client {
self.get::<Display>(ObjectId::DISPLAY).unwrap()
}
fn try_get<D: RequestDispatcher>(&self, id: ObjectId) -> WaylandResult<Arc<D>> {
self.get::<D>(id).ok_or(WaylandError::MissingObject(id))
async fn protocol_error(
&mut self,
sender_id: ObjectId,
object_id: ObjectId,
code: u32,
message: String,
) -> Result<()> {
self.display()
.error(self, sender_id, object_id, code, message)
.await?;
let _ = self.message_sink().send(Message::Disconnect);
Ok(())
}
}

View File

@@ -1,96 +0,0 @@
use crate::wayland::WaylandResult;
use waynest::Fixed;
use waynest::ObjectId;
pub use waynest_protocols::server::stable::viewporter::wp_viewport::*;
pub use waynest_protocols::server::stable::viewporter::wp_viewporter::*;
use waynest_server::Client as _;
// This is a barebones/stub no-op implementation of wp_viewporter to make xwayland apps work
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Viewporter {
id: ObjectId,
}
impl Viewporter {
pub fn new(id: ObjectId) -> Self {
Self { id }
}
}
impl WpViewporter for Viewporter {
type Connection = crate::wayland::Client;
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(())
}
async fn get_viewport(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
id: ObjectId,
surface_id: ObjectId,
) -> WaylandResult<()> {
let viewport = Viewport::new(id, surface_id);
client.insert(id, viewport)?;
Ok(())
}
}
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Viewport {
id: ObjectId,
_surface_id: ObjectId,
}
impl Viewport {
pub fn new(id: ObjectId, surface_id: ObjectId) -> Self {
Self {
id,
_surface_id: surface_id,
}
}
}
impl WpViewport for Viewport {
type Connection = crate::wayland::Client;
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(())
}
async fn set_source(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
_x: Fixed,
_y: Fixed,
_width: Fixed,
_height: Fixed,
) -> WaylandResult<()> {
Ok(())
}
async fn set_destination(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
_width: i32,
_height: i32,
) -> WaylandResult<()> {
Ok(())
}
}

View File

@@ -1,21 +1,12 @@
use super::toplevel::Toplevel;
use crate::{
core::{error::Result, task},
core::error::Result,
nodes::{
drawable::model::ModelPart,
items::panel::{
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo,
},
},
wayland::{
Message,
core::{
seat::{Seat, SeatMessage},
surface::Surface,
},
items::panel::{Backend, Geometry, PanelItemInitData, SurfaceId, ToplevelInfo},
},
wayland::{Message, core::surface::Surface},
};
use dashmap::DashMap;
use mint::Vector2;
use std::sync::Arc;
use std::sync::Weak;
@@ -23,17 +14,13 @@ use tracing;
#[derive(Debug)]
pub struct XdgBackend {
seat: Weak<Seat>,
toplevel: Weak<Toplevel>,
children: DashMap<u64, (Weak<Surface>, ChildInfo)>,
}
impl XdgBackend {
pub fn new(seat: &Arc<Seat>, toplevel: &Arc<Toplevel>) -> Self {
pub fn new(toplevel: Arc<Toplevel>) -> Self {
Self {
seat: Arc::downgrade(seat),
toplevel: Arc::downgrade(toplevel),
children: DashMap::new(),
toplevel: Arc::downgrade(&toplevel),
}
}
@@ -45,62 +32,17 @@ impl XdgBackend {
.expect("Toplevel should always be valid while XdgBackend exists")
}
pub fn panel_item(&self) -> Option<Arc<PanelItem<XdgBackend>>> {
self.toplevel().wl_surface().panel_item.lock().upgrade()
}
fn surface_from_id(&self, id: &SurfaceId) -> Option<Arc<Surface>> {
fn surface_from_id(&self, id: SurfaceId) -> Option<Arc<Surface>> {
match id {
SurfaceId::Toplevel(_) => Some(self.toplevel().wl_surface().clone()),
SurfaceId::Child(id) => self.children.get(id).as_deref().and_then(|c| c.0.upgrade()),
SurfaceId::Toplevel(_) => Some(self.toplevel().surface()),
SurfaceId::Child(_) => None,
}
}
pub fn add_child(&self, surface: &Arc<Surface>, info: ChildInfo) {
let Some(SurfaceId::Child(id)) = surface.surface_id.get().cloned() else {
return;
};
self.children
.insert(id, (Arc::downgrade(surface), info.clone()));
let Some(panel_item) = self.panel_item() else {
tracing::error!("Couldn't find panel item in add_child");
return;
};
panel_item.create_child(id, &info);
}
pub fn reposition_child(&self, surface: &Arc<Surface>, geometry: Geometry) {
let Some(SurfaceId::Child(id)) = surface.surface_id.get() else {
return;
};
if let Some(mut child) = self.children.get_mut(id) {
child.1.geometry = geometry;
}
let Some(panel_item) = self.panel_item() else {
tracing::error!("Couldn't find panel item in reposition_child");
return;
};
panel_item.reposition_child(*id, &geometry);
}
pub fn remove_child(&self, surface: &Surface) {
let Some(SurfaceId::Child(id)) = surface.surface_id.get() else {
return;
};
self.children.remove(id);
let Some(panel_item) = self.panel_item() else {
tracing::error!("Couldn't find panel item in remove_child");
return;
};
panel_item.destroy_child(*id);
}
}
impl Backend for XdgBackend {
fn start_data(&self) -> Result<PanelItemInitData> {
let surface_state = self.toplevel().wl_surface().current_state();
let surface_state = self.toplevel().surface().current_state();
let size = surface_state
.buffer
@@ -132,20 +74,9 @@ impl Backend for XdgBackend {
})
}
fn apply_cursor_material(&self, model_part: &Arc<ModelPart>) {
let model_part = model_part.clone();
let Some(seat) = self.seat.upgrade() else {
return;
};
let _ = task::new(|| "Apply cursor material", async move {
let Some(cursor) = seat.cursor_surface().await else {
return;
};
cursor.apply_material(&model_part);
});
}
fn apply_cursor_material(&self, _model_part: &Arc<ModelPart>) {}
fn apply_surface_material(&self, surface: SurfaceId, model_part: &Arc<ModelPart>) {
if let Some(surface) = self.surface_from_id(&surface) {
if let Some(surface) = self.surface_from_id(surface) {
surface.apply_material(model_part);
}
}
@@ -153,7 +84,7 @@ impl Backend for XdgBackend {
fn close_toplevel(&self) {
let _ = self
.toplevel()
.wl_surface()
.surface()
.message_sink
.send(Message::CloseToplevel(self.toplevel().clone()));
}
@@ -161,7 +92,7 @@ impl Backend for XdgBackend {
fn auto_size_toplevel(&self) {
let _ = self
.toplevel()
.wl_surface()
.surface()
.message_sink
.send(Message::ResizeToplevel {
toplevel: self.toplevel().clone(),
@@ -172,7 +103,7 @@ impl Backend for XdgBackend {
fn set_toplevel_size(&self, size: Vector2<u32>) {
let _ = self
.toplevel()
.wl_surface()
.surface()
.message_sink
.send(Message::ResizeToplevel {
toplevel: self.toplevel().clone(),
@@ -183,7 +114,7 @@ impl Backend for XdgBackend {
fn set_toplevel_focused_visuals(&self, focused: bool) {
let _ = self
.toplevel()
.wl_surface()
.surface()
.message_sink
.send(Message::SetToplevelVisualActive {
toplevel: self.toplevel().clone(),
@@ -192,29 +123,22 @@ impl Backend for XdgBackend {
}
fn pointer_motion(&self, surface: &SurfaceId, position: Vector2<f32>) {
if let Some(surface) = self.surface_from_id(surface) {
let _ = self
.toplevel()
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::PointerMotion {
surface,
position,
}));
if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self.toplevel().surface().message_sink.send(Message::Seat(
crate::wayland::core::seat::SeatMessage::PointerMotion { surface, position },
));
}
}
fn pointer_button(&self, surface: &SurfaceId, button: u32, pressed: bool) {
if let Some(surface) = self.surface_from_id(surface) {
let _ = self
.toplevel()
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::PointerButton {
if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self.toplevel().surface().message_sink.send(Message::Seat(
crate::wayland::core::seat::SeatMessage::PointerButton {
surface,
button,
pressed,
}));
},
));
}
}
@@ -224,16 +148,14 @@ impl Backend for XdgBackend {
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
if let Some(surface) = self.surface_from_id(surface) {
let _ = self
.toplevel()
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::PointerScroll {
if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self.toplevel().surface().message_sink.send(Message::Seat(
crate::wayland::core::seat::SeatMessage::PointerScroll {
surface,
scroll_distance,
scroll_steps,
}));
},
));
}
}
@@ -243,17 +165,15 @@ impl Backend for XdgBackend {
key,
if pressed { "pressed" } else { "released" }
);
if let Some(surface) = self.surface_from_id(surface) {
let _ = self
.toplevel()
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::KeyboardKey {
if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self.toplevel().surface().message_sink.send(Message::Seat(
crate::wayland::core::seat::SeatMessage::KeyboardKey {
surface,
keymap_id,
key,
pressed,
}));
},
));
}
}
@@ -264,16 +184,14 @@ impl Backend for XdgBackend {
position.x,
position.y
);
if let Some(surface) = self.surface_from_id(surface) {
let _ = self
.toplevel()
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::TouchDown {
if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self.toplevel().surface().message_sink.send(Message::Seat(
crate::wayland::core::seat::SeatMessage::TouchDown {
surface,
id,
position,
}));
},
));
}
}
@@ -284,28 +202,25 @@ impl Backend for XdgBackend {
position.x,
position.y
);
let toplevel = self.toplevel();
let _ = toplevel
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::TouchMove { id, position }));
let surface = self.toplevel().surface();
let _ = surface.message_sink.send(Message::Seat(
crate::wayland::core::seat::SeatMessage::TouchMove { id, position },
));
}
fn touch_up(&self, id: u32) {
tracing::debug!("Backend: Touch up {}", id);
let toplevel = self.toplevel();
let _ = toplevel
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::TouchUp { id }));
let surface = self.toplevel().surface();
let _ = surface.message_sink.send(Message::Seat(
crate::wayland::core::seat::SeatMessage::TouchUp { id },
));
}
fn reset_input(&self) {
tracing::debug!("Backend: Reset input");
let toplevel = self.toplevel();
let _ = toplevel
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::Reset));
let surface = self.toplevel().surface();
let _ = surface.message_sink.send(Message::Seat(
crate::wayland::core::seat::SeatMessage::Reset,
));
}
}

View File

@@ -1,62 +1,78 @@
use super::{
backend::XdgBackend,
positioner::{Positioner, PositionerData},
surface::Surface,
};
use crate::nodes::items::panel::SurfaceId;
use crate::wayland::WaylandResult;
use crate::{
nodes::items::panel::{Geometry, PanelItem, SurfaceId},
wayland::util::DoubleBuffer,
};
use parking_lot::Mutex;
use rand::Rng;
use std::sync::Arc;
use waynest::ObjectId;
use waynest_protocols::server::stable::xdg_shell::xdg_popup::XdgPopup;
use waynest_server::Client as _;
use std::{
sync::{Arc, Weak, atomic::AtomicBool},
u64,
};
use waynest::{
server::{Client, Dispatcher, Result, protocol::stable::xdg_shell::xdg_popup::XdgPopup},
wire::ObjectId,
};
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct Popup {
version: u32,
pub surface: Arc<Surface>,
positioner_data: Mutex<PositionerData>,
id: ObjectId,
version: u32,
surface_id: SurfaceId,
parent: Arc<Surface>,
surface: Weak<Surface>,
pub panel_item: Weak<PanelItem<XdgBackend>>,
positioner_data: Mutex<PositionerData>,
geometry: DoubleBuffer<Geometry>,
mapped: AtomicBool,
}
impl Popup {
pub fn new(version: u32, surface: Arc<Surface>, positioner: &Positioner, id: ObjectId) -> Self {
let _ = surface
.wl_surface
.surface_id
.set(SurfaceId::Child(rand::rng().random()));
pub fn new(
id: ObjectId,
version: u32,
parent: Arc<Surface>,
panel_item: &Arc<PanelItem<XdgBackend>>,
xdg_surface: &Arc<Surface>,
positioner: &Positioner,
) -> Self {
let positioner_data = positioner.data();
Self {
version,
surface,
positioner_data: Mutex::new(positioner_data),
id,
version,
surface_id: SurfaceId::Child(rand::thread_rng().gen_range(0..u64::MAX)),
parent,
surface: Arc::downgrade(xdg_surface),
panel_item: Arc::downgrade(panel_item),
positioner_data: Mutex::new(positioner_data),
geometry: DoubleBuffer::new(positioner_data.infinite_geometry()),
mapped: AtomicBool::new(false),
}
}
}
impl XdgPopup for Popup {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab
async fn grab(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_seat: ObjectId,
_serial: u32,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
/// https://wayland.app/protocols/xdg-shell#xdg_popup:request:reposition
async fn reposition(
&self,
client: &mut Self::Connection,
client: &mut Client,
sender_id: ObjectId,
positioner: ObjectId,
token: u32,
) -> WaylandResult<()> {
) -> Result<()> {
let positioner = client.get::<Positioner>(positioner).unwrap();
let positioner_data = positioner.data();
*self.positioner_data.lock() = positioner_data;
@@ -73,32 +89,12 @@ impl XdgPopup for Popup {
geometry.size.y as i32,
)
.await?;
self.surface.reconfigure(client).await?;
let Some(panel_item) = self.surface.wl_surface.panel_item.lock().upgrade() else {
return Ok(());
};
panel_item
.backend
.reposition_child(&self.surface.wl_surface, geometry);
self.surface.upgrade().unwrap().reconfigure(client).await?;
Ok(())
}
/// https://wayland.app/protocols/xdg-shell#xdg_popup:request:destroy
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}
impl Drop for Popup {
fn drop(&mut self) {
let Some(panel_item) = self.surface.wl_surface.panel_item.lock().upgrade() else {
return;
};
panel_item.backend.remove_child(&self.surface.wl_surface);
}
}

View File

@@ -1,9 +1,15 @@
use crate::{nodes::items::panel::Geometry, wayland::WaylandResult};
use crate::nodes::items::panel::Geometry;
use mint::Vector2;
use parking_lot::Mutex;
use waynest::ObjectId;
use waynest_protocols::server::stable::xdg_shell::xdg_positioner::*;
use waynest_server::Client as _;
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::stable::xdg_shell::xdg_positioner::{
Anchor, ConstraintAdjustment, Gravity, XdgPositioner,
},
},
wire::ObjectId,
};
#[derive(Debug, Clone, Copy)]
pub struct PositionerData {
@@ -11,37 +17,11 @@ pub struct PositionerData {
pub anchor_rect: Geometry,
pub offset: Vector2<i32>,
pub anchor: Anchor,
pub gravity: Gravity,
pub constraint_adjustment: ConstraintAdjustment,
pub reactive: bool,
pub parent_size: Vector2<u32>,
}
impl PositionerData {
fn gravity_has_edge(&self, edge: Gravity) -> bool {
match edge {
Gravity::Top => {
self.gravity == Gravity::Top
|| self.gravity == Gravity::TopLeft
|| self.gravity == Gravity::TopRight
}
Gravity::Bottom => {
self.gravity == Gravity::Bottom
|| self.gravity == Gravity::BottomLeft
|| self.gravity == Gravity::BottomRight
}
Gravity::Left => {
self.gravity == Gravity::Left
|| self.gravity == Gravity::TopLeft
|| self.gravity == Gravity::BottomLeft
}
Gravity::Right => {
self.gravity == Gravity::Right
|| self.gravity == Gravity::TopRight
|| self.gravity == Gravity::BottomRight
}
_ => unreachable!(),
}
}
pub fn infinite_geometry(&self) -> Geometry {
let anchor_point = match self.anchor {
Anchor::TopLeft => self.anchor_rect.origin,
@@ -87,29 +67,30 @@ impl PositionerData {
.into(),
};
let mut geometry = Geometry {
origin: [
anchor_point.x + self.offset.x,
anchor_point.y + self.offset.y,
]
.into(),
let mut position = anchor_point;
// Apply gravity
if self
.constraint_adjustment
.contains(ConstraintAdjustment::FlipX)
{
position.x -= self.size.x as i32;
}
if self
.constraint_adjustment
.contains(ConstraintAdjustment::FlipY)
{
position.y -= self.size.y as i32;
}
// Apply offset
position.x += self.offset.x;
position.y += self.offset.y;
Geometry {
origin: position,
size: self.size,
};
// apply gravity
if self.gravity_has_edge(Gravity::Top) {
geometry.origin.y -= geometry.size.y as i32;
} else if !self.gravity_has_edge(Gravity::Bottom) {
geometry.origin.y -= (geometry.size.y / 2) as i32;
}
if self.gravity_has_edge(Gravity::Left) {
geometry.origin.x -= geometry.size.x as i32;
} else if !self.gravity_has_edge(Gravity::Right) {
geometry.origin.x -= (geometry.size.x / 2) as i32;
}
geometry
}
}
impl Default for PositionerData {
@@ -119,7 +100,6 @@ impl Default for PositionerData {
anchor_rect: Default::default(),
offset: [0, 0].into(),
anchor: Anchor::TopLeft,
gravity: Gravity::TopLeft,
constraint_adjustment: ConstraintAdjustment::empty(),
reactive: false,
parent_size: [0; 2].into(),
@@ -127,17 +107,14 @@ impl Default for PositionerData {
}
}
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct Positioner {
data: Mutex<PositionerData>,
id: ObjectId,
}
impl Positioner {
pub fn new(id: ObjectId) -> Self {
impl Default for Positioner {
fn default() -> Self {
Self {
data: Mutex::new(PositionerData::default()),
id,
}
}
}
@@ -147,15 +124,13 @@ impl Positioner {
}
}
impl XdgPositioner for Positioner {
type Connection = crate::wayland::Client;
async fn set_size(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_width: i32,
_height: i32,
) -> WaylandResult<()> {
) -> Result<()> {
let mut data = self.data.lock();
data.size = [_width.max(0) as u32, _height.max(0) as u32].into();
data.reactive = true;
@@ -164,13 +139,13 @@ impl XdgPositioner for Positioner {
async fn set_anchor_rect(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> WaylandResult<()> {
) -> Result<()> {
let mut data = self.data.lock();
data.anchor_rect.origin = [_x, _y].into();
data.anchor_rect.size = [_width.max(0) as u32, _height.max(0) as u32].into();
@@ -180,10 +155,10 @@ impl XdgPositioner for Positioner {
async fn set_anchor(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_anchor: Anchor,
) -> WaylandResult<()> {
) -> Result<()> {
let mut data = self.data.lock();
data.anchor = _anchor;
Ok(())
@@ -191,21 +166,19 @@ impl XdgPositioner for Positioner {
async fn set_gravity(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
gravity: Gravity,
) -> WaylandResult<()> {
let mut data = self.data.lock();
data.gravity = gravity;
_gravity: Gravity,
) -> Result<()> {
Ok(())
}
async fn set_constraint_adjustment(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_constraint_adjustment: ConstraintAdjustment,
) -> WaylandResult<()> {
) -> Result<()> {
let mut data = self.data.lock();
data.constraint_adjustment = _constraint_adjustment;
Ok(())
@@ -213,32 +186,28 @@ impl XdgPositioner for Positioner {
async fn set_offset(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
) -> WaylandResult<()> {
) -> Result<()> {
let mut data = self.data.lock();
data.offset.x += _x;
data.offset.y += _y;
Ok(())
}
async fn set_reactive(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn set_reactive(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn set_parent_size(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_parent_width: i32,
_parent_height: i32,
) -> WaylandResult<()> {
) -> Result<()> {
let mut data = self.data.lock();
data.parent_size.x = _parent_width.max(0) as u32;
data.parent_size.y = _parent_height.max(0) as u32;
@@ -247,19 +216,14 @@ impl XdgPositioner for Positioner {
async fn set_parent_configure(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_serial: u32,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

View File

@@ -1,22 +1,18 @@
use super::{popup::Popup, positioner::Positioner, toplevel::MappedInner};
use crate::nodes::items::panel::{ChildInfo, SurfaceId};
use crate::wayland::{Client, WaylandError};
use crate::wayland::{
Message, WaylandResult, core::surface::SurfaceRole, display::Display, util::ClientExt,
xdg::toplevel::Toplevel,
};
use super::{popup::Popup, positioner::Positioner, toplevel::Mapped};
use crate::wayland::{core::surface::SurfaceRole, display::Display, xdg::toplevel::Toplevel};
use std::sync::Arc;
use waynest::ObjectId;
use waynest_protocols::server::stable::xdg_shell::xdg_popup::XdgPopup;
pub use waynest_protocols::server::stable::xdg_shell::xdg_surface::*;
use waynest_server::Client as _;
use std::sync::Weak;
pub use waynest::server::protocol::stable::xdg_shell::xdg_surface::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct Surface {
id: ObjectId,
version: u32,
pub wl_surface: Arc<crate::wayland::core::surface::Surface>,
wl_surface: Weak<crate::wayland::core::surface::Surface>,
configured: Arc<std::sync::atomic::AtomicBool>,
}
impl Surface {
@@ -28,75 +24,84 @@ impl Surface {
Self {
id,
version,
wl_surface,
wl_surface: Arc::downgrade(&wl_surface),
configured: Arc::new(std::sync::atomic::AtomicBool::new(false)),
}
}
pub async fn reconfigure(&self, client: &mut Client) -> WaylandResult<()> {
pub fn wl_surface(&self) -> Arc<crate::wayland::core::surface::Surface> {
// We can safely unwrap as the surface must exist for the lifetime of the xdg_surface
self.wl_surface
.upgrade()
.expect("Surface was dropped before xdg_surface")
}
pub async fn reconfigure(&self, client: &mut Client) -> Result<()> {
let serial = client.next_event_serial();
self.configure(client, self.id, serial).await
}
}
impl XdgSurface for Surface {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:destroy
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:get_toplevel
async fn get_toplevel(
&self,
client: &mut Self::Connection,
client: &mut Client,
sender_id: ObjectId,
toplevel_id: ObjectId,
) -> WaylandResult<()> {
) -> Result<()> {
let surface = self.wl_surface();
let toplevel = client.insert(
toplevel_id,
Toplevel::new(
toplevel_id,
self.wl_surface.clone(),
surface.clone(),
client.get::<Self>(sender_id).unwrap(),
),
)?;
);
self.wl_surface
.try_set_role(SurfaceRole::XdgToplevel, Error::AlreadyConstructed)
.await?;
{
let mut surface_role = surface.role.lock();
let toplevel_weak = Arc::downgrade(&toplevel);
let display = client.get::<Display>(ObjectId::DISPLAY).unwrap();
let seat = Arc::downgrade(display.seat.get().unwrap());
let pid = display.pid;
// A surface must not have any existing role when assigning a new one
// "A surface must not have more than one role, and a role must not be assigned to more than one
// surface at a time. However, wl_surface role-specific interfaces may reassign the role, allow
// a role to be destroyed, or allow multiple role-specific interfaces to share the same role."
// - xdg_surface protocol doc
if surface_role.is_some() {
// We should send "role" error here as per xdg_wm_base.error enum
// But we'll ignore for now
} else {
surface_role.replace(SurfaceRole::XdgToplevel(toplevel.clone()));
}
}
toplevel.reconfigure(client).await?;
let pid = client.get::<Display>(ObjectId::DISPLAY).unwrap().pid;
let configured = self.configured.clone();
let mut first_commit = true;
let message_tx = client.message_sink().clone();
self.wl_surface.add_commit_handler(move |surface, state| {
let Some(toplevel) = toplevel_weak.upgrade() else {
surface.add_commit_handler(move |surface, state| {
let Some(SurfaceRole::XdgToplevel(toplevel)) = &mut *surface.role.lock() else {
return true;
};
if first_commit {
let _ = message_tx.send(Message::ReconfigureToplevel(toplevel.clone()));
first_commit = false;
}
// Only proceed if configured and has valid buffer
let has_valid_buffer = state
.buffer
.as_ref()
.is_some_and(|b| b.buffer.size().x > 0 && b.buffer.size().y > 0);
let mut mapped_lock = toplevel.mapped.lock();
if mapped_lock.is_none()
&& configured.load(std::sync::atomic::Ordering::SeqCst)
&& state.has_valid_buffer()
&& has_valid_buffer
{
let mapped_inner = MappedInner::create(&seat.upgrade().unwrap(), &toplevel, pid);
*surface.panel_item.lock() = Arc::downgrade(&mapped_inner.panel_item);
mapped_lock.replace(mapped_inner);
mapped_lock.replace(Mapped::create(toplevel.clone(), pid));
return false;
}
true
@@ -108,88 +113,74 @@ impl XdgSurface for Surface {
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:get_popup
async fn get_popup(
&self,
client: &mut Self::Connection,
client: &mut Client,
sender_id: ObjectId,
popup_id: ObjectId,
parent: Option<ObjectId>,
positioner: ObjectId,
) -> WaylandResult<()> {
self.wl_surface
.try_set_role(SurfaceRole::XdgPopup, Error::AlreadyConstructed)
.await?;
let Some(parent) = parent else {
return Err(WaylandError::Fatal {
object_id: popup_id,
code: 3,
message: "Parent surface does not have an XDG role",
});
) -> Result<()> {
let parent = client.get::<Surface>(parent.unwrap()).unwrap();
let panel_item = match parent.wl_surface().role.lock().as_ref().unwrap() {
SurfaceRole::XdgToplevel(toplevel) => {
let toplevel_lock = toplevel.mapped.lock();
toplevel_lock.as_ref().unwrap()._panel_item.clone()
}
SurfaceRole::XDGPopup(popup) => popup.panel_item.upgrade().unwrap(),
};
let Some(parent) = client.get::<Surface>(parent) else {
return Err(WaylandError::Fatal {
object_id: popup_id,
code: 3,
message: "Parent surface does not exist",
});
};
*self.wl_surface.panel_item.lock() = parent.wl_surface.panel_item.lock().clone();
let positioner = client.get::<Positioner>(positioner).unwrap();
let surface = client.get::<Surface>(self.id).unwrap();
let popup = client.insert(
popup_id,
Popup::new(self.version, surface, &positioner, popup_id),
)?;
let positioner_geometry = positioner.data().infinite_geometry();
popup
.configure(
client,
Popup::new(
popup_id,
positioner_geometry.origin.x,
positioner_geometry.origin.y,
positioner_geometry.size.x as i32,
positioner_geometry.size.y as i32,
)
.await?;
self.version,
parent,
&panel_item,
&surface,
&positioner,
),
);
{
let wl_surface = self.wl_surface();
let mut surface_role = wl_surface.role.lock();
if surface_role.is_some() {
// We should send "role" error here as per xdg_wm_base.error enum
// But we'll ignore for now
} else {
surface_role.replace(SurfaceRole::XDGPopup(popup.clone()));
}
}
let serial = client.next_event_serial();
self.configure(client, sender_id, serial).await?;
let Some(SurfaceId::Child(id)) = self.wl_surface.surface_id.get() else {
return Ok(());
};
let Some(parent_id) = parent.wl_surface.surface_id.get() else {
return Ok(());
};
// let pid = client.get::<Display>(ObjectId::DISPLAY).unwrap().pid;
// let configured = self.configured.clone();
// surface.add_commit_handler(move |surface, state| {
// let Some(SurfaceRole::XDGPopup(popup)) = &mut *surface.role.lock() else {
// return true;
// };
let child_info = ChildInfo {
id: *id,
parent: parent_id.clone(),
geometry: positioner.data().infinite_geometry(),
z_order: 1,
receives_input: true,
};
// // Only proceed if configured and has valid buffer
// let has_valid_buffer = state
// .buffer
// .as_ref()
// .is_some_and(|b| b.buffer.size().x > 0 && b.buffer.size().y > 0);
let popup_weak = Arc::downgrade(&popup);
let configured = self.configured.clone();
self.wl_surface.add_commit_handler(move |surface, state| {
let Some(popup) = popup_weak.upgrade() else {
return true;
};
let Some(panel_item) = surface.panel_item.lock().upgrade() else {
return true;
};
if configured.load(std::sync::atomic::Ordering::SeqCst) && state.has_valid_buffer() {
panel_item
.backend
.add_child(&popup.surface.wl_surface, child_info.clone());
return false;
}
true
});
// let mut mapped_lock = popup.mapped.lock();
// if mapped_lock.is_none()
// && configured.load(std::sync::atomic::Ordering::SeqCst)
// && has_valid_buffer
// {
// mapped_lock.replace(Mapped::create(popup.clone(), pid));
// return false;
// }
// true
// });
Ok(())
}
@@ -197,13 +188,13 @@ impl XdgSurface for Surface {
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:set_window_geometry
async fn set_window_geometry(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> WaylandResult<()> {
) -> Result<()> {
// we're gonna delegate literally all the window management
// to 3D stuff sooo we don't care, maximized is the floating state
Ok(())
@@ -212,10 +203,10 @@ impl XdgSurface for Surface {
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:ack_configure
async fn ack_configure(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_serial: u32,
) -> WaylandResult<()> {
) -> Result<()> {
self.configured
.store(true, std::sync::atomic::Ordering::SeqCst);
Ok(())

View File

@@ -1,34 +1,31 @@
use super::backend::XdgBackend;
use crate::{
nodes::{
Node,
items::panel::{PanelItem, SurfaceId},
},
wayland::{
Client, WaylandResult,
core::{seat::Seat, surface::Surface},
},
nodes::{Node, items::panel::PanelItem},
wayland::core::surface::Surface,
};
use mint::Vector2;
use parking_lot::Mutex;
use std::sync::Arc;
use waynest::ObjectId;
pub use waynest_protocols::server::stable::xdg_shell::xdg_toplevel::*;
use waynest_server::Client as _;
use std::sync::Weak;
pub use waynest::server::protocol::stable::xdg_shell::xdg_toplevel::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug)]
pub struct MappedInner {
pub struct Mapped {
pub panel_item_node: Arc<Node>,
pub panel_item: Arc<PanelItem<XdgBackend>>,
pub _panel_item: Arc<PanelItem<XdgBackend>>,
}
impl MappedInner {
pub fn create(seat: &Arc<Seat>, toplevel: &Arc<Toplevel>, pid: Option<i32>) -> Self {
let (panel_item_node, panel_item) =
PanelItem::create(Box::new(XdgBackend::new(seat, toplevel)), pid);
impl Mapped {
pub fn create(toplevel: Arc<Toplevel>, pid: Option<i32>) -> Self {
let (panel_item_node, _panel_item) =
PanelItem::create(Box::new(XdgBackend::new(toplevel)), pid);
Self {
panel_item_node,
panel_item,
_panel_item,
}
}
}
@@ -55,12 +52,12 @@ impl Default for ToplevelData {
}
}
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct Toplevel {
pub id: ObjectId,
xdg_surface: Arc<super::surface::Surface>,
pub mapped: Mutex<Option<MappedInner>>,
wl_surface: Weak<Surface>,
xdg_surface: Weak<super::surface::Surface>,
pub mapped: Mutex<Option<Mapped>>,
data: Mutex<ToplevelData>,
}
impl Toplevel {
@@ -69,18 +66,20 @@ impl Toplevel {
wl_surface: Arc<Surface>,
xdg_surface: Arc<super::surface::Surface>,
) -> Self {
let _ = wl_surface.surface_id.set(SurfaceId::Toplevel(()));
Toplevel {
id: object_id,
xdg_surface,
wl_surface: Arc::downgrade(&wl_surface),
xdg_surface: Arc::downgrade(&xdg_surface),
mapped: Mutex::new(None),
data: Mutex::new(ToplevelData::default()),
}
}
pub fn wl_surface(&self) -> &Arc<Surface> {
&self.xdg_surface.wl_surface
pub fn surface(&self) -> Arc<Surface> {
// We can safely unwrap as the surface must exist for the lifetime of the toplevel
self.wl_surface
.upgrade()
.expect("Surface was dropped before toplevel")
}
pub fn title(&self) -> Option<String> {
@@ -103,7 +102,7 @@ impl Toplevel {
// Helper to clamp size against constraints
fn clamp_size(&self, size: Vector2<u32>) -> Vector2<u32> {
let state = self.wl_surface().current_state();
let state = self.surface().current_state();
let mut clamped = size;
if let Some(min_size) = state.min_size {
@@ -117,7 +116,7 @@ impl Toplevel {
clamped
}
pub async fn reconfigure(&self, client: &mut Client) -> WaylandResult<()> {
pub async fn reconfigure(&self, client: &mut Client) -> Result<()> {
let data = self.data.lock().clone();
// Use the explicitly set size, applying constraints
@@ -149,19 +148,20 @@ impl Toplevel {
.collect(),
)
.await?;
self.xdg_surface.reconfigure(client).await?;
Ok(())
self.xdg_surface
.upgrade()
.unwrap()
.reconfigure(client)
.await
}
}
impl XdgToplevel for Toplevel {
type Connection = crate::wayland::Client;
async fn set_parent(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
parent: Option<ObjectId>,
) -> WaylandResult<()> {
) -> Result<()> {
// Handle case where parent is specified
if let Some(parent) = parent {
// Per spec: parent must be another xdg_toplevel surface
@@ -192,65 +192,65 @@ impl XdgToplevel for Toplevel {
async fn set_title(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
title: String,
) -> WaylandResult<()> {
) -> Result<()> {
self.data.lock().title.replace(title);
Ok(())
}
async fn set_app_id(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
app_id: String,
) -> WaylandResult<()> {
) -> Result<()> {
self.data.lock().app_id.replace(app_id);
Ok(())
}
async fn show_window_menu(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_seat: ObjectId,
_serial: u32,
_x: i32,
_y: i32,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
async fn r#move(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_seat: ObjectId,
_serial: u32,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
async fn resize(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_seat: ObjectId,
_serial: u32,
_edges: ResizeEdge,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
async fn set_max_size(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
width: i32,
height: i32,
) -> WaylandResult<()> {
self.wl_surface().state_lock().pending.max_size = if width == 0 && height == 0 {
) -> Result<()> {
self.surface().pending_state().pending.max_size = if width == 0 && height == 0 {
None
} else {
Some([width as u32, height as u32].into())
@@ -260,12 +260,12 @@ impl XdgToplevel for Toplevel {
async fn set_min_size(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
width: i32,
height: i32,
) -> WaylandResult<()> {
self.xdg_surface.wl_surface.state_lock().pending.min_size = if width == 0 && height == 0 {
) -> Result<()> {
self.surface().pending_state().pending.min_size = if width == 0 && height == 0 {
None
} else {
Some([width as u32, height as u32].into())
@@ -273,53 +273,32 @@ impl XdgToplevel for Toplevel {
Ok(())
}
async fn set_maximized(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn set_maximized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn unset_maximized(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn unset_maximized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn set_fullscreen(
&self,
_client: &mut Self::Connection,
_client: &mut Client,
_sender_id: ObjectId,
_output: Option<ObjectId>,
) -> WaylandResult<()> {
) -> Result<()> {
Ok(())
}
async fn unset_fullscreen(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn unset_fullscreen(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn set_minimized(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
async fn set_minimized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
self.mapped.lock().take();
Ok(())
}

View File

@@ -1,73 +1,50 @@
use crate::wayland::xdg::surface::Surface;
pub use waynest::server::protocol::stable::xdg_shell::xdg_wm_base::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
use super::positioner::Positioner;
use crate::wayland::{WaylandError, WaylandResult, util::ClientExt, xdg::surface::Surface};
use waynest::ObjectId;
pub use waynest_protocols::server::stable::xdg_shell::xdg_wm_base::*;
use waynest_server::Client as _;
#[derive(Debug, waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher, Default)]
pub struct WmBase {
version: u32,
id: ObjectId,
}
impl WmBase {
pub fn new(id: ObjectId, version: u32) -> Self {
Self { version, id }
}
pub version: u32,
}
impl XdgWmBase for WmBase {
type Connection = crate::wayland::Client;
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn create_positioner(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> WaylandResult<()> {
client.insert(id, Positioner::new(id))?;
) -> Result<()> {
client.insert(id, Positioner::default());
Ok(())
}
async fn get_xdg_surface(
&self,
client: &mut Self::Connection,
client: &mut Client,
_sender_id: ObjectId,
xdg_surface_id: ObjectId,
wl_surface_id: ObjectId,
) -> WaylandResult<()> {
let wl_surface = client.try_get::<crate::wayland::core::surface::Surface>(wl_surface_id)?;
match wl_surface.role.get() {
None => (),
Some(_) => {
return Err(WaylandError::Fatal {
object_id: wl_surface_id,
code: Error::Role as u32,
message: "Wayland surface has role",
});
}
};
) -> Result<()> {
let wl_surface = client
.get::<crate::wayland::core::surface::Surface>(wl_surface_id)
.ok_or(waynest::server::Error::Custom(
"can't get wayland surface id".to_string(),
))?;
let xdg_surface = Surface::new(xdg_surface_id, self.version, wl_surface);
client.insert(xdg_surface_id, xdg_surface)?;
client.insert(xdg_surface_id, xdg_surface);
Ok(())
}
async fn pong(
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
_serial: u32,
) -> WaylandResult<()> {
async fn pong(&self, _client: &mut Client, _sender_id: ObjectId, _serial: u32) -> Result<()> {
Ok(())
}
}