1 Commits

Author SHA1 Message Date
Nova King
5f38d79e29 Handle xdg popups 2025-08-12 21:25:07 -07:00
72 changed files with 2320 additions and 3725 deletions

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use split_iter::Splittable;
use stardust_xr::schemas::protocol::*; use stardust_xr::schemas::protocol::*;
fn fold_tokens(a: TokenStream, b: TokenStream) -> TokenStream { 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(), description: protocol.description.clone(),
inherits: vec![], inherits: vec![],
members: p.members, members: p.members,
inherited_aspects: vec![],
}); });
quote! { quote! {
#node_id #node_id
@@ -105,7 +105,7 @@ fn codegen_protocol(protocol: &'static str) -> proc_macro::TokenStream {
let aspects = protocol let aspects = protocol
.aspects .aspects
.iter() .iter()
.map(|a| generate_aspect(&a.blocking_read())) .map(generate_aspect)
.reduce(fold_tokens) .reduce(fold_tokens)
.unwrap_or_default(); .unwrap_or_default();
quote!(#custom_enums #custom_unions #custom_structs #aspects #interface).into() 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 { fn generate_aspect(aspect: &Aspect) -> TokenStream {
let description = &aspect.description; let description = &aspect.description;
let (client_members, server_members) = aspect let (client_members, server_members) = aspect.members.iter().split(|m| m.side == Side::Server);
.members
.iter()
.partition::<Vec<_>, _>(|m| m.side == Side::Client);
let client_mod_name = Ident::new( let client_mod_name = Ident::new(
&format!("{}_client", &aspect.name.to_case(Case::Snake)), &format!("{}_client", &aspect.name.to_case(Case::Snake)),
Span::call_site(), Span::call_site(),
); );
let client_side_members = client_members let client_side_members = client_members
.into_iter() .map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Snake), m))
.map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Pascal), m))
.reduce(fold_tokens) .reduce(fold_tokens)
.map(|t| { .map(|t| {
// TODO: properly import all dependencies // TODO: properly import all dependencies
@@ -232,7 +228,6 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
let alias_info = generate_alias_info(aspect); let alias_info = generate_alias_info(aspect);
let server_side_members = server_members let server_side_members = server_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::Pascal), m))
.reduce(fold_tokens) .reduce(fold_tokens)
.unwrap_or_default(); .unwrap_or_default();
@@ -286,6 +281,9 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
} }
macro_rules! #aspect_macro_name { macro_rules! #aspect_macro_name {
() => { () => {
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
self
}
#[allow(clippy::all)] #[allow(clippy::all)]
fn run_signal( fn run_signal(
&self, &self,
@@ -306,12 +304,12 @@ fn generate_aspect(aspect: &Aspect) -> TokenStream {
_node: std::sync::Arc<crate::nodes::Node>, _node: std::sync::Arc<crate::nodes::Node>,
_method: u64, _method: u64,
_message: crate::nodes::Message, _message: crate::nodes::Message,
_method_response: crate::core::scenegraph::MethodResponseSender, _method_response: crate::nodes::MethodResponseSender,
) { ) {
match _method { match _method {
#run_methods #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), 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 let argument_decls = member
.arguments .arguments
.iter() .iter()
@@ -419,13 +405,12 @@ fn generate_member(aspect_id: u64, aspect_name: &str, member: &Member) -> TokenS
quote! { quote! {
#[doc = #description] #[doc = #description]
pub fn #name(#argument_decls) -> crate::core::error::Result<()> { 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() { 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 result
} }
@@ -435,18 +420,11 @@ fn generate_member(aspect_id: u64, aspect_name: &str, member: &Member) -> TokenS
quote! { quote! {
#[doc = #description] #[doc = #description]
pub async fn #name(#argument_decls) -> crate::core::error::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> { pub async fn #name(#argument_decls) -> crate::core::error::Result<(#return_type, Vec<std::os::fd::OwnedFd>)> {
let arguments = (#argument_uses); let result = _node.execute_remote_method_typed(#aspect_id, #opcode, &(#argument_uses), vec![]).await;
let (#(#arguments),*) = &arguments; if let Err(err) = result.as_ref() {
::tracing::trace!(#argument_debug "called client method: {}::{}",#aspect_name,#name_str); ::tracing::warn!("failed to call remote method: {}::{}, error: {}",#aspect_name,#name_str,err);
let result = _node.execute_remote_method_typed(#aspect_id, #opcode, &arguments, vec![]).await; } else {
::tracing::trace!("called remote method: {}::{}",#aspect_name,#name_str);
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);
}
} }
result 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 { fn generate_run_member(aspect_name: &Ident, _type: MemberType, member: &Member) -> TokenStream {
let opcode = member.opcode; let opcode = member.opcode;
let member_name_ident = Ident::new(&member.name, Span::call_site()); 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 let argument_names = member
.arguments .arguments
@@ -485,13 +461,6 @@ fn generate_run_member(aspect_name: &Ident, _type: MemberType, member: &Member)
generate_argument_type(&_type, a.optional, true) generate_argument_type(&_type, a.optional, true)
}) })
.reduce(|a, b| quote!(#a, #b)); .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); // dbg!(&argument_types);
let deserialize = argument_names let deserialize = argument_names
.clone() .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)) .map(|a| generate_argument_deserialize(&a.name, &a._type, a.optional))
.reduce(|a, b| quote!(#a, #b)) .reduce(|a, b| quote!(#a, #b))
.unwrap_or_default(); .unwrap_or_default();
let member_name = member_name_ident.to_string();
let aspect_name_str = aspect_name.to_string();
match _type { match _type {
MemberType::Signal => quote! { MemberType::Signal => quote! {
#opcode => (move || { #opcode => { let result = (move || {
#deserialize #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) <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! { MemberType::Method => quote! {
#opcode => _method_response.wrap_async(async move { #opcode => _method_response.wrap_async(async move {
#deserialize #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;
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);
match result.as_ref() { } else {
Ok(value) => { ::tracing::trace!("called local method: {}::{}",#aspect_name_str,#member_name);
::tracing::trace!(?value, "client method returned value: {}::{}",#aspect_name_str,#member_name); };
}, let result = result?;
Err(err) => { Ok((#serialize, Vec::<std::os::fd::OwnedFd>::new()))
::tracing::warn!("client method returned error: {}::{}, error: {}",#aspect_name_str,#member_name,err);
}
}
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" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1754487366, "lastModified": 1722555600,
"narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=", "narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18", "rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -47,11 +47,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1754487366, "lastModified": 1712014858,
"narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=", "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18", "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -65,11 +65,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1725868201, "lastModified": 1713050562,
"narHash": "sha256-rDBQ9tXQCCA7emikSYH59ADJELE2IpzB7eoLrpHYzU4=", "narHash": "sha256-m7c6XpmpTM1URuqMG2KqtaWbL2Vt8vJFJtmvq123BmY=",
"owner": "StardustXR", "owner": "StardustXR",
"repo": "flatland", "repo": "flatland",
"rev": "0914dd3df54a5e6258dfc0a02d65af1c0fc0fc90", "rev": "b3b0f29c4ea1b82c96cf9de507837bf15a5e4c0e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -84,11 +84,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1755233722, "lastModified": 1719226092,
"narHash": "sha256-AavrbMltJKcC2Fx0lfJoZfmy7g87ebXU0ddVenhajLA=", "narHash": "sha256-YNkUMcCUCpnULp40g+svYsaH1RbSEj6s4WdZY/SHe38=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "hercules-ci-effects", "repo": "hercules-ci-effects",
"rev": "99e03e72e3f7e13506f80ef9ebaedccb929d84d0", "rev": "11e4b8dc112e2f485d7c97e1cee77f9958f498f5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -115,26 +115,23 @@
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1753579242, "lastModified": 1722555339,
"narHash": "sha256-zvaMGVn14/Zz8hnp4VWT9xVnhc8vuL3TStRqwk22biA=", "narHash": "sha256-uFf2QeW7eAHlYXuDktm9c25OxOyCoUOQmh5SZ9amE5Q=",
"owner": "nix-community", "type": "tarball",
"repo": "nixpkgs.lib", "url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
"rev": "0f36c44e01a6129be94e3ade315a5883f0228a6e",
"type": "github"
}, },
"original": { "original": {
"owner": "nix-community", "type": "tarball",
"repo": "nixpkgs.lib", "url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
"type": "github"
} }
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1755027561, "lastModified": 1713714899,
"narHash": "sha256-IVft239Bc8p8Dtvf7UAACMG5P3ZV+3/aO28gXpGtMXI=", "narHash": "sha256-+z/XjO3QJs5rLE5UOf015gdVauVRQd2vZtsFkaXBq2Y=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "005433b926e16227259a1843015b5b2b7f7d1fc3", "rev": "6143fc5eeb9c4f00163267708e26191d1e918932",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -146,11 +143,11 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1755186698, "lastModified": 1723991338,
"narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=", "narHash": "sha256-Grh5PF0+gootJfOJFenTTxDTYPidA3V28dqJ/WV7iis=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c", "rev": "8a3354191c0d7144db9756a74755672387b702ba",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

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

View File

@@ -20,8 +20,8 @@
# Set a nice desktop background that is pleasing to the eyes :3 # Set a nice desktop background that is pleasing to the eyes :3
extraGSettingsOverrides = '' extraGSettingsOverrides = ''
[org.gnome.desktop.background] [org.gnome.desktop.background]
picture-uri='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-backgrounds}/share/backgrounds/gnome/blobs-l.svg' picture-uri-dark='file://${pkgs.gnome.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
''; '';
}; };
displayManager = { displayManager = {
@@ -100,7 +100,7 @@
# Eval API is now internal so Shell needs to run in unsafe mode. # Eval API is now internal so Shell needs to run in unsafe mode.
# TODO: improve test driver so that it supports openqa-like manipulation # TODO: improve test driver so that it supports openqa-like manipulation
# that would allow us to drop this mess. # 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
rustPlatform, , src
src, , name
name, , libGL
vulkan-loader, , mesa
vulkan-headers, , xorg
mesa, , fontconfig
xorg, , libxkbcommon
fontconfig, , libclang
libxkbcommon,
libclang,
cmake, , cmake
pkg-config, , cpm-cmake
llvmPackages, , pkg-config
fetchFromGitHub, , llvmPackages
libXau, , fetchFromGitHub
, sk_gpu
, libXau
libXdmcp, , libXdmcp
stdenv, , stdenv
lib, , lib
openxr-loader, , openxr-loader
wayland,
alsa-lib,
}: }:
rustPlatform.buildRustPackage rec { rustPlatform.buildRustPackage rec {
@@ -30,27 +28,50 @@ rustPlatform.buildRustPackage rec {
lockFile = (src + "/Cargo.lock"); lockFile = (src + "/Cargo.lock");
allowBuiltinFetchGit = true; 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 = '' meshoptimizer = fetchFromGitHub {
substituteInPlace /build/cargo-vendor-dir/bevy_gltf-0.16.1/Cargo.toml \ owner = "zeux";
--replace-fail '[lints]' "" \ repo = "meshoptimizer";
--replace-fail 'workspace = true' "" 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
''; '';
nativeBuildInputs = [ cmake pkg-config llvmPackages.libcxxClang ];
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
];
buildInputs = [ buildInputs = [
vulkan-loader libGL
vulkan-headers
mesa mesa
xorg.libX11.dev xorg.libX11.dev
xorg.libXft xorg.libXft
@@ -60,9 +81,6 @@ rustPlatform.buildRustPackage rec {
libXau libXau
libXdmcp libXdmcp
openxr-loader openxr-loader
wayland
alsa-lib
]; ];
LIBCLANG_PATH = "${libclang.lib}/lib"; LIBCLANG_PATH = "${libclang.lib}/lib";
} }

View File

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

View File

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

View File

@@ -3,8 +3,10 @@ use stardust_xr::values::color::{AlphaColor, Rgb, color_space::LinearRgb};
pub trait ColorConvert { pub trait ColorConvert {
fn to_bevy(&self) -> bevy::color::Color; 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>> { impl ColorConvert for AlphaColor<f32, Rgb<f32, LinearRgb>> {
fn to_bevy(&self) -> bevy::color::Color { 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 bevy::prelude::*;
use crate::nodes::spatial::SpatialNode;
use super::bevy_channel::{BevyChannel, BevyChannelReader}; use super::bevy_channel::{BevyChannel, BevyChannelReader};
pub struct EntityHandlePlugin; pub struct EntityHandlePlugin;
@@ -15,46 +10,24 @@ impl Plugin for EntityHandlePlugin {
} }
} }
fn despawn( fn despawn(mut cmds: Commands, mut reader: ResMut<BevyChannelReader<Entity>>) {
mut cmds: Commands,
mut reader: ResMut<BevyChannelReader<Entity>>,
child_query: Query<&Children>,
has_spatial: Query<Has<SpatialNode>>,
) {
while let Some(e) = reader.read() { while let Some(e) = reader.read() {
if let Ok(children) = child_query.get(e) { cmds.entity(e).try_despawn();
for e in children {
if has_spatial.get(*e).unwrap_or_default() {
cmds.entity(*e).try_remove::<ChildOf>();
}
}
}
cmds.entity(e).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(); static DESTROY: BevyChannel<Entity> = BevyChannel::new();
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] #[derive(Deref, DerefMut, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct EntityHandleInner(Entity); pub struct EntityHandle(pub Entity);
impl Drop for EntityHandleInner { impl Drop for EntityHandle {
fn drop(&mut self) { 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), DeserializationError(#[from] DeserializationError),
#[error("Reader error: {0}")] #[error("Reader error: {0}")]
ReaderError(#[from] ReaderError), ReaderError(#[from] ReaderError),
#[cfg(feature = "wayland")]
#[error("Wayland error: {0}")]
WaylandError(waynest::server::Error),
#[error("Aspect {} does not exist for node", 0.to_string())] #[error("Aspect {} does not exist for node", 0.to_string())]
NoAspect(TypeId), NoAspect(TypeId),
#[error("{0}")] #[error("{0}")]

View File

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

View File

@@ -1,80 +1,19 @@
use crate::{ use crate::core::error::Result;
core::{ use crate::nodes::Node;
client::Client, use crate::nodes::alias::get_original;
error::{Result, ServerError}, use crate::{core::client::Client, nodes::Message};
},
nodes::{Message, Node, alias::get_original},
};
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::Serialize; use serde::Serialize;
use stardust_xr::{ use stardust_xr::scenegraph;
messenger::MethodResponse, use stardust_xr::scenegraph::ScenegraphError;
scenegraph::{self, ScenegraphError}, use stardust_xr::schemas::flex::serialize;
schemas::flex::serialize, use std::future::Future;
}; use std::os::fd::OwnedFd;
use std::{ use std::sync::{Arc, OnceLock, Weak};
os::fd::OwnedFd, use tokio::sync::oneshot;
sync::{Arc, OnceLock, Weak},
};
use tracing::{debug, debug_span}; 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)] #[derive(Default)]
pub struct Scenegraph { pub struct Scenegraph {
pub(super) client: OnceLock<Weak<Client>>, pub(super) client: OnceLock<Weak<Client>>,
@@ -106,6 +45,43 @@ impl Scenegraph {
self.nodes.lock().remove(&node) 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 { impl scenegraph::Scenegraph for Scenegraph {
fn send_signal( fn send_signal(
&self, &self,
@@ -139,15 +115,15 @@ impl scenegraph::Scenegraph for Scenegraph {
method: u64, method: u64,
data: &[u8], data: &[u8],
fds: Vec<OwnedFd>, fds: Vec<OwnedFd>,
response: MethodResponse, response: oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>,
) { ) {
let Some(client) = self.get_client() else { let Some(client) = self.get_client() else {
response.send(Err(ScenegraphError::NodeNotFound)); let _ = response.send(Err(ScenegraphError::NodeNotFound));
return; return;
}; };
debug!(aspect_id, node_id, method, "Handle method"); debug!(aspect_id, node_id, method, "Handle method");
let Some(node) = self.get_node(node_id) else { let Some(node) = self.get_node(node_id) else {
response.send(Err(ScenegraphError::NodeNotFound)); let _ = response.send(Err(ScenegraphError::NodeNotFound));
return; return;
}; };
node.execute_local_method( node.execute_local_method(

View File

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

View File

@@ -1,4 +1,3 @@
#![recursion_limit = "256"]
#![allow(clippy::empty_docs)] #![allow(clippy::empty_docs)]
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
@@ -6,14 +5,11 @@ mod core;
mod nodes; mod nodes;
mod objects; mod objects;
mod session; mod session;
mod spectator_cam;
pub mod tracking_offset; pub mod tracking_offset;
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
mod wayland; mod wayland;
use crate::nodes::drawable::sky::SkyPlugin;
use crate::nodes::input; use crate::nodes::input;
use crate::spectator_cam::SpectatorCameraPlugin;
use bevy::MinimalPlugins; use bevy::MinimalPlugins;
use bevy::a11y::AccessibilityPlugin; use bevy::a11y::AccessibilityPlugin;
@@ -22,16 +18,12 @@ use bevy::asset::{AssetMetaCheck, UnapprovedPathMode};
use bevy::audio::AudioPlugin; use bevy::audio::AudioPlugin;
use bevy::core_pipeline::CorePipelinePlugin; use bevy::core_pipeline::CorePipelinePlugin;
use bevy::core_pipeline::oit::OrderIndependentTransparencySettings; use bevy::core_pipeline::oit::OrderIndependentTransparencySettings;
use bevy::core_pipeline::tonemapping::Tonemapping;
use bevy::diagnostic::DiagnosticsPlugin; use bevy::diagnostic::DiagnosticsPlugin;
use bevy::ecs::schedule::{ExecutorKind, ScheduleLabel}; use bevy::ecs::schedule::{ExecutorKind, ScheduleLabel};
use bevy::gizmos::GizmoPlugin; use bevy::gizmos::GizmoPlugin;
use bevy::gltf::GltfPlugin; use bevy::gltf::GltfPlugin;
use bevy::input::InputPlugin; use bevy::input::InputPlugin;
use bevy::pbr::PbrPlugin; use bevy::pbr::PbrPlugin;
use bevy::render::pipelined_rendering::{
PipelinedRenderThreadOnCreateCallback, PipelinedRenderingPlugin,
};
use bevy::render::settings::{Backends, RenderCreation, WgpuSettings}; use bevy::render::settings::{Backends, RenderCreation, WgpuSettings};
use bevy::render::{RenderDebugFlags, RenderPlugin}; use bevy::render::{RenderDebugFlags, RenderPlugin};
use bevy::scene::ScenePlugin; use bevy::scene::ScenePlugin;
@@ -70,20 +62,17 @@ use objects::play_space::PlaySpacePlugin;
use openxr::{EnvironmentBlendMode, ReferenceSpaceType}; use openxr::{EnvironmentBlendMode, ReferenceSpaceType};
use session::{launch_start, save_session}; use session::{launch_start, save_session};
use stardust_xr::schemas::dbus::object_registry::ObjectRegistry; use stardust_xr::schemas::dbus::object_registry::ObjectRegistry;
use stardust_xr::server::LockedSocket; use stardust_xr::server;
use std::ops::DerefMut as _; use std::ops::DerefMut as _;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
use tokio::net::UnixListener; use tokio::net::UnixListener;
use tokio::sync::Notify; use tokio::sync::Notify;
use tokio::task::JoinError; use tokio::task::JoinError;
use tracing::metadata::LevelFilter; use tracing::metadata::LevelFilter;
use tracing::{error, info}; use tracing::{error, info};
use tracing_subscriber::filter::Directive;
use tracing_subscriber::{EnvFilter, fmt, prelude::*}; use tracing_subscriber::{EnvFilter, fmt, prelude::*};
use tracking_offset::TrackingOffsetPlugin; use tracking_offset::TrackingOffsetPlugin;
#[cfg(feature = "wayland")]
use wayland::{Wayland, WaylandPlugin}; use wayland::{Wayland, WaylandPlugin};
use zbus::Connection; use zbus::Connection;
use zbus::fdo::ObjectManager; use zbus::fdo::ObjectManager;
@@ -95,11 +84,7 @@ use bevy::prelude::*;
struct CliArgs { struct CliArgs {
/// Force flatscreen mode and use the mouse pointer as a 3D pointer /// Force flatscreen mode and use the mouse pointer as a 3D pointer
#[clap(short, long, action)] #[clap(short, long, action)]
force_flatscreen: bool, flatscreen: bool,
/// Replaces the flatscreen mode with a first person spectator camera
#[clap(short, long, action)]
spectator: bool,
/// Creates a transparent window fot the flatscreen mode /// Creates a transparent window fot the flatscreen mode
#[clap(short, long, action)] #[clap(short, long, action)]
@@ -147,7 +132,9 @@ async fn main() -> Result<AppExit, JoinError> {
); );
#[cfg(feature = "profile_tokio")] #[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() let log_layer = fmt::Layer::new()
.with_thread_names(true) .with_thread_names(true)
@@ -156,23 +143,22 @@ async fn main() -> Result<AppExit, JoinError> {
.with_filter( .with_filter(
EnvFilter::builder() EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into()) .with_default_directive(LevelFilter::WARN.into())
.from_env_lossy() .from_env_lossy(),
.add_directive(Directive::from_str("bevy_mesh_text_3d::text_glyphs=off").unwrap()),
); );
registry.with(log_layer).init(); registry.with(log_layer).init();
let cli_args = CliArgs::parse(); let cli_args = CliArgs::parse();
let locked_socket = let socket_path =
LockedSocket::get_free().expect("Unable to find a free stardust socket path"); server::get_free_socket_path().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"); 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!( info!(
socket_path = ?locked_socket.socket_path.display(), socket_path = ?socket_path.display(),
"Stardust socket created" "Stardust socket created"
); );
let socket = UnixListener::bind(locked_socket.socket_path) let socket =
.expect("Couldn't spawn stardust server at {socket_path}"); UnixListener::bind(socket_path).expect("Couldn't spawn stardust server at {socket_path}");
task::new(|| "Stardust socket accept loop", async move { task::new(|| "client join loop", async move {
loop { loop {
let Ok((stream, _)) = socket.accept().await else { let Ok((stream, _)) = socket.accept().await else {
continue; continue;
@@ -210,9 +196,10 @@ async fn main() -> Result<AppExit, JoinError> {
.await .await
.expect("Couldn't add the object manager"); .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 _wayland = Wayland::new().expect("Couldn't create Wayland instance");
let ready_notifier = Arc::new(Notify::new()); let ready_notifier = Arc::new(Notify::new());
@@ -255,7 +242,7 @@ async fn main() -> Result<AppExit, JoinError> {
#[derive(ScheduleLabel, Hash, Debug, PartialEq, Eq, Clone, Copy)] #[derive(ScheduleLabel, Hash, Debug, PartialEq, Eq, Clone, Copy)]
pub struct PreFrameWait; pub struct PreFrameWait;
#[derive(Resource, Deref)] #[derive(Resource, Deref)]
pub struct ObjectRegistryRes(Arc<ObjectRegistry>); pub struct ObjectRegistryRes(ObjectRegistry);
#[derive(Resource, Deref)] #[derive(Resource, Deref)]
pub struct DbusConnection(Connection); pub struct DbusConnection(Connection);
@@ -264,7 +251,7 @@ fn bevy_loop(
_project_dirs: Option<ProjectDirs>, _project_dirs: Option<ProjectDirs>,
args: CliArgs, args: CliArgs,
dbus_connection: Connection, dbus_connection: Connection,
object_registry: Arc<ObjectRegistry>, object_registry: ObjectRegistry,
) -> AppExit { ) -> AppExit {
let mut app = App::new(); let mut app = App::new();
app.insert_resource(DbusConnection(dbus_connection)); app.insert_resource(DbusConnection(dbus_connection));
@@ -312,11 +299,8 @@ fn bevy_loop(
// .add(AnimationPlugin) // .add(AnimationPlugin)
.add(AudioPlugin::default()) .add(AudioPlugin::default())
.add(GizmoPlugin) .add(GizmoPlugin)
.add(WindowPlugin::default()); .add(WindowPlugin::default())
#[cfg(feature = "wayland")] .add(DmabufImportPlugin);
{
plugins = plugins.add(DmabufImportPlugin);
}
let mut task_pool_plugin = TaskPoolPlugin::default(); let mut task_pool_plugin = TaskPoolPlugin::default();
// make tokio work // make tokio work
let handle = tokio::runtime::Handle::current(); let handle = tokio::runtime::Handle::current();
@@ -332,22 +316,19 @@ fn bevy_loop(
.async_compute .async_compute
.on_thread_spawn = Some(enter_runtime_context.clone()); .on_thread_spawn = Some(enter_runtime_context.clone());
plugins = plugins.set(task_pool_plugin); plugins = plugins.set(task_pool_plugin);
if std::env::var("DISPLAY").is_ok_and(|s| !s.is_empty()) if args.flatscreen
|| std::env::var("WAYLAND_DISPLAY").is_ok_and(|s| !s.is_empty()) || 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())
{ {
let mut plugin = WinitPlugin::<WakeUp>::default(); let mut plugin = WinitPlugin::<WakeUp>::default();
plugin.run_on_any_thread = true; plugin.run_on_any_thread = true;
plugins = plugins.add(plugin).disable::<ScheduleRunnerPlugin>(); plugins = plugins
plugins = match args.spectator { .add(plugin)
true => plugins.add(SpectatorCameraPlugin), .disable::<ScheduleRunnerPlugin>()
false => plugins.add(FlatscreenInputPlugin), .add(FlatscreenInputPlugin);
};
} }
app.insert_resource(PipelinedRenderThreadOnCreateCallback(
enter_runtime_context.clone(),
));
app.add_plugins( app.add_plugins(
if !args.force_flatscreen { if !args.flatscreen {
add_xr_plugins(plugins) add_xr_plugins(plugins)
.set(OxrInitPlugin { .set(OxrInitPlugin {
app_info: AppInfo { app_info: AppInfo {
@@ -389,7 +370,7 @@ fn bevy_loop(
composite_alpha_mode: if args.transparent_flatscreen { composite_alpha_mode: if args.transparent_flatscreen {
CompositeAlphaMode::PreMultiplied CompositeAlphaMode::PreMultiplied
} else { } else {
CompositeAlphaMode::Auto CompositeAlphaMode::Inherit
}, },
title: "StardustXR server flatscreen mode".to_string(), title: "StardustXR server flatscreen mode".to_string(),
..default() ..default()
@@ -397,10 +378,8 @@ fn bevy_loop(
..default() ..default()
}), }),
); );
app.add_plugins(PipelinedRenderingPlugin);
app.add_plugins(bevy_sk::hand::HandPlugin); app.add_plugins(bevy_sk::hand::HandPlugin);
app.add_plugins(bevy_equirect::EquirectangularPlugin);
// app.add_plugins(HandGizmosPlugin); // app.add_plugins(HandGizmosPlugin);
app.world_mut().resource_mut::<AmbientLight>().brightness = 1000.0; app.world_mut().resource_mut::<AmbientLight>().brightness = 1000.0;
if let Some(priority) = args.overlay_priority { if let Some(priority) = args.overlay_priority {
@@ -437,19 +416,15 @@ fn bevy_loop(
TextNodePlugin, TextNodePlugin,
LinesNodePlugin, LinesNodePlugin,
AudioNodePlugin, AudioNodePlugin,
// not really a node ig? at least for now
SkyPlugin,
)); ));
// object plugins // object plugins
app.add_plugins((PlaySpacePlugin, HandPlugin, ControllerPlugin, HmdPlugin)); app.add_plugins((PlaySpacePlugin, HandPlugin, ControllerPlugin, HmdPlugin));
// feature plugins // feature plugins
#[cfg(feature = "wayland")] app.add_plugins((WaylandPlugin, TrackingOffsetPlugin, FieldDebugGizmoPlugin));
app.add_plugins(WaylandPlugin);
app.add_plugins((TrackingOffsetPlugin, FieldDebugGizmoPlugin));
app.add_systems(PostStartup, move || { app.add_systems(PostStartup, move || {
ready_notifier.notify_waiters(); ready_notifier.notify_waiters();
}); });
app.add_observer(cam_settings); app.add_observer(cam_observer);
app.add_systems( app.add_systems(
XrFirst, XrFirst,
xr_step xr_step
@@ -460,13 +435,11 @@ fn bevy_loop(
app.run() app.run()
} }
fn cam_settings( fn cam_observer(
trigger: Trigger<OnAdd, Camera3d>, trigger: Trigger<OnAdd, Camera3d>,
mut query: Query<(Entity, &mut Projection, &mut Msaa, &mut Tonemapping), With<Camera3d>>, mut query: Query<(&mut Projection, &mut Msaa), With<Camera3d>>,
mut cmds: Commands,
) { ) {
let Ok((entity, mut projection, mut msaa, mut tonemapping)) = query.get_mut(trigger.target()) let Ok((mut projection, mut msaa)) = query.get_mut(trigger.target()) else {
else {
return; return;
}; };
info!("modifying cam"); info!("modifying cam");
@@ -482,9 +455,26 @@ fn cam_settings(
} }
} }
*msaa = Msaa::Off; *msaa = Msaa::Off;
*tonemapping = Tonemapping::None; }
cmds.entity(entity)
.insert(OrderIndependentTransparencySettings::default()); fn add_oit(
mut commands: Commands,
cameras: Query<
Entity,
(
With<Camera3d>,
Without<OrderIndependentTransparencySettings>,
),
>,
) {
for entity in &cameras {
commands
.entity(entity)
.insert(OrderIndependentTransparencySettings {
layer_count: 4,
alpha_threshold: 0.00,
});
}
} }
fn xr_step(world: &mut World) { fn xr_step(world: &mut World) {
@@ -494,6 +484,13 @@ fn xr_step(world: &mut World) {
let time = world.resource::<bevy::prelude::Time>().delta_secs_f64(); let time = world.resource::<bevy::prelude::Time>().delta_secs_f64();
nodes::root::Root::send_frame_events(time); 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 let should_wait = world
.run_system_cached(should_run_frame_loop) .run_system_cached(should_run_frame_loop)
.unwrap_or(false); .unwrap_or(false);
@@ -513,13 +510,3 @@ fn xr_step(world: &mut World) {
tick_internal_client(); 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 super::{Aspect, AspectIdentifier, Node};
use crate::core::{ use crate::core::{client::Client, error::Result, registry::Registry};
client::Client, error::Result, registry::Registry, scenegraph::MethodResponseSender,
};
use std::{ use std::{
ops::Add, ops::Add,
sync::{Arc, Weak}, sync::{Arc, Weak},
@@ -73,6 +71,9 @@ impl AspectIdentifier for Alias {
const ID: u64 = 0; const ID: u64 = 0;
} }
impl Aspect for Alias { impl Aspect for Alias {
fn as_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
self
}
fn run_signal( fn run_signal(
&self, &self,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
@@ -88,7 +89,7 @@ impl Aspect for Alias {
_node: Arc<Node>, _node: Arc<Node>,
_method: u64, _method: u64,
_message: super::Message, _message: super::Message,
_response: MethodResponseSender, _response: crate::core::scenegraph::MethodResponseSender,
) { ) {
} }
} }
@@ -121,7 +122,6 @@ impl AliasList {
fn add(&self, node: &Arc<Node>) { fn add(&self, node: &Arc<Node>) {
self.0.add_raw(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>> { pub fn get_from_original_node(&self, original: Weak<Node>) -> Option<Arc<Node>> {
self.0 self.0
.get_valid_contents() .get_valid_contents()

View File

@@ -57,27 +57,29 @@ fn update_sound_event(
for sound in SOUND_REGISTRY.get_valid_contents() { for sound in SOUND_REGISTRY.get_valid_contents() {
if sound.entity.get().is_none() { if sound.entity.get().is_none() {
let handle = asset_server.load(sound.pending_audio_path.as_path()); let handle = asset_server.load(sound.pending_audio_path.as_path());
let entity = cmds sound
.spawn(( .entity
Name::new("Audio Node"), .set(
SpatialNode(Arc::downgrade(&sound.spatial)), cmds.spawn((
AudioPlayer::new(handle), Name::new("Audio Node"),
PlaybackSettings { SpatialNode(Arc::downgrade(&sound.spatial)),
mode: PlaybackMode::Once, AudioPlayer::new(handle),
volume: Volume::Linear(sound.volume), PlaybackSettings {
speed: 1.0, mode: PlaybackMode::Once,
paused: true, volume: Volume::Linear(sound.volume),
muted: false, speed: 1.0,
spatial: true, paused: true,
spatial_scale: None, muted: false,
}, spatial: true,
)) spatial_scale: None,
.id(); },
let entity = EntityHandle::new(entity); ))
sound.spatial.set_entity(entity.clone()); .id()
sound.entity.set(entity).unwrap(); .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() { if sound.play.lock().take().is_some() {
sink.play(); 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, client::Client, color::ColorConvert, entity_handle::EntityHandle, error::Result,
registry::Registry, registry::Registry,
}, },
nodes::{Node, drawable::LinePoint, spatial::Spatial}, nodes::{
Node,
spatial::{Spatial, SpatialNode},
},
}; };
use bevy::{ use bevy::{
asset::{AssetEvents, RenderAssetUsages, weak_handle}, asset::RenderAssetUsages,
pbr::{ExtendedMaterial, MaterialExtension},
prelude::*, prelude::*,
render::{ render::{
mesh::{Indices, PrimitiveTopology, VertexAttributeValues}, mesh::{Indices, MeshAabb, PrimitiveTopology},
primitives::Aabb, primitives::Aabb,
render_resource::{AsBindGroup, ShaderRef},
view::VisibilitySystems,
}, },
}; };
use glam::Vec3; use glam::Vec3;
@@ -24,79 +24,25 @@ use std::sync::{
Arc, OnceLock, Arc, OnceLock,
atomic::{AtomicBool, Ordering}, 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; pub struct LinesNodePlugin;
impl Plugin for LinesNodePlugin { impl Plugin for LinesNodePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems( app.add_systems(Update, build_line_mesh);
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());
} }
} }
fn build_line_mesh( fn build_line_mesh(
mut cmds: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<LineMaterial>>, mut cmds: Commands,
query: Query<(Ref<GlobalTransform>, &InheritedVisibility)>, 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); lines.gen_mesh.store(false, Ordering::Relaxed);
let mut vertex_positions = Vec::<Vec3>::new(); let mut vertex_positions = Vec::<Vec3>::new();
let mut vertex_normals = 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 mut vertex_indices = Vec::<u32>::new();
let lines_data = lines.data.lock(); let lines_data = lines.data.lock();
if lines_data.is_empty() { if lines_data.is_empty() {
*lines.bounds.lock() = Some(Aabb::default()); *lines.bounds.lock() = Aabb::default();
lines.setup_complete.notify_waiters();
match lines.entity.get() { match lines.entity.get() {
Some(e) => cmds.entity(**e), Some(e) => cmds.entity(**e),
None => { None => {
// if we couldn't get the lines entity then we need to gen the mesh later let e = cmds.spawn((
lines.gen_mesh.store(true, Ordering::Relaxed); Name::new("LinesNode"),
continue; SpatialNode(Arc::downgrade(&lines.spatial)),
));
_ = lines.entity.set(e.id().into());
e
} }
} }
.remove::<Mesh3d>(); .remove::<Mesh3d>();
@@ -120,35 +68,15 @@ fn build_line_mesh(
let mut indices_set = 0; let mut indices_set = 0;
for line in lines_data.iter() { 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; let start_set = indices_set;
// Create a sliding window of points to process each segment of the line // 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 cyclic lines: wraps around by connecting last point back to first
// For non-cyclic lines: handles endpoints with None values // For non-cyclic lines: handles endpoints with None values
let point_windows = { let point_windows = {
let mut out = Vec::new(); let mut out = Vec::new();
let mut last = line.cyclic.then(|| line_points.last()).flatten(); let mut last = line.cyclic.then(|| line.points.last()).flatten();
let mut peekable = line_points.iter().peekable(); let mut peekable = line.points.iter().peekable();
while let Some(curr) = peekable.next() { 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;
}
let mut end = false; let mut end = false;
// Determine the next point - either the next in sequence or // Determine the next point - either the next in sequence or
// for cyclic lines, wrap back to first point at the end // for cyclic lines, wrap back to first point at the end
@@ -156,7 +84,7 @@ fn build_line_mesh(
Some(v) => Some(*v), Some(v) => Some(*v),
None => { None => {
end = true; end = true;
line.cyclic.then(|| line_points.first()).flatten() line.cyclic.then(|| line.points.first()).flatten()
} }
}; };
@@ -165,10 +93,6 @@ fn build_line_mesh(
} }
out out
}; };
// if we can't make a full line, don't bother trying
if point_windows.len() < 2 {
continue;
}
for (last, curr, next, last_point) in point_windows { for (last, curr, next, last_point) in point_windows {
let last_quat = last.map(|v| { let last_quat = last.map(|v| {
Quat::from_rotation_arc( Quat::from_rotation_arc(
@@ -191,10 +115,6 @@ fn build_line_mesh(
(Some(last), None) => last, (Some(last), None) => last,
(Some(last), Some(next)) => last.lerp(next, 0.5), (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 = [ let normals = [
Vec3::X, Vec3::X,
Vec3::new(1., 0., 1.).normalize(), Vec3::new(1., 0., 1.).normalize(),
@@ -206,81 +126,71 @@ fn build_line_mesh(
Vec3::new(1., 0., -1.).normalize(), Vec3::new(1., 0., -1.).normalize(),
] ]
.map(Vec3::normalize) .map(Vec3::normalize)
.map(|v| quat * v); .map(|v| (quat * v));
let points = normals.map(|v| (v * curr.thickness) + Vec3::from(curr.point)); let points = normals.map(|v| (v * curr.thickness) + Vec3::from(curr.point));
vertex_normals.extend(normals); vertex_normals.extend(normals);
vertex_positions.extend(points); 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 // Only connect vertices between segments if this isn't the end point
if !last_point { if !last_point {
vertex_indices.extend(indices(indices_set)); vertex_indices.extend(indices(indices_set));
} }
indices_set += 1; indices_set += 1;
} }
if indices_set > 0 { // Handle the connection between start and end points:
// Handle the connection between start and end points: // - For cyclic lines: connect last segment back to first
// - For cyclic lines: connect last segment back to first // - For non-cyclic lines: add caps at both ends
// - For non-cyclic lines: add caps at both ends if line.cyclic {
if line.cyclic { vertex_indices.extend(cyclic_indices(start_set, indices_set - 1));
vertex_indices.extend(cyclic_indices(start_set, indices_set - 1)); } else {
} else { vertex_indices.extend(cap_indices(start_set, false));
vertex_indices.extend(cap_indices(start_set, false)); vertex_indices.extend(cap_indices(indices_set - 1, true));
vertex_indices.extend(cap_indices(indices_set - 1, true));
}
} }
} }
let mut mesh = Mesh::new( let mut mesh = Mesh::new(
PrimitiveTopology::TriangleList, PrimitiveTopology::TriangleList,
RenderAssetUsages::RENDER_WORLD, 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_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_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), Some(e) => cmds.entity(**e),
None => { None => {
let e = cmds.spawn(( let e = cmds.spawn((
Name::new("LinesNode"), Name::new("LinesNode"),
MeshMaterial3d(materials.add(ExtendedMaterial { SpatialNode(Arc::downgrade(&lines.spatial)),
base: BevyMaterial { MeshMaterial3d(materials.add(BevyMaterial {
base_color: Color::WHITE, base_color: Color::WHITE,
perceptual_roughness: 1.0, perceptual_roughness: 1.0,
alpha_mode: AlphaMode::Premultiplied, // TODO: this should be Blend
emissive: Color::linear_rgba(0.25, 0.25, 0.25, 1.0).into(), alpha_mode: AlphaMode::Opaque,
..default() ..default()
},
extension: LineExtension {},
})), })),
)); ));
_ = lines.entity.set(EntityHandle::new(e.id())); _ = lines.entity.set(e.id().into());
e 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(Mesh3d(meshes.add(mesh)))
.insert(*visibil)
.insert(match visibil.get() {
true => Visibility::Visible,
false => Visibility::Hidden,
});
} }
} }
@@ -322,8 +232,7 @@ pub struct Lines {
data: Mutex<Vec<Line>>, data: Mutex<Vec<Line>>,
gen_mesh: AtomicBool, gen_mesh: AtomicBool,
entity: OnceLock<EntityHandle>, entity: OnceLock<EntityHandle>,
bounds: Mutex<Option<Aabb>>, bounds: Mutex<Aabb>,
setup_complete: Notify,
} }
impl Lines { impl Lines {
pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> { pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
@@ -332,19 +241,10 @@ impl Lines {
.unwrap() .unwrap()
.bounding_box_calc .bounding_box_calc
.set(|node| { .set(|node| {
Box::pin(async { node.get_aspect::<Lines>()
let Ok(lines) = node.get_aspect::<Lines>() else { .ok()
return Default::default(); .map(|v| *v.bounds.lock())
}; .unwrap_or_default()
let bounds = *lines.bounds.lock();
match bounds {
Some(aabb) => aabb,
None => {
lines.setup_complete.notified().await;
lines.bounds.lock().unwrap_or_default()
}
}
})
}); });
let lines = LINES_REGISTRY.add(Lines { let lines = LINES_REGISTRY.add(Lines {
@@ -352,8 +252,7 @@ impl Lines {
data: Mutex::new(lines), data: Mutex::new(lines),
gen_mesh: AtomicBool::new(true), gen_mesh: AtomicBool::new(true),
entity: OnceLock::new(), entity: OnceLock::new(),
bounds: Mutex::new(Some(Aabb::default())), bounds: Mutex::new(Aabb::default()),
setup_complete: Notify::new(),
}); });
node.add_aspect_raw(lines.clone()); node.add_aspect_raw(lines.clone());

View File

@@ -1,6 +1,5 @@
pub mod lines; pub mod lines;
pub mod model; pub mod model;
pub mod sky;
pub mod text; pub mod text;
use self::{lines::Lines, model::Model, text::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::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock, Weak}; use std::sync::{Arc, OnceLock, Weak};
use tokio::sync::Notify;
static LOAD_MODEL: BevyChannel<(Arc<Model>, PathBuf)> = BevyChannel::new(); static LOAD_MODEL: BevyChannel<(Arc<Model>, PathBuf)> = BevyChannel::new();
@@ -55,7 +54,11 @@ impl Plugin for ModelNodePlugin {
app.init_resource::<MaterialRegistry>(); app.init_resource::<MaterialRegistry>();
app.add_systems( app.add_systems(
Update, Update,
(load_models, gen_model_parts, apply_materials) (
load_models,
gen_model_parts.after(TransformSystem::TransformPropagate),
apply_materials,
)
.chain() .chain()
.in_set(ModelNodeSystemSet), .in_set(ModelNodeSystemSet),
); );
@@ -64,14 +67,7 @@ impl Plugin for ModelNodePlugin {
// No extra data needed for a simple holdout // No extra data needed for a simple holdout
#[derive(Default, Asset, AsBindGroup, TypePath, Debug, Clone)] #[derive(Default, Asset, AsBindGroup, TypePath, Debug, Clone)]
#[data(50, u32, binding_array(101))]
#[bindless(index_table(range(50..51), binding(100)))]
pub struct HoldoutExtension {} pub struct HoldoutExtension {}
impl From<&HoldoutExtension> for u32 {
fn from(_: &HoldoutExtension) -> Self {
0
}
}
impl MaterialExtension for HoldoutExtension { impl MaterialExtension for HoldoutExtension {
fn fragment_shader() -> ShaderRef { fn fragment_shader() -> ShaderRef {
HOLDOUT_SHADER_HANDLE.into() HOLDOUT_SHADER_HANDLE.into()
@@ -105,13 +101,9 @@ fn load_models(
SceneRoot(handle), SceneRoot(handle),
ModelNode(Arc::downgrade(&model)), ModelNode(Arc::downgrade(&model)),
SpatialNode(Arc::downgrade(&model.spatial)), SpatialNode(Arc::downgrade(&model.spatial)),
Visibility::Hidden,
)) ))
.id(); .id();
model model.bevy_scene_entity.set(entity.into()).unwrap();
.bevy_scene_entity
.set(EntityHandle::new(entity))
.unwrap();
} }
} }
@@ -148,7 +140,7 @@ fn apply_materials(
for (param_name, param) in model_part.pending_material_parameters.lock().drain() { for (param_name, param) in model_part.pending_material_parameters.lock().drain() {
let mut new_mat = materials.get(&mesh_mat.0).unwrap().clone(); let mut new_mat = materials.get(&mesh_mat.0).unwrap().clone();
param.apply_to_material( param.apply_to_material(
&model_part.spatial.node().unwrap().get_client().unwrap(), &model_part.space.node().unwrap().get_client().unwrap(),
&mut new_mat, &mut new_mat,
&param_name, &param_name,
&asset_server, &asset_server,
@@ -197,7 +189,7 @@ fn gen_model_parts(
.unwrap_or_else(|| name.to_string()); .unwrap_or_else(|| name.to_string());
let parent_spatial = parent let parent_spatial = parent
.as_ref() .as_ref()
.map(|p| p.spatial.clone()) .map(|p| p.space.clone())
.unwrap_or_else(|| model.spatial.clone()); .unwrap_or_else(|| model.spatial.clone());
let client = model.spatial.node()?.get_client()?; let client = model.spatial.node()?.get_client()?;
let (spatial, model_part) = let (spatial, model_part) =
@@ -207,7 +199,7 @@ fn gen_model_parts(
client.scenegraph.add_node(Node::generate(&client, false)); client.scenegraph.add_node(Node::generate(&client, false));
let spatial = Spatial::add_to( let spatial = Spatial::add_to(
&node, &node,
Some(parent_spatial.clone()), Some(parent_spatial),
transform.compute_matrix(), transform.compute_matrix(),
false, false,
); );
@@ -215,7 +207,8 @@ fn gen_model_parts(
entity: OnceLock::new(), entity: OnceLock::new(),
mesh_entity: OnceLock::new(), mesh_entity: OnceLock::new(),
path, path,
spatial: spatial.clone(), space: spatial.clone(),
_model: Arc::downgrade(&model),
pending_material_parameters: Mutex::default(), pending_material_parameters: Mutex::default(),
pending_material_replacement: Mutex::default(), pending_material_replacement: Mutex::default(),
holdout: AtomicBool::new(false), holdout: AtomicBool::new(false),
@@ -225,8 +218,8 @@ fn gen_model_parts(
(spatial, model_part) (spatial, model_part)
} }
Some(part) => { Some(part) => {
part.spatial.set_spatial_parent(&parent_spatial).unwrap(); part.space.set_spatial_parent(&parent_spatial).unwrap();
(part.spatial.clone(), part.clone()) (part.space.clone(), part.clone())
} }
}; };
let aabb = Aabb::enclosing( let aabb = Aabb::enclosing(
@@ -243,17 +236,13 @@ fn gen_model_parts(
) )
.unwrap_or_default(); .unwrap_or_default();
_ = spatial.bounding_box_calc.set(move |n| { _ = spatial.bounding_box_calc.set(move |n| {
Box::pin(async { n.get_aspect::<ModelPart>()
n.get_aspect::<ModelPart>() .ok()
.ok() .and_then(|v| v.bounds.get().copied())
.and_then(|v| v.bounds.get().copied()) .unwrap_or_default()
.unwrap_or_default()
})
}); });
let _ = spatial.set_spatial_parent(&parent_spatial);
spatial.set_local_transform(transform.compute_matrix()); spatial.set_local_transform(transform.compute_matrix());
let entity_handle = EntityHandle::new(entity);
spatial.set_entity(entity_handle.clone());
cmds.entity(entity) cmds.entity(entity)
.insert(SpatialNode(Arc::downgrade(&spatial))); .insert(SpatialNode(Arc::downgrade(&spatial)));
let mesh_entity = children_query let mesh_entity = children_query
@@ -262,20 +251,14 @@ fn gen_model_parts(
.flat_map(|v| v.iter()) .flat_map(|v| v.iter())
.find(|e| has_mesh.get(*e).unwrap_or(false))?; .find(|e| has_mesh.get(*e).unwrap_or(false))?;
_ = model_part.bounds.set(aabb); _ = model_part.bounds.set(aabb);
_ = model_part.entity.set(entity_handle); _ = model_part.entity.set(entity.into());
_ = model_part.mesh_entity.set(EntityHandle::new(mesh_entity)); _ = model_part.mesh_entity.set(mesh_entity.into());
parts.push(model_part.clone()); parts.push(model_part.clone());
Some(model_part) Some(model_part)
}, },
); );
} }
_ = model.parts.set(parts); _ = 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.emissive_texture.hash(state);
mat.metallic_roughness_texture.hash(state); mat.metallic_roughness_texture.hash(state);
mat.occlusion_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) { fn hash_color<H: Hasher>(color: Color, state: &mut H) {
@@ -469,7 +454,8 @@ pub struct ModelPart {
entity: OnceLock<EntityHandle>, entity: OnceLock<EntityHandle>,
mesh_entity: OnceLock<EntityHandle>, mesh_entity: OnceLock<EntityHandle>,
path: String, path: String,
spatial: Arc<Spatial>, space: Arc<Spatial>,
_model: Weak<Model>,
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>, pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
pending_material_replacement: Mutex<Option<Handle<BevyMaterial>>>, pending_material_replacement: Mutex<Option<Handle<BevyMaterial>>>,
holdout: AtomicBool, holdout: AtomicBool,
@@ -539,8 +525,6 @@ pub struct Model {
bevy_scene_entity: OnceLock<EntityHandle>, bevy_scene_entity: OnceLock<EntityHandle>,
parts: OnceLock<Vec<Arc<ModelPart>>>, parts: OnceLock<Vec<Arc<ModelPart>>>,
pre_bound_parts: Mutex<Vec<Arc<ModelPart>>>, pre_bound_parts: Mutex<Vec<Arc<ModelPart>>>,
setup_complete: AtomicBool,
setup_complete_notify: Notify,
} }
impl Model { impl Model {
pub fn add_to(node: &Arc<Node>, resource_id: ResourceID) -> Result<Arc<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(), bevy_scene_entity: OnceLock::new(),
pre_bound_parts: Mutex::default(), pre_bound_parts: Mutex::default(),
parts: OnceLock::new(), 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 LOAD_MODEL
.send((model.clone(), pending_model_path)) .send((model.clone(), pending_model_path))
@@ -598,6 +570,7 @@ impl Model {
); );
} }
None => { None => {
// TODO: this could be a denail of service vector
let client = self.spatial.node().unwrap().get_client().unwrap(); let client = self.spatial.node().unwrap().get_client().unwrap();
let part_node = client.scenegraph.add_node(Node::generate(&client, false)); let part_node = client.scenegraph.add_node(Node::generate(&client, false));
let spatial = Spatial::add_to( let spatial = Spatial::add_to(
@@ -610,7 +583,8 @@ impl Model {
entity: OnceLock::new(), entity: OnceLock::new(),
mesh_entity: OnceLock::new(), mesh_entity: OnceLock::new(),
path: part_path, path: part_path,
spatial, space: spatial,
_model: Arc::downgrade(self),
pending_material_parameters: Mutex::default(), pending_material_parameters: Mutex::default(),
pending_material_replacement: Mutex::default(), pending_material_replacement: Mutex::default(),
holdout: AtomicBool::new(false), holdout: AtomicBool::new(false),
@@ -635,7 +609,7 @@ impl ModelAspect for Model {
let model = node.get_aspect::<Model>()?; let model = node.get_aspect::<Model>()?;
let part = model.get_model_part(part_path)?; let part = model.get_model_part(part_path)?;
Alias::create_with_id( Alias::create_with_id(
&part.spatial.node().unwrap(), &part.space.node().unwrap(),
&calling_client, &calling_client,
id, id,
MODEL_PART_ASPECT_ALIAS_INFO.clone(), MODEL_PART_ASPECT_ALIAS_INFO.clone(),
@@ -646,16 +620,6 @@ impl ModelAspect for Model {
} }
impl Drop for Model { impl Drop for Model {
fn drop(&mut self) { 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); 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::{ nodes::{
Node, Node,
drawable::{TextFit, XAlign}, drawable::XAlign,
spatial::{Spatial, SpatialNode}, spatial::{Spatial, SpatialNode},
}, },
}; };
use bevy::{platform::collections::HashMap, prelude::*}; use bevy::{platform::collections::HashMap, prelude::*, render::mesh::MeshAabb};
use bevy_mesh_text_3d::{ use bevy_mesh_text_3d::{
Align, Attrs, HorizontalAnchorPoint, MeshTextPlugin, Settings as FontSettings, VerticalAlign, Align, Attrs, MeshTextPlugin, Settings as FontSettings, generate_meshes,
VerticalAnchorPoint, generate_meshes, text_glyphs::TextGlyphs,
}; };
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use core::f32; use core::f32;
use cosmic_text::Metrics;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ffi::OsStr, mem, path::PathBuf, sync::Arc}; use std::{ffi::OsStr, mem, path::PathBuf, sync::Arc};
@@ -32,7 +33,8 @@ pub struct TextNodePlugin;
impl Plugin for TextNodePlugin { impl Plugin for TextNodePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
// Text init stuff // Text init stuff
app.add_plugins(MeshTextPlugin); // 1.0 for font size in meters
app.add_plugins(MeshTextPlugin::new(1.0));
app.world_mut() app.world_mut()
.resource_mut::<FontSettings>() .resource_mut::<FontSettings>()
.font_system .font_system
@@ -70,33 +72,21 @@ fn spawn_text(
super::XAlign::Center => Align::Center, super::XAlign::Center => Align::Center,
super::XAlign::Right => Align::Left, 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 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_width = style.bounds.as_ref().map(|v| v.bounds.x);
let max_height = style.bounds.as_ref().map(|v| v.bounds.y); let max_height = style.bounds.as_ref().map(|v| v.bounds.x);
let horizontal_anchor_point = style let (width, _height) =
.bounds text_glyphs.measure(max_width, max_height, &mut font_settings.font_system);
.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 char_meshes = generate_meshes( let char_meshes = generate_meshes(
bevy_mesh_text_3d::InputText::Simple { bevy_mesh_text_3d::InputText::Simple {
text: text_string, text: text_string,
@@ -106,7 +96,8 @@ fn spawn_text(
emissive: Color::WHITE.to_linear(), emissive: Color::WHITE.to_linear(),
metallic: 0.0, metallic: 0.0,
perceptual_roughness: 1.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, double_sided: false,
..default() ..default()
}, },
@@ -118,30 +109,55 @@ fn spawn_text(
bevy_mesh_text_3d::Parameters { bevy_mesh_text_3d::Parameters {
extrusion_depth: 0.0, extrusion_depth: 0.0,
font_size: style.character_height, font_size: style.character_height,
line_height: style.character_height * 1.1, line_height: style.character_height,
alignment, alignment,
max_width: wrap.then_some(0).and(max_width), max_width,
max_height: wrap.then_some(0).and(max_height), max_height,
vertical_alignment,
horizontal_anchor_point,
vertical_anchor_point,
}, },
&mut meshes, &mut meshes,
); );
if let Some(db) = old_db { if let Some(db) = old_db {
mem::swap(font_settings.font_system.db_mut(), 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}")) char_meshes.inspect_err(|err| error!("unable to create text meshes: {err}"))
else { else {
continue; 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 let letters = char_meshes
.into_iter() .into_iter()
.map(|v| { .map(|v| {
cmds.spawn((Mesh3d(v.mesh), MeshMaterial3d(v.material), v.transform)) cmds.spawn((
.id() 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<_>>(); .collect::<Vec<_>>();
let entity = cmds let entity = cmds
@@ -151,9 +167,7 @@ fn spawn_text(
)) ))
.add_children(&letters) .add_children(&letters)
.id(); .id();
let entity = EntityHandle::new(entity); text.entity.lock().replace(EntityHandle(entity));
text.entity.lock().replace(entity.clone());
text.spatial.set_entity(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(); static TEXT_REGISTRY: Registry<Text> = Registry::new();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,21 +6,18 @@ use super::fields::{Field, FieldTrait};
use super::{Aspect, AspectIdentifier}; use super::{Aspect, AspectIdentifier};
use crate::bail; use crate::bail;
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::entity_handle::EntityHandle;
use crate::core::error::Result; use crate::core::error::Result;
use crate::core::registry::Registry; use crate::core::registry::Registry;
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO}; use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
use bevy::ecs::entity::EntityHashMap;
use bevy::prelude::Transform as BevyTransform; use bevy::prelude::Transform as BevyTransform;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::primitives::Aabb; use bevy::render::primitives::Aabb;
use color_eyre::eyre::OptionExt; use color_eyre::eyre::OptionExt;
use glam::{Mat4, Quat, Vec3, vec3a}; use glam::{Mat4, Quat, Vec3, vec3a};
use mint::Vector3; use mint::Vector3;
use parking_lot::{Mutex, RwLock}; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::pin::Pin;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, OnceLock, Weak}; use std::sync::{Arc, OnceLock, Weak};
use std::{f32, ptr}; use std::{f32, ptr};
@@ -30,60 +27,68 @@ impl Plugin for SpatialNodePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems( app.add_systems(
PostUpdate, PostUpdate,
(spawn_spatial_nodes, update_spatial_nodes) update_spatial_nodes.before(TransformSystem::TransformPropagate),
.chain()
.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( fn update_spatial_nodes(
mut query: Query<(&mut BevyTransform, &mut Visibility, Option<&ChildOf>)>, mut query: Query<(
mut cmds: Commands, &mut BevyTransform,
&SpatialNode,
Option<&ChildOf>,
&mut Visibility,
)>,
parent_query: Query<&GlobalTransform>,
) { ) {
for (entity, (transform, parent_entity)) in UPDATED_SPATIALS_NODES.lock().drain() { query
let _span = debug_span!("updating spatial node").entered(); .par_iter_mut()
let Ok((mut bevy_transform, mut vis, parent)) = query.get_mut(entity) else { .for_each(|(mut transform, spatial_node, child_of, mut vis)| {
continue; let _span = debug_span!("updating spatial node").entered();
}; let Some(spatial) = spatial_node.0.upgrade() else {
// Set visibility based on node enabled state return;
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>(),
}; };
} 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)] #[derive(Clone, Component, Debug)]
#[require(BevyTransform, Visibility)] #[require(BevyTransform, Visibility)]
pub struct SpatialNode(pub Weak<Spatial>); pub struct SpatialNode(pub Weak<Spatial>);
const EPSILON: f32 = 0.00001;
stardust_xr_server_codegen::codegen_spatial_protocol!(); stardust_xr_server_codegen::codegen_spatial_protocol!();
impl Transform { impl Transform {
pub fn to_mat4(&self, position: bool, rotation: bool, scale: bool) -> Mat4 { pub fn to_mat4(&self, position: bool, rotation: bool, scale: bool) -> Mat4 {
@@ -95,16 +100,9 @@ impl Transform {
.then_some(self.rotation) .then_some(self.rotation)
.flatten() .flatten()
.unwrap_or_else(|| Quat::IDENTITY.into()); .unwrap_or_else(|| Quat::IDENTITY.into());
// Zero scale values break everything
let scale = scale let scale = scale
.then_some(self.scale) .then_some(self.scale)
.flatten() .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])); .unwrap_or_else(|| Vector3::from([1.0; 3]));
Mat4::from_scale_rotation_translation(scale.into(), rotation.into(), position.into()) 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 struct Spatial {
pub node: Weak<Node>, pub node: Weak<Node>,
entity: RwLock<Option<EntityHandle>>, parent: Mutex<Option<Arc<Spatial>>>,
parent: RwLock<Option<Arc<Spatial>>>, old_parent: Mutex<Option<Arc<Spatial>>>,
old_parent: RwLock<Option<Arc<Spatial>>>, transform: Mutex<Mat4>,
transform: RwLock<Mat4>, zone: Mutex<Weak<Zone>>,
zone: RwLock<Weak<Zone>>,
children: Registry<Spatial>, children: Registry<Spatial>,
pub bounding_box_calc: pub bounding_box_calc: OnceLock<fn(&Node) -> Aabb>,
OnceLock<for<'a> fn(&'a Node) -> Pin<Box<dyn Future<Output = Aabb> + 'a + Send + Sync>>>,
} }
impl Spatial { impl Spatial {
pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> { pub fn new(node: Weak<Node>, parent: Option<Arc<Spatial>>, transform: Mat4) -> Arc<Self> {
let spatial = SPATIAL_REGISTRY.add(Spatial { Arc::new(Spatial {
node, node,
entity: RwLock::new(None), parent: Mutex::new(parent),
parent: RwLock::new(parent), old_parent: Mutex::new(None),
old_parent: RwLock::new(None), transform: Mutex::new(transform),
transform: RwLock::new(transform), zone: Mutex::new(Weak::new()),
zone: RwLock::new(Weak::new()),
children: Registry::new(), children: Registry::new(),
bounding_box_calc: OnceLock::default(), 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( pub fn add_to(
node: &Arc<Node>, node: &Arc<Node>,
@@ -189,17 +172,18 @@ impl Spatial {
} }
// the output bounds are probably way bigger than they need to be // 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 { let Some(node) = self.node() else {
return Aabb::default(); return Aabb::default();
}; };
let mut bounds = match self.bounding_box_calc.get() { let mut bounds = self
Some(f) => f(&node).await, .bounding_box_calc
None => Aabb::default(), .get()
}; .map(|b| (b)(&node))
.unwrap_or_default();
for child in self.children.get_valid_contents() { for child in self.children.get_valid_contents() {
let mat = child.local_transform(); 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 = Aabb::enclosing([
bounds.min().into(), bounds.min().into(),
bounds.max().into(), bounds.max().into(),
@@ -210,47 +194,9 @@ impl Spatial {
} }
bounds 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 { pub fn local_transform(&self) -> Mat4 {
*self.transform.read() *self.transform.lock()
}
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()
} }
pub fn global_transform(&self) -> Mat4 { pub fn global_transform(&self) -> Mat4 {
let parent_transform = self let parent_transform = self
@@ -261,8 +207,7 @@ impl Spatial {
parent_transform * self.local_transform() parent_transform * self.local_transform()
} }
pub fn set_local_transform(&self, transform: Mat4) { pub fn set_local_transform(&self, transform: Mat4) {
*self.transform.write() = transform; *self.transform.lock() = transform;
self.mark_dirty();
} }
pub fn set_local_transform_components( pub fn set_local_transform_components(
&self, &self,
@@ -270,7 +215,9 @@ impl Spatial {
transform: Transform, transform: Transform,
) { ) {
if reference_space == Some(self) { 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; return;
} }
let reference_to_parent_transform = reference_space let reference_to_parent_transform = reference_space
@@ -321,7 +268,7 @@ impl Spatial {
} }
fn get_parent(&self) -> Option<Arc<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>) { fn set_parent(self: &Arc<Self>, new_parent: &Arc<Spatial>) {
if let Some(parent) = self.get_parent() { if let Some(parent) = self.get_parent() {
@@ -329,8 +276,7 @@ impl Spatial {
} }
new_parent.children.add_raw(self); new_parent.children.add_raw(self);
*self.parent.write() = Some(new_parent.clone()); *self.parent.lock() = Some(new_parent.clone());
self.mark_dirty();
} }
pub fn set_spatial_parent(self: &Arc<Self>, parent: &Arc<Spatial>) -> Result<()> { 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 { pub(self) fn zone_distance(&self) -> f32 {
self.zone self.zone
.read() .lock()
.upgrade() .upgrade()
.map(|zone| zone.field.clone()) .map(|zone| zone.field.clone())
.map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0))) .map(|field| field.distance(self, vec3a(0.0, 0.0, 0.0)))
.unwrap_or(f32::NEG_INFINITY) .unwrap_or(f32::NEG_INFINITY)
} }
} }
static UPDATED_SPATIALS_NODES: Mutex<EntityHashMap<(Option<BevyTransform>, Option<Entity>)>> =
Mutex::new(EntityHashMap::new());
impl AspectIdentifier for Spatial { impl AspectIdentifier for Spatial {
impl_aspect_for_spatial_aspect_id! {} impl_aspect_for_spatial_aspect_id! {}
} }
@@ -452,7 +396,6 @@ impl Drop for Spatial {
fn drop(&mut self) { fn drop(&mut self) {
zone::release(self); zone::release(self);
ZONEABLE_REGISTRY.remove(self); ZONEABLE_REGISTRY.remove(self);
SPATIAL_REGISTRY.remove(self);
} }
} }
@@ -469,7 +412,7 @@ impl SpatialRefAspect for SpatialRef {
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
) -> Result<BoundingBox> { ) -> Result<BoundingBox> {
let this_spatial = node.get_aspect::<Spatial>()?; let this_spatial = node.get_aspect::<Spatial>()?;
let bounds = this_spatial.get_bounding_box().await; let bounds = this_spatial.get_bounding_box();
Ok(BoundingBox { Ok(BoundingBox {
center: Vec3::from(bounds.center).into(), center: Vec3::from(bounds.center).into(),
@@ -485,7 +428,7 @@ impl SpatialRefAspect for SpatialRef {
let this_spatial = node.get_aspect::<Spatial>()?; let this_spatial = node.get_aspect::<Spatial>()?;
let relative_spatial = relative_to.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 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([ let bounds = Aabb::enclosing([
mat.transform_point3(bb.min().into()), mat.transform_point3(bb.min().into()),
mat.transform_point3(bb.max().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 { impl InterfaceAspect for Interface {
fn create_spatial( fn create_spatial(
_node: Arc<Node>, _node: Arc<Node>,
@@ -530,7 +490,7 @@ impl InterfaceAspect for Interface {
zoneable: bool, zoneable: bool,
) -> Result<()> { ) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?; 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()?; let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?;
Spatial::add_to(&node, Some(parent.clone()), transform, zoneable); Spatial::add_to(&node, Some(parent.clone()), transform, zoneable);
Ok(()) Ok(())
@@ -544,7 +504,7 @@ impl InterfaceAspect for Interface {
field: Arc<Node>, field: Arc<Node>,
) -> Result<()> { ) -> Result<()> {
let parent = parent.get_aspect::<Spatial>()?; 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 field = field.get_aspect::<Field>()?;
let node = Node::from_id(&calling_client, id, true).add_to_scenegraph()?; 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); release(spatial);
*spatial.old_parent.write() = spatial.get_parent(); *spatial.old_parent.lock() = spatial.get_parent();
*spatial.zone.write() = Arc::downgrade(zone); *spatial.zone.lock() = Arc::downgrade(zone);
let Some(zone_node) = zone.spatial.node.upgrade() else { let Some(zone_node) = zone.spatial.node.upgrade() else {
return; return;
}; };
@@ -45,11 +45,11 @@ pub fn release(spatial: &Spatial) {
}; };
let spatial = spatial_node.get_aspect::<Spatial>().unwrap(); 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; return;
}; };
let _ = spatial.set_spatial_parent_in_place(&old_parent); 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() { if let Some(spatial_zone) = spatial_zone.upgrade() {
spatial_zone.captured.remove_aspect(spatial.as_ref()); spatial_zone.captured.remove_aspect(spatial.as_ref());

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
use super::{CaptureManager, DistanceCalculator, get_sorted_handlers}; use super::{CaptureManager, DistanceCalculator, get_sorted_handlers};
use crate::{ use crate::{
DbusConnection, ObjectRegistryRes, DbusConnection, ObjectRegistryRes,
core::{client::INTERNAL_CLIENT, task}, core::client::INTERNAL_CLIENT,
nodes::{ nodes::{
Node, OwnedNode, Node, OwnedNode,
fields::{EXPORTED_FIELDS, Field, FieldTrait, Ray}, fields::{EXPORTED_FIELDS, Field, FieldTrait, Ray},
@@ -9,7 +9,6 @@ use crate::{
items::panel::KEYMAPS, items::panel::KEYMAPS,
spatial::Spatial, spatial::Spatial,
}, },
objects::FieldRef,
}; };
use bevy::{ use bevy::{
input::{ input::{
@@ -21,43 +20,22 @@ use bevy::{
window::PrimaryWindow, window::PrimaryWindow,
}; };
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use dashmap::DashMap;
use glam::{Mat4, Vec3, vec3}; use glam::{Mat4, Vec3, vec3};
use mint::Vector2; use mint::Vector2;
use rustc_hash::{FxHashMap, FxHasher};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use slotmap::{DefaultKey, Key as SlotKey}; use slotmap::{DefaultKey, Key as SlotKey};
use stardust_xr::{ use stardust_xr::{
schemas::dbus::{ schemas::dbus::{interfaces::FieldRefProxy, object_registry::ObjectRegistry},
ObjectInfo,
interfaces::FieldRefProxy,
list_query::{ListEvent, ObjectListQuery},
object_registry::ObjectRegistry,
query::{ObjectQuery, QueryContext, QueryEvent},
},
values::Datamap, values::Datamap,
}; };
use std::sync::{Arc, Weak}; use std::sync::Arc;
use tokio::sync::{Notify, mpsc, watch}; use tokio::task::JoinSet;
use tokio::task::{AbortHandle, JoinSet};
use tokio::time::{Duration, timeout}; use tokio::time::{Duration, timeout};
use xkbcommon_rs::{Context, Keymap, KeymapFormat, xkb_keymap::CompileFlags}; use xkbcommon_rs::{Context, Keymap, KeymapFormat, xkb_keymap::CompileFlags};
use zbus::{Connection, names::OwnedInterfaceName}; 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; pub struct FlatscreenInputPlugin;
impl Plugin for FlatscreenInputPlugin { impl Plugin for FlatscreenInputPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
@@ -70,9 +48,9 @@ impl Plugin for FlatscreenInputPlugin {
#[require(Camera3d)] #[require(Camera3d)]
pub struct FlatscreenCam; pub struct FlatscreenCam;
fn setup(mut cmds: Commands, object_registry: Res<ObjectRegistryRes>) { fn setup(mut cmds: Commands) {
let Ok(pointer) = MousePointer::new(object_registry.0.clone()) let Ok(pointer) =
.inspect_err(|err| error!("unable to create mouse pointer: {err}")) MousePointer::new().inspect_err(|err| error!("unable to create mouse pointer: {err}"))
else { else {
return; return;
}; };
@@ -181,14 +159,6 @@ trait KeyboardHandler {
async fn reset(&self) -> zbus::Result<()>; 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)] #[derive(Resource)]
pub struct MousePointer { pub struct MousePointer {
node: OwnedNode, node: OwnedNode,
@@ -197,16 +167,9 @@ pub struct MousePointer {
pointer: Arc<InputMethod>, pointer: Arc<InputMethod>,
capture_manager: CaptureManager, capture_manager: CaptureManager,
mouse_datamap: MouseEvent, 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 { 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 node = Node::generate(&INTERNAL_CLIENT, false).add_to_scenegraph_owned()?;
let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false); let spatial = Spatial::add_to(&node.0, None, Mat4::IDENTITY, false);
let pointer = InputMethod::add_to( let pointer = InputMethod::add_to(
@@ -223,39 +186,6 @@ impl MousePointer {
.unwrap(), .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 { Ok(MousePointer {
node, node,
spatial, spatial,
@@ -263,10 +193,6 @@ impl MousePointer {
capture_manager: CaptureManager::default(), capture_manager: CaptureManager::default(),
mouse_datamap: Default::default(), mouse_datamap: Default::default(),
keymap, keymap,
focus_task_abort_handle,
input_delivery_task_abort_handle,
input_event_tx,
focus_notify,
}) })
} }
pub fn update( pub fn update(
@@ -321,28 +247,7 @@ impl MousePointer {
*self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap(); *self.pointer.datamap.lock() = Datamap::from_typed(&self.mouse_datamap).unwrap();
} }
self.target_pointer_input(); self.target_pointer_input();
self.send_keyboard_input(dbus_connection, object_registry, keyboard_input_events);
// 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();
} }
fn target_pointer_input(&mut self) { fn target_pointer_input(&mut self) {
let distance_calculator: DistanceCalculator = |space, data, field| { let distance_calculator: DistanceCalculator = |space, data, field| {
@@ -379,145 +284,96 @@ impl MousePointer {
); );
} }
async fn focus_tracking_task( pub fn send_keyboard_input(
object_registry: Arc<ObjectRegistry>, &mut self,
focus_notify: Arc<Notify>, dbus_connection: &Connection,
spatial: Arc<Spatial>, object_registry: &ObjectRegistry,
pointer: Arc<InputMethod>, mut keyboard_input_events: EventReader<KeyboardInput>,
focused_handler_tx: watch::Sender<Option<HandlerInfo>>,
) { ) {
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 async move {
let mut keyboard_query = ObjectQuery::< let mut closest_handler = None;
(FieldRefProxy<'static>, KeyboardHandlerProxy<'static>), let mut closest_distance = f32::MAX;
_,
>::new(object_registry.clone(), ()); let mut join_set = JoinSet::new();
let (keyboard_handlers, mapper) = keyboard_query.to_list_query(); for handler in &keyboard_handlers {
task::new( let handler = handler.clone();
|| "Focus tracking mapper", let dbus_connection = dbus_connection.clone();
mapper.init(async |ev| match ev { join_set.spawn(async move {
ListEvent::NewMatch((field_ref, keyboard_proxy)) => { // TODO: refactor the whole thing so picking the keyboardhandler to send input to is separate from sending
info!("New keyboard handler found"); timeout(Duration::from_millis(10), async {
let uid = timeout(Duration::from_millis(100), field_ref.uid()) let field_ref = handler
.to_typed_proxy::<FieldRefProxy>(&dbus_connection)
.await
.ok()?;
let uid = field_ref.uid().await.ok()?;
Some((handler, uid))
})
.await .await
.ok()? .ok()
.ok()?; .flatten()
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(),
}); });
} }
} 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 let result = field_ref.ray_march(Ray {
if let Some(ref handler_info) = closest_handler { origin: vec3(0.0, 0.0, 0.0),
info!( direction: vec3(0.0, 0.0, -1.0),
"Focus tracking task: Focused on handler at distance {}", space: spatial.clone(),
closest_distance });
);
} else {
debug!("Focus tracking task: No handler in focus");
}
let _ = focused_handler_tx.send(closest_handler);
// Wait for next frame signal if result.deepest_point_distance > 0.0
focus_notify.notified().await; && 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( let Some(handler) = closest_handler else {
dbus_connection: Connection, return;
mut focused_handler_rx: watch::Receiver<Option<HandlerInfo>>, };
mut input_event_rx: mpsc::UnboundedReceiver<InputEvent>, let Ok(keyboard_handler) = handler
keymap_id: u64, .to_typed_proxy::<KeyboardHandlerProxy>(&dbus_connection)
) { .await
info!("Input delivery task started"); else {
loop { return;
// 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;
}; };
// Send input to handler using cached proxy
info!("Input delivery task: Sending to handler");
let keyboard_handler = &handler_info.keyboard_proxy;
// Register keymap first // Register keymap first
if let Err(e) = keyboard_handler.keymap(keymap_id).await { let _ = keyboard_handler.keymap(keymap_id).await;
warn!("Input delivery task: Failed to register keymap: {}", e);
}
// Send key state // Send key states
if let Err(e) = keyboard_handler for (key, state) in events.iter() {
.key_state(input_event.key + 8, input_event.pressed) let pressed = matches!(state, ButtonState::Pressed);
.await let _ = keyboard_handler.key_state(key + 8, 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
);
} }
} }
} });
}
}
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::Tab => Some(input_event_codes::KEY_TAB!()),
Key::Enter => Some(input_event_codes::KEY_ENTER!()), Key::Enter => Some(input_event_codes::KEY_ENTER!()),
Key::ShiftLeft => Some(input_event_codes::KEY_LEFTSHIFT!()), 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::ControlLeft => Some(input_event_codes::KEY_LEFTCTRL!()),
Key::ControlRight => Some(input_event_codes::KEY_RIGHTCTRL!()),
Key::AltLeft => Some(input_event_codes::KEY_LEFTALT!()), 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::CapsLock => Some(input_event_codes::KEY_CAPSLOCK!()),
Key::Escape => Some(input_event_codes::KEY_ESC!()), Key::Escape => Some(input_event_codes::KEY_ESC!()),
Key::Space => Some(input_event_codes::KEY_SPACE!()), 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::F3 => Some(input_event_codes::KEY_F3!()),
Key::F4 => Some(input_event_codes::KEY_F4!()), Key::F4 => Some(input_event_codes::KEY_F4!()),
Key::F5 => Some(input_event_codes::KEY_F5!()), Key::F5 => Some(input_event_codes::KEY_F5!()),
Key::F6 => Some(input_event_codes::KEY_F6!()), // Key::F6 => Some(input_event_codes::KEY_F6!()),
Key::F7 => Some(input_event_codes::KEY_F7!()), // Key::F7 => Some(input_event_codes::KEY_F7!()),
Key::F8 => Some(input_event_codes::KEY_F8!()), // Key::F8 => Some(input_event_codes::KEY_F8!()),
Key::F9 => Some(input_event_codes::KEY_F9!()), Key::F9 => Some(input_event_codes::KEY_F9!()),
Key::F10 => Some(input_event_codes::KEY_F10!()), Key::F10 => Some(input_event_codes::KEY_F10!()),
Key::F11 => Some(input_event_codes::KEY_F11!()), Key::F11 => Some(input_event_codes::KEY_F11!()),
Key::F12 => Some(input_event_codes::KEY_F12!()), 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::Comma => Some(input_event_codes::KEY_COMMA!()),
Key::Period => Some(input_event_codes::KEY_DOT!()), Key::Period => Some(input_event_codes::KEY_DOT!()),
Key::Slash => Some(input_event_codes::KEY_SLASH!()), 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::NumpadSubtract => Some(input_event_codes::KEY_MINUS!()),
Key::NumpadDecimal => Some(input_event_codes::KEY_DOT!()), Key::NumpadDecimal => Some(input_event_codes::KEY_DOT!()),
Key::NumpadDivide => Some(input_event_codes::KEY_SLASH!()), 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, _ => None,
} }
} }

View File

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

View File

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

View File

@@ -20,7 +20,6 @@ use std::{
marker::PhantomData, marker::PhantomData,
sync::{Arc, atomic::Ordering}, sync::{Arc, atomic::Ordering},
}; };
use tokio::{sync::mpsc, task::AbortHandle};
use zbus::{Connection, interface, object_server::Interface, zvariant::OwnedObjectPath}; use zbus::{Connection, interface, object_server::Interface, zvariant::OwnedObjectPath};
pub mod hmd; 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); pub struct SpatialRef(u64, OwnedNode);
impl SpatialRef { impl SpatialRef {
pub fn create(connection: &Connection, path: &str) -> (Arc<Spatial>, ObjectHandle<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::prelude::*;
use bevy_mod_openxr::{ use bevy_mod_openxr::{
helper_traits::{ToQuat, ToVec3}, helper_traits::{ToQuat, ToVec3},
resources::{OxrFrameState, Pipelined}, resources::OxrFrameState,
session::OxrSession, session::OxrSession,
}; };
use bevy_mod_xr::{ use bevy_mod_xr::{
@@ -14,9 +14,9 @@ use openxr::SpaceLocationFlags;
use parking_lot::RwLock; use parking_lot::RwLock;
use zbus::{Connection, ObjectServer, interface}; 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; pub struct PlaySpacePlugin;
impl Plugin for PlaySpacePlugin { impl Plugin for PlaySpacePlugin {
@@ -31,7 +31,7 @@ impl Plugin for PlaySpacePlugin {
fn setup(connection: Res<DbusConnection>, mut cmds: Commands) { fn setup(connection: Res<DbusConnection>, mut cmds: Commands) {
let (spatial, spatial_handle) = SpatialRef::create(&connection, "/org/stardustxr/PlaySpace"); let (spatial, spatial_handle) = SpatialRef::create(&connection, "/org/stardustxr/PlaySpace");
// the OpenXR session might not exist quite yet // 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 dbus_connection = connection.clone();
let play_space_data = Arc::new(RwLock::default()); let play_space_data = Arc::new(RwLock::default());
tokio::task::spawn({ tokio::task::spawn({
@@ -75,22 +75,26 @@ fn update(
ref_space: Option<Res<XrPrimaryReferenceSpace>>, ref_space: Option<Res<XrPrimaryReferenceSpace>>,
play_space: Res<PlaySpace>, play_space: Res<PlaySpace>,
state: Option<Res<OxrFrameState>>, state: Option<Res<OxrFrameState>>,
pipelined: Option<Res<Pipelined>>,
) { ) {
let (Some(session), Some(stage), Some(ref_space), Some(state)) = let (Some(session), Some(stage), Some(ref_space), Some(state)) =
(session, stage, ref_space, state) (session, stage, ref_space, state)
else { else {
play_space.bounds.write().drain(..); 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 play_space
.spatial .spatial
.set_local_transform(Mat4::from_translation(vec3(0.0, -1.65, 0.0))); .set_local_transform(Mat4::from_translation(vec3(0.0, -1.65, 0.0)));
return; return;
}; };
let time = get_time(pipelined.is_some(), &state); // this won't be correct with pipelined rendering
let location = session 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}")); .inspect_err(|err| error!("Error while Locating OpenXR Stage Space {err}"));
if let Ok(location) = location { if let Ok(location) = location {
let is_tracked = location.location_flags.contains( let is_tracked = location.location_flags.contains(
@@ -99,7 +103,12 @@ fn update(
| SpaceLocationFlags::ORIENTATION_VALID | SpaceLocationFlags::ORIENTATION_VALID
| SpaceLocationFlags::ORIENTATION_TRACKED, | 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 { if is_tracked {
play_space play_space
.spatial .spatial
@@ -131,7 +140,7 @@ fn update(
pub struct PlaySpace { pub struct PlaySpace {
spatial: Arc<Spatial>, spatial: Arc<Spatial>,
_spatial_handle: ObjectHandle<SpatialRef>, _spatial_handle: ObjectHandle<SpatialRef>,
tracked_handle: AsyncTracked, tracked_handle: ObjectHandle<Tracked>,
bounds: Arc<RwLock<Vec<(f64, f64)>>>, bounds: Arc<RwLock<Vec<(f64, f64)>>>,
} }
pub struct PlaySpaceBounds(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 crate::{CliArgs, STARDUST_INSTANCE};
use directories::ProjectDirs; use directories::ProjectDirs;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::ffi::OsStr;
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
use std::path::Path; use std::path::Path;
use std::process::{Child, Command, Stdio}; use std::process::{Child, Command, Stdio};
@@ -59,7 +58,6 @@ pub fn restore_session(session_dir: &Path, debug_launched_clients: bool) -> Vec<
}; };
clients clients
.filter_map(Result::ok) .filter_map(Result::ok)
.filter(|c| c.path().extension() == Some(OsStr::new("toml")))
.filter_map(|c| ClientStateParsed::from_file(&c.path())) .filter_map(|c| ClientStateParsed::from_file(&c.path()))
.filter_map(ClientStateParsed::launch_command) .filter_map(ClientStateParsed::launch_command)
.filter_map(|c| run_client(c, debug_launched_clients)) .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::dmabuf::buffer_backing::DmabufBacking;
use crate::wayland::{Client, Message, WaylandResult};
use crate::wayland::{MessageSink, core::shm_buffer_backing::ShmBufferBacking, util::ClientExt}; use crate::wayland::{MessageSink, core::shm_buffer_backing::ShmBufferBacking, util::ClientExt};
use bevy::{ use bevy::{
asset::{Assets, Handle}, asset::{Assets, Handle},
@@ -8,9 +8,11 @@ use bevy::{
use bevy_dmabuf::import::ImportedDmatexs; use bevy_dmabuf::import::ImportedDmatexs;
use mint::Vector2; use mint::Vector2;
use std::sync::Arc; use std::sync::Arc;
use waynest::ObjectId; pub use waynest::server::protocol::core::wayland::wl_buffer::*;
pub use waynest_protocols::server::core::wayland::wl_buffer::*; use waynest::{
use waynest_server::{Client as _, RequestDispatcher}; server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug)] #[derive(Debug)]
pub struct BufferUsage { pub struct BufferUsage {
@@ -39,8 +41,7 @@ pub enum BufferBacking {
Dmabuf(DmabufBacking), Dmabuf(DmabufBacking),
} }
#[derive(Debug, RequestDispatcher)] #[derive(Debug, Dispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Buffer { pub struct Buffer {
pub id: ObjectId, pub id: ObjectId,
backing: BufferBacking, backing: BufferBacking,
@@ -48,12 +49,8 @@ pub struct Buffer {
impl Buffer { impl Buffer {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub fn new( pub fn new(client: &mut Client, id: ObjectId, backing: BufferBacking) -> Arc<Self> {
client: &mut Client, client.insert(id, Self { id, backing })
id: ObjectId,
backing: BufferBacking,
) -> WaylandResult<Arc<Self>> {
Ok(client.insert(id, Self { id, backing })?)
} }
/// Returns the tex if it was updated /// Returns the tex if it was updated
@@ -65,20 +62,11 @@ impl Buffer {
) -> Option<Handle<Image>> { ) -> Option<Handle<Image>> {
tracing::debug!("Updating texture for buffer {:?}", self.id); tracing::debug!("Updating texture for buffer {:?}", self.id);
match &self.backing { 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), 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 { pub fn is_transparent(&self) -> bool {
match &self.backing { match &self.backing {
BufferBacking::Shm(backing) => backing.is_transparent(), BufferBacking::Shm(backing) => backing.is_transparent(),
@@ -93,19 +81,13 @@ impl Buffer {
} }
} }
pub fn uses_buffer_usage(&self) -> bool { pub fn uses_buffer_usage(&self) -> bool {
matches!( matches!(self.backing, BufferBacking::Dmabuf(_))
self.backing,
BufferBacking::Dmabuf(_) | BufferBacking::Shm(_)
)
} }
} }
impl WlBuffer for Buffer { impl WlBuffer for Buffer {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_buffer:request:destroy /// https://wayland.app/protocols/wayland#wl_buffer:request:destroy
async fn destroy(&self, client: &mut Client, _sender_id: ObjectId) -> WaylandResult<()> { async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
client.remove(self.id);
tracing::info!("Destroying buffer {:?}", self.id); tracing::info!("Destroying buffer {:?}", self.id);
Ok(()) Ok(())
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,24 +1,20 @@
use super::surface::SurfaceRole; use crate::wayland::core::{seat::fixed_from_f32, surface::Surface};
use crate::nodes::items::panel::Geometry;
use crate::wayland::core::surface::Surface;
use crate::wayland::{Client, WaylandResult};
use mint::Vector2; use mint::Vector2;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Weak; use std::sync::Weak;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tracing; use tracing;
use waynest::ObjectId; pub use waynest::server::protocol::core::wayland::wl_pointer::*;
use waynest_server::Client as _; use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
pub use waynest_protocols::server::core::wayland::wl_pointer::*; #[derive(Dispatcher)]
#[derive(waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Pointer { pub struct Pointer {
pub id: ObjectId, pub id: ObjectId,
version: u32, version: u32,
focused_surface: Mutex<Weak<Surface>>, focused_surface: Mutex<Weak<Surface>>,
cursor_surface: Mutex<Option<Arc<Surface>>>,
} }
impl Pointer { impl Pointer {
pub fn new(id: ObjectId, version: u32) -> Self { pub fn new(id: ObjectId, version: u32) -> Self {
@@ -26,7 +22,6 @@ impl Pointer {
id, id,
version, version,
focused_surface: Mutex::new(Weak::new()), focused_surface: Mutex::new(Weak::new()),
cursor_surface: Mutex::new(None),
} }
} }
@@ -35,7 +30,7 @@ impl Pointer {
client: &mut Client, client: &mut Client,
surface: Arc<Surface>, surface: Arc<Surface>,
position: Vector2<f32>, position: Vector2<f32>,
) -> WaylandResult<()> { ) -> Result<()> {
tracing::debug!( tracing::debug!(
"Handling pointer motion at ({}, {})", "Handling pointer motion at ({}, {})",
position.x, position.x,
@@ -65,8 +60,8 @@ impl Pointer {
self.id, self.id,
serial, serial,
surface.id, surface.id,
(position.x as f64).into(), fixed_from_f32(position.x),
(position.y as f64).into(), fixed_from_f32(position.y),
) )
.await?; .await?;
@@ -80,8 +75,8 @@ impl Pointer {
client, client,
self.id, self.id,
0, // time 0, // time
(position.x as f64).into(), fixed_from_f32(position.x),
(position.y as f64).into(), fixed_from_f32(position.y),
) )
.await?; .await?;
if self.version >= 5 { if self.version >= 5 {
@@ -97,7 +92,7 @@ impl Pointer {
surface: Arc<Surface>, surface: Arc<Surface>,
button: u32, button: u32,
pressed: bool, pressed: bool,
) -> WaylandResult<()> { ) -> Result<()> {
tracing::debug!( tracing::debug!(
"Handling pointer button {} {} on surface {:?}", "Handling pointer button {} {} on surface {:?}",
button, button,
@@ -126,7 +121,7 @@ impl Pointer {
_surface: Arc<Surface>, _surface: Arc<Surface>,
scroll_distance: Option<Vector2<f32>>, scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>, scroll_steps: Option<Vector2<f32>>,
) -> WaylandResult<()> { ) -> Result<()> {
tracing::debug!( tracing::debug!(
"Handling pointer scroll: distance={:?}, steps={:?}", "Handling pointer scroll: distance={:?}, steps={:?}",
scroll_distance, scroll_distance,
@@ -138,7 +133,7 @@ impl Pointer {
self.id, self.id,
0, // time 0, // time
Axis::HorizontalScroll, Axis::HorizontalScroll,
(distance.x as f64).into(), fixed_from_f32(distance.x),
) )
.await?; .await?;
self.axis( self.axis(
@@ -146,44 +141,23 @@ impl Pointer {
self.id, self.id,
0, // time 0, // time
Axis::VerticalScroll, Axis::VerticalScroll,
(distance.y as f64).into(), fixed_from_f32(distance.y),
)
.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,
) )
.await?; .await?;
} }
if self.version >= 5 { 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?; self.frame(client, self.id).await?;
} }
Ok(()) 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; let mut focused = self.focused_surface.lock().await;
if let Some(old_surface) = focused.upgrade() { if let Some(old_surface) = focused.upgrade() {
let serial = client.next_event_serial(); let serial = client.next_event_serial();
@@ -193,60 +167,24 @@ impl Pointer {
*focused = Weak::new(); *focused = Weak::new();
Ok(()) Ok(())
} }
pub async fn cursor_surface(&self) -> Option<Arc<Surface>> {
self.cursor_surface.lock().await.clone()
}
} }
impl WlPointer for Pointer { impl WlPointer for Pointer {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_pointer:request:set_cursor /// https://wayland.app/protocols/wayland#wl_pointer:request:set_cursor
async fn set_cursor( async fn set_cursor(
&self, &self,
client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_serial: u32, _serial: u32,
surface: Option<ObjectId>, _surface: Option<ObjectId>,
hotspot_x: i32, _hotspot_x: i32,
hotspot_y: i32, _hotspot_y: i32,
) -> WaylandResult<()> { ) -> Result<()> {
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);
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/wayland#wl_pointer:request:release /// https://wayland.app/protocols/wayland#wl_pointer:request:release
async fn release( async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) 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 crate::wayland::core::{keyboard::Keyboard, pointer::Pointer, surface::Surface, touch::Touch};
use mint::Vector2; use mint::Vector2;
use std::sync::Arc; use std::sync::Arc;
use std::sync::OnceLock; use std::sync::OnceLock;
use waynest::ObjectId; pub use waynest::server::protocol::core::wayland::wl_seat::*;
pub use waynest_protocols::server::core::wayland::wl_seat::*; use waynest::server::{Client, Dispatcher, Result};
use waynest_server::Client as _; use waynest::wire::{Fixed, ObjectId};
#[derive(Debug)] #[derive(Debug)]
pub enum SeatMessage { pub enum SeatMessage {
@@ -45,8 +43,11 @@ pub enum SeatMessage {
Reset, Reset,
} }
#[derive(Default, waynest_server::RequestDispatcher)] pub fn fixed_from_f32(f: f32) -> Fixed {
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)] unsafe { Fixed::from_raw((f * 256.0).round() as u32) }
}
#[derive(Default, Dispatcher)]
pub struct Seat { pub struct Seat {
version: u32, version: u32,
pointer: OnceLock<Arc<Pointer>>, pointer: OnceLock<Arc<Pointer>>,
@@ -55,7 +56,7 @@ pub struct Seat {
} }
impl 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 { let seat = Self {
version, version,
pointer: OnceLock::new(), pointer: OnceLock::new(),
@@ -75,11 +76,7 @@ impl Seat {
Ok(seat) Ok(seat)
} }
pub async fn handle_message( pub async fn handle_message(&self, client: &mut Client, message: SeatMessage) -> Result<()> {
&self,
client: &mut Client,
message: SeatMessage,
) -> WaylandResult<()> {
match message { match message {
SeatMessage::PointerMotion { surface, position } => { SeatMessage::PointerMotion { surface, position } => {
if let Some(pointer) = self.pointer.get() { if let Some(pointer) = self.pointer.get() {
@@ -157,22 +154,16 @@ impl Seat {
} }
Ok(()) Ok(())
} }
pub async fn cursor_surface(&self) -> Option<Arc<Surface>> {
self.pointer.get()?.cursor_surface().await
}
} }
impl WlSeat for Seat { impl WlSeat for Seat {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_seat:request:get_pointer /// https://wayland.app/protocols/wayland#wl_seat:request:get_pointer
async fn get_pointer( async fn get_pointer(
&self, &self,
client: &mut Self::Connection, client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
id: ObjectId, id: ObjectId,
) -> WaylandResult<()> { ) -> Result<()> {
let pointer = client.insert(id, Pointer::new(id, self.version))?; let pointer = client.insert(id, Pointer::new(id, self.version));
let _ = self.pointer.set(pointer); let _ = self.pointer.set(pointer);
Ok(()) Ok(())
} }
@@ -180,12 +171,12 @@ impl WlSeat for Seat {
/// https://wayland.app/protocols/wayland#wl_seat:request:get_keyboard /// https://wayland.app/protocols/wayland#wl_seat:request:get_keyboard
async fn get_keyboard( async fn get_keyboard(
&self, &self,
client: &mut Self::Connection, client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
id: ObjectId, id: ObjectId,
) -> WaylandResult<()> { ) -> Result<()> {
tracing::info!("Getting keyboard"); 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); let _ = self.keyboard.set(keyboard);
Ok(()) Ok(())
} }
@@ -193,21 +184,17 @@ impl WlSeat for Seat {
/// https://wayland.app/protocols/wayland#wl_seat:request:get_touch /// https://wayland.app/protocols/wayland#wl_seat:request:get_touch
async fn get_touch( async fn get_touch(
&self, &self,
client: &mut Self::Connection, client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
id: ObjectId, id: ObjectId,
) -> WaylandResult<()> { ) -> Result<()> {
let touch = client.insert(id, Touch(id))?; let touch = client.insert(id, Touch(id));
let _ = self.touch.set(touch); let _ = self.touch.set(touch);
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/wayland#wl_seat:request:release /// https://wayland.app/protocols/wayland#wl_seat:request:release
async fn release( async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
} }

View File

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

View File

@@ -1,64 +1,20 @@
use super::shm_pool::ShmPool; use super::shm_pool::ShmPool;
use crate::wayland::{RENDER_DEVICE, vulkano_data::VULKANO_CONTEXT};
use bevy::{ use bevy::{
asset::{Assets, Handle}, asset::{Assets, Handle, RenderAssetUsages},
image::Image, 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 mint::Vector2;
use parking_lot::Mutex; use std::sync::Arc;
use std::{ use waynest::server::protocol::core::wayland::wl_shm::Format;
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;
/// Parameters for a shared memory buffer /// Parameters for a shared memory buffer
#[derive(Debug)]
pub struct ShmBufferBacking { pub struct ShmBufferBacking {
pool: Arc<ShmPool>, pool: Arc<ShmPool>,
offset: usize, offset: usize,
stride: usize, stride: usize,
size: Vector2<usize>, size: Vector2<usize>,
wl_format: Format, 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()
}
} }
impl ShmBufferBacking { impl ShmBufferBacking {
@@ -67,203 +23,70 @@ impl ShmBufferBacking {
offset: usize, offset: usize,
stride: usize, stride: usize,
size: Vector2<usize>, size: Vector2<usize>,
wl_format: Format, format: Format,
) -> Self { ) -> 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 { Self {
pool, pool,
offset, offset,
stride, stride,
size, size,
wl_format, format,
image,
pending_imported_dmatex: Mutex::new(Some(imported_dmatex)),
tex: OnceLock::new(),
} }
} }
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)] #[tracing::instrument("debug", skip_all)]
pub fn update_tex( pub fn update_tex(&self, images: &mut Assets<Image>) -> Option<Handle<Image>> {
&self, let src_data_lock = self.pool.data_lock();
dmatexes: &ImportedDmatexs, let mut src_cursor = self.offset;
images: &mut Assets<Image>,
) -> Option<Handle<Image>> { // Calculate maximum cursor position needed - stride is already in bytes
self.pending_imported_dmatex let max_cursor = self.offset + (self.size.y * self.stride);
.lock()
.take() // Check if we have enough data
.map(|tex| dmatexes.insert_imported_dmatex(images, tex)) if max_cursor > src_data_lock.len() {
.inspect(|handle| { return None;
_ = self.tex.set(handle.clone()); }
}); let data_len = self.size.x * self.size.y * 4;
self.tex.get().cloned() 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 { pub fn is_transparent(&self) -> bool {
match self.wl_format { match self.format {
Format::Xrgb8888 => false, Format::Xrgb8888 => false,
Format::Argb8888 => true, Format::Argb8888 => true,
_ => 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 memmap2::{MmapOptions, RemapOptions};
use parking_lot::{Mutex, MutexGuard, RawMutex, lock_api::MappedMutexGuard}; use parking_lot::{Mutex, MutexGuard, RawMutex, lock_api::MappedMutexGuard};
use std::os::fd::{AsRawFd, OwnedFd}; use std::os::fd::{IntoRawFd, OwnedFd};
use waynest::ObjectId; use waynest::{
use waynest_protocols::server::core::wayland::wl_shm::Format; server::{Client, Dispatcher, Result, protocol::core::wayland::wl_shm::Format},
pub use waynest_protocols::server::core::wayland::wl_shm_pool::*; wire::ObjectId,
use waynest_server::Client as _; };
#[derive(Debug, waynest_server::RequestDispatcher)] use crate::wayland::core::buffer::{Buffer, BufferBacking};
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub use waynest::server::protocol::core::wayland::wl_shm_pool::*;
use super::shm_buffer_backing::ShmBufferBacking;
#[derive(Debug, Dispatcher)]
pub struct ShmPool { pub struct ShmPool {
inner: Mutex<memmap2::MmapMut>, inner: Mutex<memmap2::MmapMut>,
id: ObjectId,
} }
impl ShmPool { impl ShmPool {
#[tracing::instrument(level = "debug", skip_all)] #[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 { let map = unsafe {
MmapOptions::new() MmapOptions::new()
.len(size as usize) .len(size as usize)
.map_mut(fd.as_raw_fd())? .map_mut(fd.into_raw_fd())?
}; };
Ok(Self { Ok(Self {
inner: Mutex::new(map), inner: Mutex::new(map),
id,
}) })
} }
#[tracing::instrument(level = "debug", skip_all)] #[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()) MutexGuard::map(self.inner.lock(), |i| i.as_mut())
} }
} }
impl WlShmPool for ShmPool { impl WlShmPool for ShmPool {
type Connection = Client;
/// https://wayland.app/protocols/wayland#wl_shm_pool:request:create_buffer /// https://wayland.app/protocols/wayland#wl_shm_pool:request:create_buffer
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn create_buffer( async fn create_buffer(
&self, &self,
client: &mut Self::Connection, client: &mut Client,
sender_id: ObjectId, sender_id: ObjectId,
id: ObjectId, id: ObjectId,
offset: i32, offset: i32,
@@ -54,7 +50,7 @@ impl WlShmPool for ShmPool {
height: i32, height: i32,
stride: i32, stride: i32,
format: Format, format: Format,
) -> WaylandResult<()> { ) -> Result<()> {
let params = ShmBufferBacking::new( let params = ShmBufferBacking::new(
client.get::<ShmPool>(sender_id).unwrap(), client.get::<ShmPool>(sender_id).unwrap(),
offset as usize, offset as usize,
@@ -63,18 +59,13 @@ impl WlShmPool for ShmPool {
format, format,
); );
Buffer::new(client, id, BufferBacking::Shm(params))?; Buffer::new(client, id, BufferBacking::Shm(params));
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/wayland#wl_shm_pool:request:resize /// https://wayland.app/protocols/wayland#wl_shm_pool:request:resize
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn resize( async fn resize(&self, _client: &mut Client, _sender_id: ObjectId, size: i32) -> Result<()> {
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
size: i32,
) -> WaylandResult<()> {
let mut inner = self.inner.lock(); let mut inner = self.inner.lock();
unsafe { inner.remap(size as usize, RemapOptions::new().may_move(true))? }; unsafe { inner.remap(size as usize, RemapOptions::new().may_move(true))? };
Ok(()) Ok(())
@@ -82,12 +73,7 @@ impl WlShmPool for ShmPool {
/// https://wayland.app/protocols/wayland#wl_shm_pool:request:destroy /// https://wayland.app/protocols/wayland#wl_shm_pool:request:destroy
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn destroy( async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
} }

View File

@@ -2,16 +2,13 @@ use super::{buffer::Buffer, callback::Callback};
use crate::{ use crate::{
BevyMaterial, BevyMaterial,
core::registry::Registry, core::registry::Registry,
nodes::{ nodes::{drawable::model::ModelPart, items::panel::Geometry},
drawable::model::ModelPart,
items::panel::{Geometry, PanelItem, SurfaceId},
},
wayland::{ wayland::{
Client, Message, MessageSink, WaylandError, WaylandResult, Message, MessageSink,
core::buffer::BufferUsage, core::buffer::BufferUsage,
presentation::{MonotonicTimestamp, PresentationFeedback}, presentation::{MonotonicTimestamp, PresentationFeedback},
util::{ClientExt, DoubleBuffer}, util::{ClientExt, DoubleBuffer},
xdg::backend::XdgBackend, xdg::{popup::Popup, toplevel::Toplevel},
}, },
}; };
use bevy::{ use bevy::{
@@ -22,35 +19,24 @@ use bevy::{
use bevy_dmabuf::import::ImportedDmatexs; use bevy_dmabuf::import::ImportedDmatexs;
use mint::Vector2; use mint::Vector2;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::sync::{Arc, OnceLock};
fmt::Display, use waynest::{
sync::{Arc, OnceLock, Weak}, 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(); pub static WL_SURFACE_REGISTRY: Registry<Surface> = Registry::new();
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone)]
pub enum SurfaceRole { pub enum SurfaceRole {
Cursor, XdgToplevel(Arc<Toplevel>),
Subsurface, XDGPopup(Arc<Popup>),
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"),
}
}
} }
#[derive(Debug, Clone)] #[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 // if returning false, don't run this callback again... just remove it
pub type OnCommitCallback = Box<dyn FnMut(&Surface, &SurfaceState) -> bool + Send + Sync>; pub type OnCommitCallback = Box<dyn Fn(&Surface, &SurfaceState) -> bool + Send + Sync>;
#[derive(waynest_server::RequestDispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)] #[derive(Dispatcher)]
pub struct Surface { pub struct Surface {
pub id: ObjectId, pub id: ObjectId,
pub surface_id: OnceLock<SurfaceId>,
state: Mutex<DoubleBuffer<SurfaceState>>, state: Mutex<DoubleBuffer<SurfaceState>>,
pub message_sink: MessageSink, pub message_sink: MessageSink,
pub role: OnceLock<SurfaceRole>, // TODO: This should probably be a OnceLock? wayland doesn't support changing the surface role
pub panel_item: Mutex<Weak<PanelItem<XdgBackend>>>, pub role: Mutex<Option<SurfaceRole>>,
on_commit_handlers: Mutex<Vec<OnCommitCallback>>, on_commit_handlers: Mutex<Vec<OnCommitCallback>>,
frame_callbacks: Mutex<Vec<Arc<Callback>>>,
material: OnceLock<Handle<BevyMaterial>>, material: OnceLock<Handle<BevyMaterial>>,
pending_material_applications: Registry<ModelPart>, pending_material_applications: Registry<ModelPart>,
presentation_feedback: Mutex<Vec<Arc<PresentationFeedback>>>, presentation_feedback: Mutex<Vec<Arc<PresentationFeedback>>>,
@@ -108,8 +85,6 @@ pub struct Surface {
impl std::fmt::Debug for Surface { impl std::fmt::Debug for Surface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Surface") f.debug_struct("Surface")
.field("id", &self.id)
.field("surface_id", &self.surface_id)
.field("state", &self.state) .field("state", &self.state)
.field("message_sink", &self.message_sink) .field("message_sink", &self.message_sink)
.field("role", &self.role) .field("role", &self.role)
@@ -117,8 +92,6 @@ impl std::fmt::Debug for Surface {
"on_commit_handlers", "on_commit_handlers",
&format!("<{} handlers>", self.on_commit_handlers.lock().len()), &format!("<{} handlers>", self.on_commit_handlers.lock().len()),
) )
.field("material", &self.material)
.field("presentation_feedback", &self.presentation_feedback)
.finish() .finish()
} }
} }
@@ -127,50 +100,23 @@ impl Surface {
pub fn new(client: &Client, id: ObjectId) -> Self { pub fn new(client: &Client, id: ObjectId) -> Self {
Surface { Surface {
id, id,
surface_id: OnceLock::new(),
state: Default::default(), state: Default::default(),
message_sink: client.message_sink(), message_sink: client.message_sink(),
role: OnceLock::new(), role: Mutex::new(None),
panel_item: Mutex::new(Weak::default()),
on_commit_handlers: Mutex::new(Vec::new()), on_commit_handlers: Mutex::new(Vec::new()),
frame_callbacks: Mutex::new(Vec::new()),
material: OnceLock::new(), material: OnceLock::new(),
pending_material_applications: Registry::new(), pending_material_applications: Registry::new(),
presentation_feedback: Mutex::default(), 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)] #[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() self.state.lock()
} }
#[tracing::instrument(level = "debug", skip_all)] #[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, &self,
handler: F, handler: F,
) { ) {
@@ -241,9 +187,8 @@ impl Surface {
} }
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub fn frame_event(&self) { pub fn frame_event(&self) {
let callbacks = std::mem::take(&mut *self.frame_callbacks.lock()); for callback in self.state.lock().current.frame_callbacks.drain(..) {
if !callbacks.is_empty() { let _ = self.message_sink.send(Message::Frame(callback));
let _ = self.message_sink.send(Message::Frame(callbacks));
} }
} }
// pub fn size(&self) -> Option<Vector2<u32>> { // 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()) // .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 (old_buffer, object) = {
// let lock = self.state.lock(); // let lock = self.state.lock();
@@ -285,11 +230,13 @@ impl Surface {
display_timestamp: MonotonicTimestamp, display_timestamp: MonotonicTimestamp,
refresh_cycle: u64, refresh_cycle: u64,
) { ) {
let _ = self.message_sink.send(Message::SendPresentationFeedback { self.message_sink
surface: self.clone(), .send(Message::SendPresentationFeedback {
display_timestamp, surface: self.clone(),
refresh_cycle, display_timestamp,
}); refresh_cycle,
})
.unwrap();
} }
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
@@ -298,16 +245,20 @@ impl Surface {
client: &mut Client, client: &mut Client,
display_timestamp: MonotonicTimestamp, display_timestamp: MonotonicTimestamp,
refresh_cycle: u64, refresh_cycle: u64,
) -> WaylandResult<()> { ) -> Result<()> {
let feedbacks = self let feedbacks = self
.presentation_feedback .presentation_feedback
.lock() .lock()
.drain(..) .drain(..)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for feedback in feedbacks { for feedback in feedbacks {
if let Some(display_id) = client.display().output.get().map(|display| display.id) { feedback
feedback.sync_output(client, feedback.0, display_id).await?; .sync_output(
} client,
feedback.0,
client.display().output.get().unwrap().id,
)
.await?;
let cycle_lo = refresh_cycle as u32; let cycle_lo = refresh_cycle as u32;
let cycle_hi = (refresh_cycle >> 32) as u32; let cycle_hi = (refresh_cycle >> 32) as u32;
feedback feedback
@@ -328,18 +279,16 @@ impl Surface {
} }
} }
impl WlSurface for Surface { impl WlSurface for Surface {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_surface:request:attach /// https://wayland.app/protocols/wayland#wl_surface:request:attach
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn attach( async fn attach(
&self, &self,
client: &mut Self::Connection, client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
buffer: Option<ObjectId>, buffer: Option<ObjectId>,
_x: i32, _x: i32,
_y: i32, _y: i32,
) -> WaylandResult<()> { ) -> Result<()> {
self.state.lock().pending.buffer = buffer.and_then(|b| { self.state.lock().pending.buffer = buffer.and_then(|b| {
let buffer = client.get::<Buffer>(b)?; let buffer = client.get::<Buffer>(b)?;
let mut usage = Some(BufferUsage::new(client, &buffer)); let mut usage = Some(BufferUsage::new(client, &buffer));
@@ -355,13 +304,13 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn damage( async fn damage(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_x: i32, _x: i32,
_y: i32, _y: i32,
_width: i32, _width: i32,
_height: i32, _height: i32,
) -> WaylandResult<()> { ) -> Result<()> {
Ok(()) Ok(())
} }
@@ -369,11 +318,11 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn frame( async fn frame(
&self, &self,
client: &mut Self::Connection, client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
callback_id: ObjectId, callback_id: ObjectId,
) -> WaylandResult<()> { ) -> Result<()> {
let callback = client.insert(callback_id, Callback(callback_id))?; let callback = client.insert(callback_id, Callback(callback_id));
self.state.lock().pending.frame_callbacks.push(callback); self.state.lock().pending.frame_callbacks.push(callback);
Ok(()) Ok(())
} }
@@ -382,10 +331,10 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn set_opaque_region( async fn set_opaque_region(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_region: Option<ObjectId>, _region: Option<ObjectId>,
) -> WaylandResult<()> { ) -> Result<()> {
// nothing we can really do to repaint behind this so ignore it // nothing we can really do to repaint behind this so ignore it
Ok(()) Ok(())
} }
@@ -394,42 +343,22 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn set_input_region( async fn set_input_region(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_region: Option<ObjectId>, _region: Option<ObjectId>,
) -> WaylandResult<()> { ) -> Result<()> {
// too complicated to implement this for now so who the hell cares // too complicated to implement this for now so who the hell cares
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/wayland#wl_surface:request:commit /// https://wayland.app/protocols/wayland#wl_surface:request:commit
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn commit( async fn commit(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
&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();
}
self.state.lock().apply(); self.state.lock().apply();
self.state.lock().pending.frame_callbacks.clear(); self.state.lock().pending.frame_callbacks.clear();
let current_state = self.current_state(); 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(); let mut handlers = self.on_commit_handlers.lock();
handlers.retain_mut(|f| (f)(self, &current_state)); handlers.retain(|f| (f)(self, &current_state));
Ok(()) Ok(())
} }
@@ -437,10 +366,10 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn set_buffer_transform( async fn set_buffer_transform(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_transform: Transform, _transform: Transform,
) -> WaylandResult<()> { ) -> Result<()> {
// we just don't have the output transform or fullscreen at all so this optimization is never needed // we just don't have the output transform or fullscreen at all so this optimization is never needed
Ok(()) Ok(())
} }
@@ -449,10 +378,10 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn set_buffer_scale( async fn set_buffer_scale(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
scale: i32, scale: i32,
) -> WaylandResult<()> { ) -> Result<()> {
self.state.lock().pending.density = scale as f32; self.state.lock().pending.density = scale as f32;
Ok(()) Ok(())
} }
@@ -461,13 +390,13 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn damage_buffer( async fn damage_buffer(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_x: i32, _x: i32,
_y: i32, _y: i32,
_width: i32, _width: i32,
_height: i32, _height: i32,
) -> WaylandResult<()> { ) -> Result<()> {
Ok(()) Ok(())
} }
@@ -475,27 +404,23 @@ impl WlSurface for Surface {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn offset( async fn offset(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_x: i32, _x: i32,
_y: i32, _y: i32,
) -> WaylandResult<()> { ) -> Result<()> {
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/wayland#wl_surface:request:destroy /// https://wayland.app/protocols/wayland#wl_surface:request:destroy
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn destroy( async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
} }
impl Drop for Surface { impl Drop for Surface {
fn drop(&mut self) { 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 mint::Vector2;
use std::sync::Arc; use std::sync::Arc;
use waynest::ObjectId; pub use waynest::server::protocol::core::wayland::wl_touch::*;
pub use waynest_protocols::server::core::wayland::wl_touch::*; use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug, waynest_server::RequestDispatcher)] use super::seat::fixed_from_f32;
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
#[derive(Debug, Dispatcher)]
pub struct Touch(pub ObjectId); pub struct Touch(pub ObjectId);
impl Touch { impl Touch {
pub async fn handle_touch_down( pub async fn handle_touch_down(
@@ -14,7 +18,7 @@ impl Touch {
surface: Arc<Surface>, surface: Arc<Surface>,
id: u32, id: u32,
position: Vector2<f32>, position: Vector2<f32>,
) -> WaylandResult<()> { ) -> Result<()> {
let serial = client.next_event_serial(); let serial = client.next_event_serial();
self.down( self.down(
client, client,
@@ -23,8 +27,8 @@ impl Touch {
0, 0,
surface.id, surface.id,
id as i32, id as i32,
(position.x as f64).into(), fixed_from_f32(position.x),
(position.y as f64).into(), fixed_from_f32(position.y),
) )
.await?; .await?;
self.frame(client, self.0).await self.frame(client, self.0).await
@@ -35,39 +39,37 @@ impl Touch {
client: &mut Client, client: &mut Client,
id: u32, id: u32,
position: Vector2<f32>, position: Vector2<f32>,
) -> WaylandResult<()> { ) -> Result<()> {
self.motion( self.motion(
client, client,
self.0, self.0,
0, 0,
id as i32, id as i32,
(position.x as f64).into(), fixed_from_f32(position.x),
(position.y as f64).into(), fixed_from_f32(position.y),
) )
.await?; .await?;
self.frame(client, self.0).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(); let serial = client.next_event_serial();
self.up(client, self.0, serial, 0, id as i32).await?; self.up(client, self.0, serial, 0, id as i32).await?;
self.frame(client, self.0).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 self.frame(client, self.0).await
} }
} }
impl WlTouch for Touch { impl WlTouch for Touch {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/wayland#wl_touch:request:release /// https://wayland.app/protocols/wayland#wl_touch:request:release
async fn release( async fn release(
&self, &self,
_client: &mut Self::Connection, _client: &mut waynest::server::Client,
_sender_id: ObjectId, _sender_id: waynest::wire::ObjectId,
) -> WaylandResult<()> { ) -> Result<()> {
Ok(()) Ok(())
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,19 +5,21 @@ mod mesa_drm;
mod presentation; mod presentation;
mod registry; mod registry;
mod util; mod util;
mod viewporter;
mod vulkano_data; mod vulkano_data;
mod xdg; mod xdg;
use crate::core::error::ServerError;
use crate::core::registry::OwnedRegistry; use crate::core::registry::OwnedRegistry;
use crate::get_time;
use crate::nodes::drawable::model::ModelNodeSystemSet; use crate::nodes::drawable::model::ModelNodeSystemSet;
use crate::wayland::core::seat::SeatMessage; use crate::wayland::core::seat::SeatMessage;
use crate::wayland::core::surface::Surface; use crate::wayland::core::surface::Surface;
use crate::wayland::presentation::MonotonicTimestamp; use crate::wayland::presentation::MonotonicTimestamp;
use crate::wayland::util::ClientExt; use crate::{
use crate::{BevyMaterial, core::task}; BevyMaterial,
core::{
error::{Result, ServerError},
task,
},
};
use bevy::app::{App, Plugin, Update}; use bevy::app::{App, Plugin, Update};
use bevy::ecs::schedule::IntoScheduleConfigs; use bevy::ecs::schedule::IntoScheduleConfigs;
use bevy::ecs::system::{Local, Res, ResMut}; 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::{asset::Assets, ecs::resource::Resource, image::Image};
use bevy_dmabuf::import::ImportedDmatexs; use bevy_dmabuf::import::ImportedDmatexs;
use bevy_mod_openxr::render::end_frame; use bevy_mod_openxr::render::end_frame;
use bevy_mod_openxr::resources::{OxrFrameState, OxrInstance, Pipelined};
use bevy_mod_xr::session::XrRenderSet; use bevy_mod_xr::session::XrRenderSet;
use cluFlock::{FlockLock, ToFlock};
use core::buffer::BufferUsage; use core::buffer::BufferUsage;
use core::{buffer::Buffer, callback::Callback, surface::WL_SURFACE_REGISTRY}; use core::{buffer::Buffer, callback::Callback, surface::WL_SURFACE_REGISTRY};
use display::Display; use display::Display;
use mint::Vector2; use mint::Vector2;
use pin_project_lite::pin_project;
use std::fs::File; use std::fs::File;
use std::io::ErrorKind;
use std::mem::MaybeUninit;
use std::time::Duration; use std::time::Duration;
use std::{ use std::{
io, fs::{self, OpenOptions},
io::{self, ErrorKind},
os::unix::fs::OpenOptionsExt,
path::PathBuf, path::PathBuf,
sync::{Arc, OnceLock}, sync::{Arc, OnceLock},
}; };
use tokio::{net::UnixStream, sync::mpsc, task::AbortHandle}; use tokio::{net::UnixStream, sync::mpsc, task::AbortHandle};
use tokio_stream::{Stream, StreamExt}; use tokio_stream::StreamExt;
use tracing::{debug_span, instrument}; use tracing::{debug_span, instrument};
use vulkano_data::setup_vulkano_context; use vulkano_data::setup_vulkano_context;
use waynest::{Connection, Socket}; use waynest::{
use waynest::{ObjectId, ProtocolError}; server::{
use waynest_protocols::server::core::wayland::wl_display::WlDisplay; self,
use waynest_server::{Client as _, Listener, Store, StoreError}; 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; use xdg::toplevel::Toplevel;
pub static WAYLAND_DISPLAY: OnceLock<PathBuf> = OnceLock::new(); pub static WAYLAND_DISPLAY: OnceLock<PathBuf> = OnceLock::new();
#[derive(thiserror::Error, Debug)] impl From<waynest::server::Error> for ServerError {
pub enum WaylandError { fn from(err: waynest::server::Error) -> Self {
// #[error("Listener error: {0}")] ServerError::WaylandError(err)
// 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
} }
} }
pin_project! { pub fn get_free_wayland_socket_path() -> Option<(PathBuf, FlockLock<File>)> {
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)> {
// Use XDG runtime directory for secure, user-specific sockets // Use XDG runtime directory for secure, user-specific sockets
let base_dirs = directories::BaseDirs::new()?; let base_dirs = directories::BaseDirs::new()?;
let runtime_dir = base_dirs.runtime_dir()?; 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_path = runtime_dir.join(format!("wayland-{display}"));
let socket_lock_path = runtime_dir.join(format!("wayland-{display}.lock")); 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; continue;
}; };
if lock.try_lock().is_err() { // Atomic mutual exclusion: fail if another process holds the lock\
continue; let Ok(lock) = lock.try_exclusive_lock() else {
continue; // Lock held by active compositor
}; };
// Check for zombie sockets (file exists but nothing listening) // 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 Ok(_) => continue, // Active compositor found - skip
Err(e) if e.kind() == ErrorKind::ConnectionRefused => { Err(e) if e.kind() == ErrorKind::ConnectionRefused => {
// Stale socket - safe to remove since we hold the lock // 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 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 None // Exhausted all conventional display numbers
} }
pub type WaylandResult<T, E = WaylandError> = std::result::Result<T, E>;
pub enum Message { pub enum Message {
Frame(Vec<Arc<Callback>>), Disconnect,
Frame(Arc<Callback>),
ReleaseBuffer(Arc<Buffer>), ReleaseBuffer(Arc<Buffer>),
CloseToplevel(Arc<Toplevel>), CloseToplevel(Arc<Toplevel>),
ResizeToplevel { ResizeToplevel {
toplevel: Arc<Toplevel>, toplevel: Arc<Toplevel>,
size: Option<Vector2<u32>>, size: Option<Vector2<u32>>,
}, },
ReconfigureToplevel(Arc<Toplevel>),
SetToplevelVisualActive { SetToplevelVisualActive {
toplevel: Arc<Toplevel>, toplevel: Arc<Toplevel>,
active: bool, active: bool,
@@ -239,92 +142,87 @@ struct WaylandClient {
abort_handle: AbortHandle, abort_handle: AbortHandle,
} }
impl WaylandClient { 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 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 = server::Client::new(socket)?;
let mut client = Client::new(socket)?;
let (message_sink, message_source) = mpsc::unbounded_channel(); let (message_sink, message_source) = mpsc::unbounded_channel();
client.insert(ObjectId::DISPLAY, Display::new(message_sink, pid))?; 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());
let abort_handle = task::new( let abort_handle = task::new(
|| format!("Wayland client \"{exe_printable}\" dispatch, pid={pid_printable}"), || "wayland client",
Self::dispatch_loop(client, message_source), Self::handle_client_messages(client, message_source),
)? )?
.abort_handle(); .abort_handle();
Ok(WaylandClient { abort_handle }) Ok(WaylandClient { abort_handle })
} }
async fn handle_client_messages(
async fn dispatch_loop( mut client: server::Client,
mut client: Client,
mut render_message_rx: mpsc::UnboundedReceiver<Message>, mut render_message_rx: mpsc::UnboundedReceiver<Message>,
) -> WaylandResult<()> { ) -> Result<()> {
loop { loop {
tokio::select! { tokio::select! {
biased;
// send all queued up messages // send all queued up messages
msg = render_message_rx.recv() => { msg = render_message_rx.recv() => {
let Some(msg) = msg else { if let Some(msg) = msg {
// Render message channel closed, end the dispatch loop Self::handle_render_message(&mut client, msg).await?;
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);
} }
} }
}; // 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<()> { async fn handle_render_message(
use waynest_protocols::server::core::wayland::wl_buffer::WlBuffer; client: &mut server::Client,
use waynest_protocols::server::core::wayland::wl_callback::WlCallback; message: Message,
use waynest_protocols::server::core::wayland::wl_display::WlDisplay; ) -> Result<bool, waynest::server::Error> {
use waynest_protocols::server::stable::xdg_shell::xdg_toplevel::XdgToplevel;
match message { 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 = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic);
let now = Duration::new(now.tv_sec as u64, now.tv_nsec as u32); 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; let ms = (now.as_millis() % (u32::MAX as u128)) as u32;
for callback in callbacks { callback.done(client, callback.0, ms).await?;
callback.done(client, callback.0, ms).await?; client
client .get::<Display>(ObjectId::DISPLAY)
.get::<Display>(ObjectId::DISPLAY) .unwrap()
.unwrap() .delete_id(client, ObjectId::DISPLAY, callback.0.as_raw())
.delete_id(client, ObjectId::DISPLAY, callback.0.as_raw()) .await?;
.await?; client.remove(callback.0);
client.remove(callback.0);
}
} }
Message::ReleaseBuffer(buffer) => { Message::ReleaseBuffer(buffer) => {
buffer.release(client, buffer.id).await?; buffer.release(client, buffer.id).await?;
@@ -336,9 +234,6 @@ impl WaylandClient {
toplevel.set_size(size); toplevel.set_size(size);
toplevel.reconfigure(client).await?; toplevel.reconfigure(client).await?;
} }
Message::ReconfigureToplevel(toplevel) => {
toplevel.reconfigure(client).await?;
}
Message::SetToplevelVisualActive { toplevel, active } => { Message::SetToplevelVisualActive { toplevel, active } => {
toplevel.set_activated(active); toplevel.set_activated(active);
toplevel.reconfigure(client).await?; toplevel.reconfigure(client).await?;
@@ -358,7 +253,7 @@ impl WaylandClient {
.await?; .await?;
} }
} }
Ok(()) Ok(false)
} }
} }
impl Drop for WaylandClient { impl Drop for WaylandClient {
@@ -369,32 +264,30 @@ impl Drop for WaylandClient {
#[derive(Debug, Resource)] #[derive(Debug, Resource)]
pub struct Wayland { pub struct Wayland {
_lockfile: File, _lockfile: FlockLock<File>,
abort_handle: AbortHandle, abort_handle: AbortHandle,
} }
impl Wayland { impl Wayland {
pub fn new() -> color_eyre::eyre::Result<Self> { pub fn new() -> Result<Self> {
let (socket_path, _lockfile) = get_free_wayland_socket_path().ok_or(WaylandError::Io( let (socket_path, _lockfile) =
std::io::ErrorKind::AddrNotAvailable.into(), 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 _ = WAYLAND_DISPLAY.set(socket_path.clone());
let listener = waynest_server::Listener::new_with_path(&socket_path)?; let listener =
let _ = WAYLAND_DISPLAY.set(listener.socket_path().to_path_buf()); server::Listener::new_with_path(&socket_path).map_err(ServerError::WaylandError)?;
let abort_handle = task::new( let abort_handle =
|| "Wayland socket accept loop", task::new(|| "wayland loop", Self::handle_wayland_loop(listener))?.abort_handle();
Self::handle_wayland_loop(listener),
)?
.abort_handle();
Ok(Self { Ok(Self {
_lockfile, _lockfile,
abort_handle, 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(); let mut clients = Vec::new();
loop { loop {
if let Ok(Some(stream)) = listener.try_next().await { if let Ok(Some(stream)) = listener.try_next().await {
@@ -487,39 +380,11 @@ fn update_graphics(
} }
#[instrument(level = "debug", name = "Wayland frame", skip_all)] #[instrument(level = "debug", name = "Wayland frame", skip_all)]
fn submit_frame_timings( fn submit_frame_timings(mut frame_count: Local<u64>) {
mut frame_count: Local<u64>,
instance: Option<Res<OxrInstance>>,
frame_state: Option<Res<OxrFrameState>>,
pipelined: Option<Res<Pipelined>>,
) {
*frame_count += 1; *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() { 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); 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 rustix::fs::Timespec;
use waynest::ObjectId; use waynest::{
use waynest_protocols::server::stable::presentation_time::{ server::{
wp_presentation::*, wp_presentation_feedback::*, 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 { pub struct MonotonicTimestamp {
secs: u64, secs: u64,
subsec_nanos: u32, subsec_nanos: u32,
@@ -33,49 +36,31 @@ impl From<Timespec> for MonotonicTimestamp {
} }
} }
#[derive(Debug, waynest_server::RequestDispatcher)] #[derive(Debug, Dispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)] pub struct Presentation;
pub struct Presentation {
id: ObjectId,
}
impl Presentation {
pub fn new(id: ObjectId) -> Self {
Self { id }
}
}
impl WpPresentation for Presentation { impl WpPresentation for Presentation {
type Connection = crate::wayland::Client; async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
async fn destroy(
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
async fn feedback( async fn feedback(
&self, &self,
client: &mut Self::Connection, client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
surface: ObjectId, surface: ObjectId,
id: ObjectId, id: ObjectId,
) -> WaylandResult<()> { ) -> Result<()> {
let Some(surface) = client.get::<Surface>(surface) else { let Some(surface) = client.get::<Surface>(surface) else {
tracing::error!("unable to get surface#{surface}"); tracing::error!("unable to get surface#{surface}");
return Ok(()); return Ok(());
}; };
let feedback = client.insert(id, PresentationFeedback(id))?; let feedback = client.insert(id, PresentationFeedback(id));
surface.add_presentation_feedback(feedback); surface.add_presentation_feedback(feedback);
Ok(()) Ok(())
} }
} }
#[derive(Debug, waynest_server::RequestDispatcher)] #[derive(Debug, Dispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct PresentationFeedback(pub ObjectId); pub struct PresentationFeedback(pub ObjectId);
impl WpPresentationFeedback for PresentationFeedback { impl WpPresentationFeedback for PresentationFeedback {}
type Connection = crate::wayland::Client;
}

View File

@@ -1,6 +1,4 @@
use crate::wayland::{Client, WaylandResult};
use crate::wayland::{ use crate::wayland::{
WaylandError,
core::{ core::{
compositor::{Compositor, WlCompositor}, compositor::{Compositor, WlCompositor},
data_device::DataDeviceManager, data_device::DataDeviceManager,
@@ -12,20 +10,22 @@ use crate::wayland::{
mesa_drm::MesaDrm, mesa_drm::MesaDrm,
presentation::Presentation, presentation::Presentation,
util::ClientExt, util::ClientExt,
viewporter::Viewporter,
xdg::wm_base::{WmBase, XdgWmBase}, xdg::wm_base::{WmBase, XdgWmBase},
}; };
use waynest::{NewId, ObjectId}; use waynest::{
use waynest_protocols::server::{ server::{
core::wayland::{wl_data_device_manager::WlDataDeviceManager, wl_registry::*}, Client, Dispatcher, Error, Result,
mesa::drm::wl_drm::WlDrm, protocol::{
stable::{ core::wayland::{wl_data_device_manager::WlDataDeviceManager, wl_registry::*},
linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, external::drm::wl_drm::WlDrm,
presentation_time::wp_presentation::WpPresentation, stable::{
viewporter::wp_viewporter::WpViewporter, linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
presentation_time::wp_presentation::WpPresentation,
},
},
}, },
wire::{NewId, ObjectId},
}; };
use waynest_server::Client as _;
struct RegistryGlobals; struct RegistryGlobals;
impl RegistryGlobals { impl RegistryGlobals {
@@ -38,19 +38,13 @@ impl RegistryGlobals {
pub const DMABUF: u32 = 6; pub const DMABUF: u32 = 6;
pub const WL_DRM: u32 = 7; pub const WL_DRM: u32 = 7;
pub const PRESENTATION: u32 = 8; pub const PRESENTATION: u32 = 8;
pub const VIEWPORTER: u32 = 9;
} }
#[derive(Debug, waynest_server::RequestDispatcher, Default)] #[derive(Debug, Dispatcher, Default)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Registry; pub struct Registry;
impl Registry { impl Registry {
pub async fn advertise_globals( pub async fn advertise_globals(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
&self,
client: &mut Client,
sender_id: ObjectId,
) -> WaylandResult<()> {
self.global( self.global(
client, client,
sender_id, sender_id,
@@ -132,57 +126,48 @@ impl Registry {
) )
.await?; .await?;
self.global(
client,
sender_id,
RegistryGlobals::VIEWPORTER,
Viewporter::INTERFACE.to_string(),
Viewporter::VERSION,
)
.await?;
Ok(()) Ok(())
} }
} }
impl WlRegistry for Registry { impl WlRegistry for Registry {
type Connection = crate::wayland::Client;
async fn bind( async fn bind(
&self, &self,
client: &mut Self::Connection, client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
name: u32, name: u32,
new_id: NewId, new_id: NewId,
) -> WaylandResult<()> { ) -> Result<()> {
match name { match name {
RegistryGlobals::COMPOSITOR => { RegistryGlobals::COMPOSITOR => {
tracing::info!("Binding compositor"); tracing::info!("Binding compositor");
client.insert(new_id.object_id, Compositor)?; client.insert(new_id.object_id, Compositor);
} }
RegistryGlobals::SHM => { RegistryGlobals::SHM => {
tracing::info!("Binding 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?; shm.advertise_formats(client, new_id.object_id).await?;
} }
RegistryGlobals::WM_BASE => { RegistryGlobals::WM_BASE => {
tracing::info!("Binding WM_BASE"); tracing::info!("Binding WM_BASE");
client.insert( client.insert(
new_id.object_id, new_id.object_id,
WmBase::new(new_id.object_id, new_id.version), WmBase {
)?; version: new_id.version,
},
);
} }
RegistryGlobals::SEAT => { RegistryGlobals::SEAT => {
tracing::info!("Binding seat with id {}", new_id.object_id); 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 = 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()); let _ = client.display().seat.set(seat.clone());
tracing::info!("Seat capabilities advertised"); tracing::info!("Seat capabilities advertised");
} }
RegistryGlobals::DATA_DEVICE_MANAGER => { RegistryGlobals::DATA_DEVICE_MANAGER => {
tracing::info!("Binding 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 => { RegistryGlobals::OUTPUT => {
tracing::info!("Binding output"); tracing::info!("Binding output");
@@ -192,7 +177,7 @@ impl WlRegistry for Registry {
id: new_id.object_id, id: new_id.object_id,
version: new_id.version, version: new_id.version,
}, },
)?; );
let _ = client.display().output.set(output.clone()); let _ = client.display().output.set(output.clone());
output.advertise_outputs(client).await?; output.advertise_outputs(client).await?;
} }
@@ -200,27 +185,22 @@ impl WlRegistry for Registry {
tracing::info!("Binding dmabuf"); tracing::info!("Binding dmabuf");
let dmabuf = Dmabuf::new(client, new_id.object_id, new_id.version).await?; 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 => { RegistryGlobals::WL_DRM => {
tracing::info!("Binding wl_drm"); tracing::info!("Binding wl_drm");
let drm = MesaDrm::new(client, new_id.object_id, new_id.version).await?; 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 => { RegistryGlobals::PRESENTATION => {
tracing::info!("Binding wp_presentation"); tracing::info!("Binding wp_presentation");
client.insert(new_id.object_id, Presentation::new(new_id.object_id))?; client.insert(new_id.object_id, Presentation);
}
RegistryGlobals::VIEWPORTER => {
tracing::info!("Binding wp_viewporter");
client.insert(new_id.object_id, Viewporter::new(new_id.object_id))?;
} }
id => { id => {
tracing::error!(id, "Wayland: failed to bind to registry global"); 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)] #![allow(unused)]
use super::{Message, MessageSink, display::Display}; use super::{Message, MessageSink, display::Display};
use crate::wayland::{Client, WaylandError, WaylandResult};
use std::{fmt::Debug, sync::Arc}; use std::{fmt::Debug, sync::Arc};
use waynest::ObjectId; use waynest::{
use waynest_protocols::server::core::wayland::wl_display::WlDisplay; server::{Client, Result, protocol::core::wayland::wl_display::WlDisplay},
use waynest_server::{Client as _, RequestDispatcher}; wire::ObjectId,
};
pub trait ClientExt { pub trait ClientExt {
fn message_sink(&self) -> MessageSink; fn message_sink(&self) -> MessageSink;
fn display(&self) -> Arc<Display>; 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 { impl ClientExt for Client {
fn message_sink(&self) -> MessageSink { fn message_sink(&self) -> MessageSink {
@@ -24,8 +30,19 @@ impl ClientExt for Client {
self.get::<Display>(ObjectId::DISPLAY).unwrap() self.get::<Display>(ObjectId::DISPLAY).unwrap()
} }
fn try_get<D: RequestDispatcher>(&self, id: ObjectId) -> WaylandResult<Arc<D>> { async fn protocol_error(
self.get::<D>(id).ok_or(WaylandError::MissingObject(id)) &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,41 +1,30 @@
use super::toplevel::Toplevel; use super::toplevel::Toplevel;
use crate::{ use crate::{
core::{error::Result, task}, core::error::Result,
nodes::{ nodes::{
drawable::model::ModelPart, drawable::model::ModelPart,
items::panel::{ items::panel::{Backend, Geometry, PanelItemInitData, SurfaceId, ToplevelInfo},
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo, },
}, wayland::{Message, core::surface::Surface},
},
wayland::{
Message,
core::{
seat::{Seat, SeatMessage},
surface::Surface,
},
},
}; };
use dashmap::DashMap;
use mint::Vector2; use mint::Vector2;
use std::sync::Arc; use parking_lot::Mutex;
use std::sync::Weak; use std::{collections::HashMap, sync::{Arc, Weak}};
use tracing; use tracing;
#[derive(Debug)] #[derive(Debug)]
pub struct XdgBackend { pub struct XdgBackend {
seat: Weak<Seat>, toplevel: Weak<Toplevel>,
toplevel: Weak<Toplevel>, children: Mutex<HashMap<u64, Weak<Surface>>>,
children: DashMap<u64, (Weak<Surface>, ChildInfo)>,
} }
impl XdgBackend { impl XdgBackend {
pub fn new(seat: &Arc<Seat>, toplevel: &Arc<Toplevel>) -> Self { pub fn new(toplevel: Arc<Toplevel>) -> Self {
Self { Self {
seat: Arc::downgrade(seat), toplevel: Arc::downgrade(&toplevel),
toplevel: Arc::downgrade(toplevel), children: Mutex::new(HashMap::new()),
children: DashMap::new(), }
} }
}
// Since XdgBackend is created and owned by Mapped which is owned by Toplevel, // Since XdgBackend is created and owned by Mapped which is owned by Toplevel,
// we can safely assume the Toplevel reference will always be valid // we can safely assume the Toplevel reference will always be valid
@@ -45,62 +34,25 @@ impl XdgBackend {
.expect("Toplevel should always be valid while XdgBackend exists") .expect("Toplevel should always be valid while XdgBackend exists")
} }
pub fn panel_item(&self) -> Option<Arc<PanelItem<XdgBackend>>> { fn surface_from_id(&self, id: SurfaceId) -> Option<Arc<Surface>> {
self.toplevel().wl_surface().panel_item.lock().upgrade() match id {
} SurfaceId::Toplevel(_) => Some(self.toplevel().surface()),
SurfaceId::Child(uid) => self.children.lock().get(&uid).and_then(Weak::upgrade),
}
}
fn surface_from_id(&self, id: &SurfaceId) -> Option<Arc<Surface>> { pub fn register_child(&self, id: u64, surface: &Arc<Surface>) {
match id { self.children.lock().insert(id, Arc::downgrade(surface));
SurfaceId::Toplevel(_) => Some(self.toplevel().wl_surface().clone()), }
SurfaceId::Child(id) => self.children.get(id).as_deref().and_then(|c| c.0.upgrade()),
}
}
pub fn add_child(&self, surface: &Arc<Surface>, info: ChildInfo) { pub fn unregister_child(&self, id: u64) {
let Some(SurfaceId::Child(id)) = surface.surface_id.get().cloned() else { self.children.lock().remove(&id);
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 { impl Backend for XdgBackend {
fn start_data(&self) -> Result<PanelItemInitData> { 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 let size = surface_state
.buffer .buffer
@@ -132,20 +84,9 @@ impl Backend for XdgBackend {
}) })
} }
fn apply_cursor_material(&self, model_part: &Arc<ModelPart>) { 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_surface_material(&self, surface: SurfaceId, 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); surface.apply_material(model_part);
} }
} }
@@ -153,7 +94,7 @@ impl Backend for XdgBackend {
fn close_toplevel(&self) { fn close_toplevel(&self) {
let _ = self let _ = self
.toplevel() .toplevel()
.wl_surface() .surface()
.message_sink .message_sink
.send(Message::CloseToplevel(self.toplevel().clone())); .send(Message::CloseToplevel(self.toplevel().clone()));
} }
@@ -161,7 +102,7 @@ impl Backend for XdgBackend {
fn auto_size_toplevel(&self) { fn auto_size_toplevel(&self) {
let _ = self let _ = self
.toplevel() .toplevel()
.wl_surface() .surface()
.message_sink .message_sink
.send(Message::ResizeToplevel { .send(Message::ResizeToplevel {
toplevel: self.toplevel().clone(), toplevel: self.toplevel().clone(),
@@ -172,7 +113,7 @@ impl Backend for XdgBackend {
fn set_toplevel_size(&self, size: Vector2<u32>) { fn set_toplevel_size(&self, size: Vector2<u32>) {
let _ = self let _ = self
.toplevel() .toplevel()
.wl_surface() .surface()
.message_sink .message_sink
.send(Message::ResizeToplevel { .send(Message::ResizeToplevel {
toplevel: self.toplevel().clone(), toplevel: self.toplevel().clone(),
@@ -183,7 +124,7 @@ impl Backend for XdgBackend {
fn set_toplevel_focused_visuals(&self, focused: bool) { fn set_toplevel_focused_visuals(&self, focused: bool) {
let _ = self let _ = self
.toplevel() .toplevel()
.wl_surface() .surface()
.message_sink .message_sink
.send(Message::SetToplevelVisualActive { .send(Message::SetToplevelVisualActive {
toplevel: self.toplevel().clone(), toplevel: self.toplevel().clone(),
@@ -192,29 +133,22 @@ impl Backend for XdgBackend {
} }
fn pointer_motion(&self, surface: &SurfaceId, position: Vector2<f32>) { fn pointer_motion(&self, surface: &SurfaceId, position: Vector2<f32>) {
if let Some(surface) = self.surface_from_id(surface) { if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self let _ = self.toplevel().surface().message_sink.send(Message::Seat(
.toplevel() crate::wayland::core::seat::SeatMessage::PointerMotion { surface, position },
.wl_surface() ));
.message_sink
.send(Message::Seat(SeatMessage::PointerMotion {
surface,
position,
}));
} }
} }
fn pointer_button(&self, surface: &SurfaceId, button: u32, pressed: bool) { fn pointer_button(&self, surface: &SurfaceId, button: u32, pressed: bool) {
if let Some(surface) = self.surface_from_id(surface) { if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self let _ = self.toplevel().surface().message_sink.send(Message::Seat(
.toplevel() crate::wayland::core::seat::SeatMessage::PointerButton {
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::PointerButton {
surface, surface,
button, button,
pressed, pressed,
})); },
));
} }
} }
@@ -224,16 +158,14 @@ impl Backend for XdgBackend {
scroll_distance: Option<Vector2<f32>>, scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>, scroll_steps: Option<Vector2<f32>>,
) { ) {
if let Some(surface) = self.surface_from_id(surface) { if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self let _ = self.toplevel().surface().message_sink.send(Message::Seat(
.toplevel() crate::wayland::core::seat::SeatMessage::PointerScroll {
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::PointerScroll {
surface, surface,
scroll_distance, scroll_distance,
scroll_steps, scroll_steps,
})); },
));
} }
} }
@@ -243,17 +175,15 @@ impl Backend for XdgBackend {
key, key,
if pressed { "pressed" } else { "released" } if pressed { "pressed" } else { "released" }
); );
if let Some(surface) = self.surface_from_id(surface) { if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self let _ = self.toplevel().surface().message_sink.send(Message::Seat(
.toplevel() crate::wayland::core::seat::SeatMessage::KeyboardKey {
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::KeyboardKey {
surface, surface,
keymap_id, keymap_id,
key, key,
pressed, pressed,
})); },
));
} }
} }
@@ -264,16 +194,14 @@ impl Backend for XdgBackend {
position.x, position.x,
position.y position.y
); );
if let Some(surface) = self.surface_from_id(surface) { if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self let _ = self.toplevel().surface().message_sink.send(Message::Seat(
.toplevel() crate::wayland::core::seat::SeatMessage::TouchDown {
.wl_surface()
.message_sink
.send(Message::Seat(SeatMessage::TouchDown {
surface, surface,
id, id,
position, position,
})); },
));
} }
} }
@@ -284,28 +212,25 @@ impl Backend for XdgBackend {
position.x, position.x,
position.y position.y
); );
let toplevel = self.toplevel(); let surface = self.toplevel().surface();
let _ = toplevel let _ = surface.message_sink.send(Message::Seat(
.wl_surface() crate::wayland::core::seat::SeatMessage::TouchMove { id, position },
.message_sink ));
.send(Message::Seat(SeatMessage::TouchMove { id, position }));
} }
fn touch_up(&self, id: u32) { fn touch_up(&self, id: u32) {
tracing::debug!("Backend: Touch up {}", id); tracing::debug!("Backend: Touch up {}", id);
let toplevel = self.toplevel(); let surface = self.toplevel().surface();
let _ = toplevel let _ = surface.message_sink.send(Message::Seat(
.wl_surface() crate::wayland::core::seat::SeatMessage::TouchUp { id },
.message_sink ));
.send(Message::Seat(SeatMessage::TouchUp { id }));
} }
fn reset_input(&self) { fn reset_input(&self) {
tracing::debug!("Backend: Reset input"); tracing::debug!("Backend: Reset input");
let toplevel = self.toplevel(); let surface = self.toplevel().surface();
let _ = toplevel let _ = surface.message_sink.send(Message::Seat(
.wl_surface() crate::wayland::core::seat::SeatMessage::Reset,
.message_sink ));
.send(Message::Seat(SeatMessage::Reset));
} }
} }

View File

@@ -1,104 +1,178 @@
use super::{ use super::{
backend::XdgBackend,
positioner::{Positioner, PositionerData}, positioner::{Positioner, PositionerData},
surface::Surface, surface::Surface,
}; };
use crate::nodes::items::panel::SurfaceId; use crate::{
use crate::wayland::WaylandResult; nodes::items::panel::{ChildInfo, Geometry, PanelItem, SurfaceId},
wayland::util::DoubleBuffer,
};
use parking_lot::Mutex; use parking_lot::Mutex;
use rand::Rng; use rand::Rng;
use std::sync::Arc; use std::{
use waynest::ObjectId; sync::{Arc, Weak, atomic::AtomicBool},
use waynest_protocols::server::stable::xdg_shell::xdg_popup::XdgPopup; u64,
use waynest_server::Client as _; };
use waynest::{
server::{Client, Dispatcher, Result, protocol::stable::xdg_shell::xdg_popup::XdgPopup},
wire::ObjectId,
};
#[derive(Debug, waynest_server::RequestDispatcher)] #[derive(Debug, Dispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Popup { pub struct Popup {
version: u32,
pub surface: Arc<Surface>,
positioner_data: Mutex<PositionerData>,
id: ObjectId, id: ObjectId,
version: u32,
surface_id: SurfaceId,
parent: Arc<Surface>,
surface: Weak<Surface>,
pub panel_item: Weak<PanelItem<XdgBackend>>,
positioner_data: Mutex<PositionerData>,
geometry: Mutex<DoubleBuffer<Geometry>>,
mapped: AtomicBool,
} }
impl Popup { impl Popup {
pub fn new(version: u32, surface: Arc<Surface>, positioner: &Positioner, id: ObjectId) -> Self { pub fn new(
let _ = surface id: ObjectId,
.wl_surface version: u32,
.surface_id parent: Arc<Surface>,
.set(SurfaceId::Child(rand::rng().random())); panel_item: &Arc<PanelItem<XdgBackend>>,
xdg_surface: &Arc<Surface>,
positioner: &Positioner,
) -> Self {
let positioner_data = positioner.data(); let positioner_data = positioner.data();
Self { Self {
version,
surface,
positioner_data: Mutex::new(positioner_data),
id, 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: Mutex::new(DoubleBuffer::new(positioner_data.infinite_geometry())),
mapped: AtomicBool::new(false),
}
}
}
impl Popup {
fn id(&self) -> u64 {
match self.surface_id {
SurfaceId::Child(id) => id,
SurfaceId::Toplevel(_) => 0,
}
}
pub fn surface_id(&self) -> SurfaceId { self.surface_id.clone() }
pub fn current_geometry(&self) -> Geometry {
*self.geometry.lock().current()
}
pub fn is_mapped(&self) -> bool {
self.mapped.load(std::sync::atomic::Ordering::SeqCst)
}
pub fn map(&self) {
if self.is_mapped() {
return;
}
let Some(panel_item) = self.panel_item.upgrade() else { return; };
let xdg_surface = match self.surface.upgrade() { Some(s) => s, None => return };
let core_surface = xdg_surface.wl_surface();
// Determine parent surface id
let parent_wl_surface = self.parent.wl_surface();
let parent_role = parent_wl_surface.role.lock();
let parent_id = match parent_role.as_ref().unwrap() {
crate::wayland::core::surface::SurfaceRole::XdgToplevel(_) => {
SurfaceId::Toplevel(())
}
crate::wayland::core::surface::SurfaceRole::XDGPopup(p) => p.surface_id(),
};
let geometry = *self.geometry.lock().current();
let info = ChildInfo {
id: self.id(),
parent: parent_id,
geometry,
z_order: 0,
receives_input: true,
};
panel_item.create_child(self.id(), &info);
panel_item.backend.register_child(self.id(), &core_surface);
self.mapped.store(true, std::sync::atomic::Ordering::SeqCst);
}
pub fn unmap(&self) {
if !self.mapped.swap(false, std::sync::atomic::Ordering::SeqCst) {
return;
}
if let Some(panel_item) = self.panel_item.upgrade() {
panel_item.destroy_child(self.id());
panel_item.backend.unregister_child(self.id());
}
}
fn reposition_child(&self) {
if self.is_mapped() {
if let Some(panel_item) = self.panel_item.upgrade() {
let geometry = *self.geometry.lock().current();
panel_item.reposition_child(self.id(), &geometry);
}
}
}
} }
impl XdgPopup for Popup { impl XdgPopup for Popup {
type Connection = crate::wayland::Client;
/// https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab /// https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab
async fn grab( async fn grab(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_seat: ObjectId, _seat: ObjectId,
_serial: u32, _serial: u32,
) -> WaylandResult<()> { ) -> Result<()> {
Ok(()) Ok(())
} }
/// https://wayland.app/protocols/xdg-shell#xdg_popup:request:reposition /// https://wayland.app/protocols/xdg-shell#xdg_popup:request:reposition
async fn reposition( async fn reposition(
&self, &self,
client: &mut Self::Connection, client: &mut Client,
sender_id: ObjectId, sender_id: ObjectId,
positioner: ObjectId, positioner: ObjectId,
token: u32, token: u32,
) -> WaylandResult<()> { ) -> Result<()> {
let positioner = client.get::<Positioner>(positioner).unwrap(); let positioner = client.get::<Positioner>(positioner).unwrap();
let positioner_data = positioner.data(); let positioner_data = positioner.data();
*self.positioner_data.lock() = positioner_data; *self.positioner_data.lock() = positioner_data;
if self.version >= 5 { if self.version >= 5 {
self.repositioned(client, sender_id, token).await?; self.repositioned(client, sender_id, token).await?;
} }
let geometry = positioner_data.infinite_geometry(); let geometry = positioner_data.infinite_geometry();
self.configure( {
client, let mut geo = self.geometry.lock();
sender_id, geo.pending = geometry;
geometry.origin.x, geo.apply();
geometry.origin.y, }
geometry.size.x as i32, self.configure(
geometry.size.y as i32, client,
) sender_id,
.await?; geometry.origin.x,
self.surface.reconfigure(client).await?; geometry.origin.y,
geometry.size.x as i32,
let Some(panel_item) = self.surface.wl_surface.panel_item.lock().upgrade() else { geometry.size.y as i32,
return Ok(()); )
}; .await?;
panel_item self.surface.upgrade().unwrap().reconfigure(client).await?;
.backend self.reposition_child();
.reposition_child(&self.surface.wl_surface, geometry); Ok(())
Ok(()) }
}
/// https://wayland.app/protocols/xdg-shell#xdg_popup:request:destroy /// https://wayland.app/protocols/xdg-shell#xdg_popup:request:destroy
async fn destroy( async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
&self, self.unmap();
client: &mut Self::Connection, Ok(())
_sender_id: ObjectId, }
) -> WaylandResult<()> {
client.remove(self.id);
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 mint::Vector2;
use parking_lot::Mutex; use parking_lot::Mutex;
use waynest::ObjectId; use waynest::{
use waynest_protocols::server::stable::xdg_shell::xdg_positioner::*; server::{
use waynest_server::Client as _; Client, Dispatcher, Result,
protocol::stable::xdg_shell::xdg_positioner::{
Anchor, ConstraintAdjustment, Gravity, XdgPositioner,
},
},
wire::ObjectId,
};
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct PositionerData { pub struct PositionerData {
@@ -11,37 +17,11 @@ pub struct PositionerData {
pub anchor_rect: Geometry, pub anchor_rect: Geometry,
pub offset: Vector2<i32>, pub offset: Vector2<i32>,
pub anchor: Anchor, pub anchor: Anchor,
pub gravity: Gravity,
pub constraint_adjustment: ConstraintAdjustment, pub constraint_adjustment: ConstraintAdjustment,
pub reactive: bool, pub reactive: bool,
pub parent_size: Vector2<u32>, pub parent_size: Vector2<u32>,
} }
impl PositionerData { 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 { pub fn infinite_geometry(&self) -> Geometry {
let anchor_point = match self.anchor { let anchor_point = match self.anchor {
Anchor::TopLeft => self.anchor_rect.origin, Anchor::TopLeft => self.anchor_rect.origin,
@@ -87,29 +67,30 @@ impl PositionerData {
.into(), .into(),
}; };
let mut geometry = Geometry { let mut position = anchor_point;
origin: [
anchor_point.x + self.offset.x, // Apply gravity
anchor_point.y + self.offset.y, if self
] .constraint_adjustment
.into(), .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, 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 { impl Default for PositionerData {
@@ -119,7 +100,6 @@ impl Default for PositionerData {
anchor_rect: Default::default(), anchor_rect: Default::default(),
offset: [0, 0].into(), offset: [0, 0].into(),
anchor: Anchor::TopLeft, anchor: Anchor::TopLeft,
gravity: Gravity::TopLeft,
constraint_adjustment: ConstraintAdjustment::empty(), constraint_adjustment: ConstraintAdjustment::empty(),
reactive: false, reactive: false,
parent_size: [0; 2].into(), parent_size: [0; 2].into(),
@@ -127,17 +107,14 @@ impl Default for PositionerData {
} }
} }
#[derive(Debug, waynest_server::RequestDispatcher)] #[derive(Debug, Dispatcher)]
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
pub struct Positioner { pub struct Positioner {
data: Mutex<PositionerData>, data: Mutex<PositionerData>,
id: ObjectId,
} }
impl Positioner { impl Default for Positioner {
pub fn new(id: ObjectId) -> Self { fn default() -> Self {
Self { Self {
data: Mutex::new(PositionerData::default()), data: Mutex::new(PositionerData::default()),
id,
} }
} }
} }
@@ -147,15 +124,13 @@ impl Positioner {
} }
} }
impl XdgPositioner for Positioner { impl XdgPositioner for Positioner {
type Connection = crate::wayland::Client;
async fn set_size( async fn set_size(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_width: i32, _width: i32,
_height: i32, _height: i32,
) -> WaylandResult<()> { ) -> Result<()> {
let mut data = self.data.lock(); let mut data = self.data.lock();
data.size = [_width.max(0) as u32, _height.max(0) as u32].into(); data.size = [_width.max(0) as u32, _height.max(0) as u32].into();
data.reactive = true; data.reactive = true;
@@ -164,13 +139,13 @@ impl XdgPositioner for Positioner {
async fn set_anchor_rect( async fn set_anchor_rect(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_x: i32, _x: i32,
_y: i32, _y: i32,
_width: i32, _width: i32,
_height: i32, _height: i32,
) -> WaylandResult<()> { ) -> Result<()> {
let mut data = self.data.lock(); let mut data = self.data.lock();
data.anchor_rect.origin = [_x, _y].into(); data.anchor_rect.origin = [_x, _y].into();
data.anchor_rect.size = [_width.max(0) as u32, _height.max(0) as u32].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( async fn set_anchor(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_anchor: Anchor, _anchor: Anchor,
) -> WaylandResult<()> { ) -> Result<()> {
let mut data = self.data.lock(); let mut data = self.data.lock();
data.anchor = _anchor; data.anchor = _anchor;
Ok(()) Ok(())
@@ -191,21 +166,19 @@ impl XdgPositioner for Positioner {
async fn set_gravity( async fn set_gravity(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
gravity: Gravity, _gravity: Gravity,
) -> WaylandResult<()> { ) -> Result<()> {
let mut data = self.data.lock();
data.gravity = gravity;
Ok(()) Ok(())
} }
async fn set_constraint_adjustment( async fn set_constraint_adjustment(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_constraint_adjustment: ConstraintAdjustment, _constraint_adjustment: ConstraintAdjustment,
) -> WaylandResult<()> { ) -> Result<()> {
let mut data = self.data.lock(); let mut data = self.data.lock();
data.constraint_adjustment = _constraint_adjustment; data.constraint_adjustment = _constraint_adjustment;
Ok(()) Ok(())
@@ -213,32 +186,28 @@ impl XdgPositioner for Positioner {
async fn set_offset( async fn set_offset(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_x: i32, _x: i32,
_y: i32, _y: i32,
) -> WaylandResult<()> { ) -> Result<()> {
let mut data = self.data.lock(); let mut data = self.data.lock();
data.offset.x += _x; data.offset.x += _x;
data.offset.y += _y; data.offset.y += _y;
Ok(()) Ok(())
} }
async fn set_reactive( async fn set_reactive(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
&self,
_client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
Ok(()) Ok(())
} }
async fn set_parent_size( async fn set_parent_size(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_parent_width: i32, _parent_width: i32,
_parent_height: i32, _parent_height: i32,
) -> WaylandResult<()> { ) -> Result<()> {
let mut data = self.data.lock(); let mut data = self.data.lock();
data.parent_size.x = _parent_width.max(0) as u32; data.parent_size.x = _parent_width.max(0) as u32;
data.parent_size.y = _parent_height.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( async fn set_parent_configure(
&self, &self,
_client: &mut Self::Connection, _client: &mut Client,
_sender_id: ObjectId, _sender_id: ObjectId,
_serial: u32, _serial: u32,
) -> WaylandResult<()> { ) -> Result<()> {
Ok(()) Ok(())
} }
async fn destroy( async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
&self,
client: &mut Self::Connection,
_sender_id: ObjectId,
) -> WaylandResult<()> {
client.remove(self.id);
Ok(()) Ok(())
} }
} }

View File

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

View File

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