Compare commits
140 Commits
codex/make
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e00b487167 | ||
|
|
771a79cd33 | ||
|
|
a3afb08664 | ||
|
|
4de91ef20b | ||
|
|
94630c451a | ||
|
|
8ab891edb5 | ||
|
|
9f89d1a9ec | ||
|
|
c8e8ae2506 | ||
|
|
929c061f36 | ||
|
|
189c09ed79 | ||
|
|
ccbd773cee | ||
|
|
87857090b4 | ||
|
|
5f152df9f7 | ||
|
|
b70a188e67 | ||
|
|
5a2bb6faed | ||
|
|
65c426f981 | ||
|
|
ec468b6752 | ||
|
|
23f0b5f880 | ||
|
|
8bfb01808a | ||
|
|
e621f4b60e | ||
|
|
7e0b956f49 | ||
|
|
4d229b95b6 | ||
|
|
392eaf4ee5 | ||
|
|
6e2de6ac87 | ||
|
|
04e20a3bbf | ||
|
|
429a6efcee | ||
|
|
b1ab37c8dc | ||
|
|
181224767d | ||
|
|
2b07345286 | ||
|
|
d5034b4034 | ||
|
|
891d90fc5e | ||
|
|
ba97528ed6 | ||
|
|
536fafb4cf | ||
|
|
59e6a11079 | ||
|
|
0e4f5de529 | ||
|
|
05d4670609 | ||
|
|
08cec3c700 | ||
|
|
0b29f2f6c9 | ||
|
|
38a0520299 | ||
|
|
a080560c9c | ||
|
|
024ae4ddd7 | ||
|
|
6742caa967 | ||
|
|
ba5415653e | ||
|
|
ddef55879a | ||
|
|
c63416d1f3 | ||
|
|
b0ee7e9f54 | ||
|
|
ec871f5963 | ||
|
|
418e3a2ccb | ||
|
|
75bdb44371 | ||
|
|
6678681c2c | ||
|
|
3edaaf2dfc | ||
|
|
0ebfc1153e | ||
|
|
2d6bc06cbe | ||
|
|
bbf12b9e31 | ||
|
|
621a9c6d85 | ||
|
|
96e910c450 | ||
|
|
25b0760913 | ||
|
|
b542dc1b23 | ||
|
|
3e4be41d3f | ||
|
|
76fc1bfab5 | ||
|
|
928886563d | ||
|
|
e3a3db246e | ||
|
|
b4dccf6f89 | ||
|
|
0599eece82 | ||
|
|
27196e2dda | ||
|
|
0a4d6adf74 | ||
|
|
cf1cb90642 | ||
|
|
2343bbc974 | ||
|
|
fd7cad1ab4 | ||
|
|
0c3efe9477 | ||
|
|
065214c873 | ||
|
|
795f111ebc | ||
|
|
f40c6dcbd4 | ||
|
|
2632a0c5f3 | ||
|
|
1bfd9c95f0 | ||
|
|
a753001f15 | ||
|
|
cbd77fe704 | ||
|
|
c5246c9dc8 | ||
|
|
209171abfc | ||
|
|
7e53db3d33 | ||
|
|
4bc71b01cb | ||
|
|
c4ccda2118 | ||
|
|
3a55aaa2cf | ||
|
|
0650956ab4 | ||
|
|
45ec292b99 | ||
|
|
550087841f | ||
|
|
bd1b54cf03 | ||
|
|
707452462d | ||
|
|
1c8aa93850 | ||
|
|
a0b014576e | ||
|
|
50e1921cd9 | ||
|
|
14c5c355b5 | ||
|
|
4641f4f724 | ||
|
|
053d468035 | ||
|
|
f44abad5b0 | ||
|
|
9e8f09fe97 | ||
|
|
ae68d0e135 | ||
|
|
7314428ce7 | ||
|
|
c5440bc426 | ||
|
|
ad1c97aad6 | ||
|
|
51b0942c49 | ||
|
|
c665f33d25 | ||
|
|
a6a1195922 | ||
|
|
d6ad00bf53 | ||
|
|
b6524e90e1 | ||
|
|
6f113a9ec4 | ||
|
|
bf85140b65 | ||
|
|
2b9ba2b957 | ||
|
|
8df1ba549e | ||
|
|
dd0e45cffe | ||
|
|
a14457ecc1 | ||
|
|
eac44ded78 | ||
|
|
2357cba5f9 | ||
|
|
e69d85cc56 | ||
|
|
a5b4939b57 | ||
|
|
d9a6ca9bef | ||
|
|
2e3e944f99 | ||
|
|
fefa66060a | ||
|
|
6b93a1a095 | ||
|
|
5f6b48c9b4 | ||
|
|
bda2d06e81 | ||
|
|
87907984f6 | ||
|
|
83dbde9bc0 | ||
|
|
c69b2652c8 | ||
|
|
ec50f38dfd | ||
|
|
8e9ac71ebb | ||
|
|
b95ea8e90f | ||
|
|
712678a666 | ||
|
|
1c6b42e69a | ||
|
|
51de346f6b | ||
|
|
71b1792ee2 | ||
|
|
30f340fe41 | ||
|
|
040f86d50d | ||
|
|
f0a494392a | ||
|
|
a7aa609651 | ||
|
|
7a3322efad | ||
|
|
877a32ab09 | ||
|
|
4ccee1bf89 | ||
|
|
6b78684650 | ||
|
|
4f75aa5edf |
@@ -1,2 +1,2 @@
|
|||||||
[build]
|
[build]
|
||||||
rustflags = ["--cfg", "tokio_unstable"]
|
rustflags = ["--cfg", "tokio_unstable"]
|
||||||
|
|||||||
841
Cargo.lock
generated
841
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
58
Cargo.toml
58
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.88"
|
rust-version = "1.89"
|
||||||
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,8 +23,9 @@ 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",
|
||||||
@@ -34,7 +35,12 @@ wayland = [
|
|||||||
"dep:ash",
|
"dep:ash",
|
||||||
]
|
]
|
||||||
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
|
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
|
||||||
profile_app = ["dep:tracing-tracy", "bevy/trace_tracy", "bevy/trace"]
|
profile_app = [
|
||||||
|
"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]
|
||||||
@@ -43,16 +49,19 @@ auto_link_exclude_list = ["libc*", "libdl*", "libpthread*", "ld-linux*"]
|
|||||||
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
opt-level = "s"
|
||||||
lto = true
|
lto = true
|
||||||
strip = true
|
codegen-units = 1
|
||||||
opt-level = 3
|
panic = "abort"
|
||||||
|
# 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" }
|
bevy_mod_openxr = { git = "https://github.com/awtterpip/bevy_oxr", rev = "9fd0c797" }
|
||||||
bevy_mod_xr = { git = "https://github.com/awtterpip/bevy_oxr" }
|
bevy_mod_xr = { git = "https://github.com/awtterpip/bevy_oxr", rev = "9fd0c797" }
|
||||||
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" }
|
||||||
@@ -60,12 +69,15 @@ 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.8.5"
|
rand = "0.9.2"
|
||||||
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"
|
||||||
@@ -80,12 +92,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" }
|
tracy-client = { version = "=0.18.0", optional = true }
|
||||||
|
|
||||||
# (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.8.19"
|
toml = "0.9.7"
|
||||||
|
|
||||||
# mathy stuffs
|
# mathy stuffs
|
||||||
glam = { version = "0.29.0", features = ["mint", "serde"] }
|
glam = { version = "0.29.0", features = ["mint", "serde"] }
|
||||||
@@ -106,7 +118,6 @@ 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",
|
||||||
@@ -114,7 +125,6 @@ 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"
|
||||||
@@ -123,6 +133,7 @@ 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
|
||||||
@@ -132,32 +143,33 @@ openxr = "0.19"
|
|||||||
|
|
||||||
# linux stuffs
|
# linux stuffs
|
||||||
input-event-codes = "6.2.0"
|
input-event-codes = "6.2.0"
|
||||||
zbus = { version = "5.0.0", default-features = false, features = ["tokio"] }
|
zbus = { version = "5.11.0", features = [
|
||||||
directories = "5.0.1"
|
"blocking-api",
|
||||||
|
"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
|
||||||
cluFlock = { version = "1.2.7", optional = true } # for the lockfile checking
|
waynest = { git = "https://github.com/verdiwm/waynest.git", default-features = false, optional = true }
|
||||||
# waynest = { git = "https://github.com/verdiwm/waynest.git", features = [
|
waynest-protocols = { 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",
|
||||||
"external",
|
"mesa",
|
||||||
"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", optional = true }
|
vulkano = { git = "https://github.com/Schmarni-Dev/vulkano", branch = "0_35_dmabuf_fixes", default-features = false, 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
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ 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 }
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
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 {
|
||||||
@@ -64,6 +63,7 @@ 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(generate_aspect)
|
.map(|a| generate_aspect(&a.blocking_read()))
|
||||||
.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,14 +183,18 @@ 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.members.iter().split(|m| m.side == Side::Server);
|
let (client_members, server_members) = aspect
|
||||||
|
.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
|
||||||
.map(|m| generate_member(aspect.id, &aspect.name.to_case(Case::Snake), m))
|
.into_iter()
|
||||||
|
.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
|
||||||
@@ -228,6 +232,7 @@ 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();
|
||||||
@@ -281,9 +286,6 @@ 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,
|
||||||
@@ -304,12 +306,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::nodes::MethodResponseSender,
|
_method_response: crate::core::scenegraph::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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,6 +383,18 @@ 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()
|
||||||
@@ -405,12 +419,13 @@ 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!("failed to send remote signal: {}::{}, error: {}",#aspect_name,#name_str,err);
|
::tracing::warn!(#argument_debug "failed to send signal to client : {}::{}, error: {}",#aspect_name,#name_str,err);
|
||||||
} else {
|
|
||||||
::tracing::trace!("sent remote signal: {}::{}",#aspect_name,#name_str);
|
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@@ -420,11 +435,18 @@ 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 result = _node.execute_remote_method_typed(#aspect_id, #opcode, &(#argument_uses), vec![]).await;
|
let arguments = (#argument_uses);
|
||||||
if let Err(err) = result.as_ref() {
|
let (#(#arguments),*) = &arguments;
|
||||||
::tracing::warn!("failed to call remote method: {}::{}, error: {}",#aspect_name,#name_str,err);
|
::tracing::trace!(#argument_debug "called client method: {}::{}",#aspect_name,#name_str);
|
||||||
} else {
|
let result = _node.execute_remote_method_typed(#aspect_id, #opcode, &arguments, vec![]).await;
|
||||||
::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
|
||||||
}
|
}
|
||||||
@@ -447,6 +469,8 @@ 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
|
||||||
@@ -461,6 +485,13 @@ 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()
|
||||||
@@ -483,33 +514,30 @@ 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 => { let result = (move || {
|
#opcode => (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
|
||||||
let result = <Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await;
|
::tracing::trace!(#argument_debug "called local method: {}::{}",#aspect_name_str,#member_name);
|
||||||
if let Err(err) = result.as_ref() {
|
let result = <Self as #aspect_name>::#member_name_ident(_node, _calling_client.clone(), #argument_uses).await;
|
||||||
::tracing::warn!("failed to call local method: {}::{}, error: {}",#aspect_name_str,#member_name,err);
|
|
||||||
} else {
|
match result.as_ref() {
|
||||||
::tracing::trace!("called local method: {}::{}",#aspect_name_str,#member_name);
|
Ok(value) => {
|
||||||
};
|
::tracing::trace!(?value, "client method returned value: {}::{}",#aspect_name_str,#member_name);
|
||||||
let result = result?;
|
},
|
||||||
Ok((#serialize, Vec::<std::os::fd::OwnedFd>::new()))
|
Err(err) => {
|
||||||
|
::tracing::warn!("client method returned error: {}::{}, error: {}",#aspect_name_str,#member_name,err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = result?;
|
||||||
|
Ok((#serialize, Vec::<std::os::fd::OwnedFd>::new()))
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
51
flake.lock
generated
51
flake.lock
generated
@@ -26,11 +26,11 @@
|
|||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1722555600,
|
"lastModified": 1754487366,
|
||||||
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
|
"narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
|
"rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -47,11 +47,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1712014858,
|
"lastModified": 1754487366,
|
||||||
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
"narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
"rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -65,11 +65,11 @@
|
|||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1713050562,
|
"lastModified": 1725868201,
|
||||||
"narHash": "sha256-m7c6XpmpTM1URuqMG2KqtaWbL2Vt8vJFJtmvq123BmY=",
|
"narHash": "sha256-rDBQ9tXQCCA7emikSYH59ADJELE2IpzB7eoLrpHYzU4=",
|
||||||
"owner": "StardustXR",
|
"owner": "StardustXR",
|
||||||
"repo": "flatland",
|
"repo": "flatland",
|
||||||
"rev": "b3b0f29c4ea1b82c96cf9de507837bf15a5e4c0e",
|
"rev": "0914dd3df54a5e6258dfc0a02d65af1c0fc0fc90",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -84,11 +84,11 @@
|
|||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1719226092,
|
"lastModified": 1755233722,
|
||||||
"narHash": "sha256-YNkUMcCUCpnULp40g+svYsaH1RbSEj6s4WdZY/SHe38=",
|
"narHash": "sha256-AavrbMltJKcC2Fx0lfJoZfmy7g87ebXU0ddVenhajLA=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "hercules-ci-effects",
|
"repo": "hercules-ci-effects",
|
||||||
"rev": "11e4b8dc112e2f485d7c97e1cee77f9958f498f5",
|
"rev": "99e03e72e3f7e13506f80ef9ebaedccb929d84d0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -115,23 +115,26 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1722555339,
|
"lastModified": 1753579242,
|
||||||
"narHash": "sha256-uFf2QeW7eAHlYXuDktm9c25OxOyCoUOQmh5SZ9amE5Q=",
|
"narHash": "sha256-zvaMGVn14/Zz8hnp4VWT9xVnhc8vuL3TStRqwk22biA=",
|
||||||
"type": "tarball",
|
"owner": "nix-community",
|
||||||
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
|
"repo": "nixpkgs.lib",
|
||||||
|
"rev": "0f36c44e01a6129be94e3ade315a5883f0228a6e",
|
||||||
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "tarball",
|
"owner": "nix-community",
|
||||||
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
|
"repo": "nixpkgs.lib",
|
||||||
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1713714899,
|
"lastModified": 1755027561,
|
||||||
"narHash": "sha256-+z/XjO3QJs5rLE5UOf015gdVauVRQd2vZtsFkaXBq2Y=",
|
"narHash": "sha256-IVft239Bc8p8Dtvf7UAACMG5P3ZV+3/aO28gXpGtMXI=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "6143fc5eeb9c4f00163267708e26191d1e918932",
|
"rev": "005433b926e16227259a1843015b5b2b7f7d1fc3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -143,11 +146,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
"nixpkgs_3": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1723991338,
|
"lastModified": 1755186698,
|
||||||
"narHash": "sha256-Grh5PF0+gootJfOJFenTTxDTYPidA3V28dqJ/WV7iis=",
|
"narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "8a3354191c0d7144db9756a74755672387b702ba",
|
"rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -61,14 +61,11 @@
|
|||||||
};
|
};
|
||||||
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 sk_gpu;
|
inherit name src;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
apps.default = {
|
apps.default = {
|
||||||
|
|||||||
@@ -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.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
|
picture-uri='file://${pkgs.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
|
||||||
picture-uri-dark='file://${pkgs.gnome.gnome-backgrounds}/share/backgrounds/gnome/blobs-l.svg'
|
picture-uri-dark='file://${pkgs.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.gnome-shell}/bin/gnome-shell --unsafe-mode"
|
"${pkgs.gnome-shell}/bin/gnome-shell --unsafe-mode"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
{ rustPlatform
|
{
|
||||||
, src
|
rustPlatform,
|
||||||
, name
|
src,
|
||||||
, libGL
|
name,
|
||||||
, mesa
|
vulkan-loader,
|
||||||
, xorg
|
vulkan-headers,
|
||||||
, fontconfig
|
mesa,
|
||||||
, libxkbcommon
|
xorg,
|
||||||
, libclang
|
fontconfig,
|
||||||
|
libxkbcommon,
|
||||||
|
libclang,
|
||||||
|
|
||||||
, cmake
|
cmake,
|
||||||
, cpm-cmake
|
pkg-config,
|
||||||
, pkg-config
|
llvmPackages,
|
||||||
, llvmPackages
|
fetchFromGitHub,
|
||||||
, fetchFromGitHub
|
libXau,
|
||||||
, sk_gpu
|
|
||||||
, libXau
|
|
||||||
|
|
||||||
, libXdmcp
|
libXdmcp,
|
||||||
, stdenv
|
stdenv,
|
||||||
, lib
|
lib,
|
||||||
, openxr-loader
|
openxr-loader,
|
||||||
|
wayland,
|
||||||
|
alsa-lib,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
rustPlatform.buildRustPackage rec {
|
rustPlatform.buildRustPackage rec {
|
||||||
@@ -28,50 +30,27 @@ 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;
|
|
||||||
|
|
||||||
meshoptimizer = fetchFromGitHub {
|
preBuild = ''
|
||||||
owner = "zeux";
|
substituteInPlace /build/cargo-vendor-dir/bevy_gltf-0.16.1/Cargo.toml \
|
||||||
repo = "meshoptimizer";
|
--replace-fail '[lints]' "" \
|
||||||
rev = "c21d3be6ddf627f8ca852ba4b6db9903b0557858";
|
--replace-fail 'workspace = true' ""
|
||||||
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 = [
|
||||||
libGL
|
vulkan-loader
|
||||||
|
vulkan-headers
|
||||||
mesa
|
mesa
|
||||||
xorg.libX11.dev
|
xorg.libX11.dev
|
||||||
xorg.libXft
|
xorg.libXft
|
||||||
@@ -81,6 +60,9 @@ rustPlatform.buildRustPackage rec {
|
|||||||
libXau
|
libXau
|
||||||
libXdmcp
|
libXdmcp
|
||||||
openxr-loader
|
openxr-loader
|
||||||
|
wayland
|
||||||
|
alsa-lib
|
||||||
];
|
];
|
||||||
|
|
||||||
LIBCLANG_PATH = "${libclang.lib}/lib";
|
LIBCLANG_PATH = "${libclang.lib}/lib";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,12 +144,7 @@ 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 {
|
||||||
@@ -166,7 +161,7 @@ impl Client {
|
|||||||
});
|
});
|
||||||
let _ = client.flush_join_handle.get_or_init(|| {
|
let _ = client.flush_join_handle.get_or_init(|| {
|
||||||
task::new(
|
task::new(
|
||||||
|| format!("client flush pid={} exe={}", &pid_printable, &exe_printable,),
|
|| format!("Stardust client \"{exe_printable}\" flush, pid={pid_printable}"),
|
||||||
{
|
{
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
async move {
|
async move {
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ 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>,
|
||||||
pub data: Vec<u8>,
|
#[serde(skip)]
|
||||||
|
pub data: Option<Vec<u8>>,
|
||||||
pub root: Mat4,
|
pub root: Mat4,
|
||||||
pub spatial_anchors: FxHashMap<String, Mat4>,
|
pub spatial_anchors: FxHashMap<String, Mat4>,
|
||||||
}
|
}
|
||||||
@@ -41,7 +42,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.unwrap_or_default(),
|
data: state.data,
|
||||||
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
|
||||||
@@ -63,7 +64,9 @@ 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()?;
|
||||||
toml::from_str(&file_string).ok()
|
let mut client_state: Self = toml::from_str(&file_string).ok()?;
|
||||||
|
client_state.data = std::fs::read(file.with_extension("bin")).ok();
|
||||||
|
Some(client_state)
|
||||||
}
|
}
|
||||||
pub fn to_file(&self, directory: &Path) {
|
pub fn to_file(&self, directory: &Path) {
|
||||||
let app_name = self
|
let app_name = self
|
||||||
@@ -71,11 +74,14 @@ 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_path = directory
|
let state_file_prefix = directory.join(format!("{app_name}-{}", nanoid::nanoid!()));
|
||||||
.join(format!("{app_name}-{}", nanoid::nanoid!()))
|
let state_metadata_path = state_file_prefix.with_extension("toml");
|
||||||
.with_extension("toml");
|
let state_data_path = state_file_prefix.with_extension("bin");
|
||||||
|
|
||||||
std::fs::write(state_file_path, toml::to_string(&self).unwrap()).unwrap();
|
std::fs::write(state_metadata_path, toml::to_string(&self).unwrap()).unwrap();
|
||||||
|
if let Some(data) = self.data.as_deref() {
|
||||||
|
std::fs::write(state_data_path, data).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_to(&self, client: &Arc<Client>) -> ClientState {
|
pub fn apply_to(&self, client: &Arc<Client>) -> ClientState {
|
||||||
@@ -83,7 +89,7 @@ impl ClientStateParsed {
|
|||||||
root.set_transform(self.root)
|
root.set_transform(self.root)
|
||||||
}
|
}
|
||||||
ClientState {
|
ClientState {
|
||||||
data: Some(self.data.clone()),
|
data: self.data.clone(),
|
||||||
root: 0,
|
root: 0,
|
||||||
spatial_anchors: self
|
spatial_anchors: self
|
||||||
.spatial_anchors
|
.spatial_anchors
|
||||||
@@ -111,7 +117,7 @@ impl Default for ClientStateParsed {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
launch_info: None,
|
launch_info: None,
|
||||||
data: Default::default(),
|
data: None,
|
||||||
root: Mat4::IDENTITY,
|
root: Mat4::IDENTITY,
|
||||||
spatial_anchors: Default::default(),
|
spatial_anchors: Default::default(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ 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::srgba(self.c.r, self.c.g, self.c.b, self.a)
|
bevy::color::Color::linear_rgba(self.c.r, self.c.g, self.c.b, self.a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
@@ -10,24 +15,46 @@ impl Plugin for EntityHandlePlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn despawn(mut cmds: Commands, mut reader: ResMut<BevyChannelReader<Entity>>) {
|
fn despawn(
|
||||||
|
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() {
|
||||||
cmds.entity(e).try_despawn();
|
if let Ok(children) = child_query.get(e) {
|
||||||
|
for e in children {
|
||||||
|
if has_spatial.get(*e).unwrap_or_default() {
|
||||||
|
cmds.entity(*e).try_remove::<ChildOf>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmds.entity(e).despawn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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(Deref, DerefMut, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct EntityHandle(pub Entity);
|
pub struct EntityHandleInner(Entity);
|
||||||
impl Drop for EntityHandle {
|
impl Drop for EntityHandleInner {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if DESTROY.send(self.0).is_none() {
|
DESTROY.send(self.0);
|
||||||
error!("Entity Destroy channel not open");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<Entity> for EntityHandle {
|
|
||||||
fn from(value: Entity) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ 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}")]
|
||||||
|
|||||||
@@ -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()
|
||||||
if !old.0.contains_key(id) {
|
&& !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()
|
||||||
if !new.0.contains_key(id) {
|
&& !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>
|
||||||
|
|||||||
@@ -1,19 +1,80 @@
|
|||||||
use crate::core::error::Result;
|
use crate::{
|
||||||
use crate::nodes::Node;
|
core::{
|
||||||
use crate::nodes::alias::get_original;
|
client::Client,
|
||||||
use crate::{core::client::Client, nodes::Message};
|
error::{Result, ServerError},
|
||||||
|
},
|
||||||
|
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::scenegraph;
|
use stardust_xr::{
|
||||||
use stardust_xr::scenegraph::ScenegraphError;
|
messenger::MethodResponse,
|
||||||
use stardust_xr::schemas::flex::serialize;
|
scenegraph::{self, ScenegraphError},
|
||||||
use std::future::Future;
|
schemas::flex::serialize,
|
||||||
use std::os::fd::OwnedFd;
|
};
|
||||||
use std::sync::{Arc, OnceLock, Weak};
|
use std::{
|
||||||
use tokio::sync::oneshot;
|
os::fd::OwnedFd,
|
||||||
|
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>>,
|
||||||
@@ -45,43 +106,6 @@ 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,
|
||||||
@@ -115,15 +139,15 @@ impl scenegraph::Scenegraph for Scenegraph {
|
|||||||
method: u64,
|
method: u64,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
fds: Vec<OwnedFd>,
|
fds: Vec<OwnedFd>,
|
||||||
response: oneshot::Sender<Result<(Vec<u8>, Vec<OwnedFd>), ScenegraphError>>,
|
response: MethodResponse,
|
||||||
) {
|
) {
|
||||||
let Some(client) = self.get_client() else {
|
let Some(client) = self.get_client() else {
|
||||||
let _ = response.send(Err(ScenegraphError::NodeNotFound));
|
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 {
|
||||||
let _ = response.send(Err(ScenegraphError::NodeNotFound));
|
response.send(Err(ScenegraphError::NodeNotFound));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
node.execute_local_method(
|
node.execute_local_method(
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use color_eyre::eyre::Result;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
@@ -11,7 +10,7 @@ pub fn new<
|
|||||||
>(
|
>(
|
||||||
name_fn: F,
|
name_fn: F,
|
||||||
async_future: A,
|
async_future: A,
|
||||||
) -> Result<JoinHandle<O>> {
|
) -> std::io::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")]
|
||||||
|
|||||||
135
src/main.rs
135
src/main.rs
@@ -1,3 +1,4 @@
|
|||||||
|
#![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)]
|
||||||
@@ -5,11 +6,14 @@ 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;
|
||||||
@@ -18,12 +22,16 @@ 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;
|
||||||
@@ -62,17 +70,20 @@ 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;
|
use stardust_xr::server::LockedSocket;
|
||||||
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;
|
||||||
@@ -84,7 +95,11 @@ 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)]
|
||||||
flatscreen: bool,
|
force_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)]
|
||||||
@@ -132,9 +147,7 @@ async fn main() -> Result<AppExit, JoinError> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "profile_tokio")]
|
#[cfg(feature = "profile_tokio")]
|
||||||
let (console_layer, _) = console_subscriber::ConsoleLayer::builder().build();
|
let registry = registry.with(console_subscriber::spawn());
|
||||||
#[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)
|
||||||
@@ -143,22 +156,23 @@ 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 socket_path =
|
let locked_socket =
|
||||||
server::get_free_socket_path().expect("Unable to find a free stardust socket path");
|
LockedSocket::get_free().expect("Unable to find a free stardust socket path");
|
||||||
STARDUST_INSTANCE.set(socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell");
|
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");
|
||||||
info!(
|
info!(
|
||||||
socket_path = ?socket_path.display(),
|
socket_path = ?locked_socket.socket_path.display(),
|
||||||
"Stardust socket created"
|
"Stardust socket created"
|
||||||
);
|
);
|
||||||
let socket =
|
let socket = UnixListener::bind(locked_socket.socket_path)
|
||||||
UnixListener::bind(socket_path).expect("Couldn't spawn stardust server at {socket_path}");
|
.expect("Couldn't spawn stardust server at {socket_path}");
|
||||||
task::new(|| "client join loop", async move {
|
task::new(|| "Stardust socket accept loop", async move {
|
||||||
loop {
|
loop {
|
||||||
let Ok((stream, _)) = socket.accept().await else {
|
let Ok((stream, _)) = socket.accept().await else {
|
||||||
continue;
|
continue;
|
||||||
@@ -196,10 +210,9 @@ 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.expect(
|
let object_registry = ObjectRegistry::new(&dbus_connection).await;
|
||||||
"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());
|
||||||
@@ -242,7 +255,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(ObjectRegistry);
|
pub struct ObjectRegistryRes(Arc<ObjectRegistry>);
|
||||||
#[derive(Resource, Deref)]
|
#[derive(Resource, Deref)]
|
||||||
pub struct DbusConnection(Connection);
|
pub struct DbusConnection(Connection);
|
||||||
|
|
||||||
@@ -251,7 +264,7 @@ fn bevy_loop(
|
|||||||
_project_dirs: Option<ProjectDirs>,
|
_project_dirs: Option<ProjectDirs>,
|
||||||
args: CliArgs,
|
args: CliArgs,
|
||||||
dbus_connection: Connection,
|
dbus_connection: Connection,
|
||||||
object_registry: ObjectRegistry,
|
object_registry: Arc<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));
|
||||||
@@ -299,8 +312,11 @@ fn bevy_loop(
|
|||||||
// .add(AnimationPlugin)
|
// .add(AnimationPlugin)
|
||||||
.add(AudioPlugin::default())
|
.add(AudioPlugin::default())
|
||||||
.add(GizmoPlugin)
|
.add(GizmoPlugin)
|
||||||
.add(WindowPlugin::default())
|
.add(WindowPlugin::default());
|
||||||
.add(DmabufImportPlugin);
|
#[cfg(feature = "wayland")]
|
||||||
|
{
|
||||||
|
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();
|
||||||
@@ -316,19 +332,22 @@ 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 args.flatscreen
|
if std::env::var("DISPLAY").is_ok_and(|s| !s.is_empty())
|
||||||
|| std::env::var_os("DISPLAY").is_some_and(|s| !s.is_empty())
|
|| std::env::var("WAYLAND_DISPLAY").is_ok_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
|
plugins = plugins.add(plugin).disable::<ScheduleRunnerPlugin>();
|
||||||
.add(plugin)
|
plugins = match args.spectator {
|
||||||
.disable::<ScheduleRunnerPlugin>()
|
true => plugins.add(SpectatorCameraPlugin),
|
||||||
.add(FlatscreenInputPlugin);
|
false => plugins.add(FlatscreenInputPlugin),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
app.insert_resource(PipelinedRenderThreadOnCreateCallback(
|
||||||
|
enter_runtime_context.clone(),
|
||||||
|
));
|
||||||
app.add_plugins(
|
app.add_plugins(
|
||||||
if !args.flatscreen {
|
if !args.force_flatscreen {
|
||||||
add_xr_plugins(plugins)
|
add_xr_plugins(plugins)
|
||||||
.set(OxrInitPlugin {
|
.set(OxrInitPlugin {
|
||||||
app_info: AppInfo {
|
app_info: AppInfo {
|
||||||
@@ -370,7 +389,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::Inherit
|
CompositeAlphaMode::Auto
|
||||||
},
|
},
|
||||||
title: "StardustXR server flatscreen mode".to_string(),
|
title: "StardustXR server flatscreen mode".to_string(),
|
||||||
..default()
|
..default()
|
||||||
@@ -378,8 +397,10 @@ 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 {
|
||||||
@@ -416,15 +437,19 @@ 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
|
||||||
app.add_plugins((WaylandPlugin, TrackingOffsetPlugin, FieldDebugGizmoPlugin));
|
#[cfg(feature = "wayland")]
|
||||||
|
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_observer);
|
app.add_observer(cam_settings);
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
XrFirst,
|
XrFirst,
|
||||||
xr_step
|
xr_step
|
||||||
@@ -435,11 +460,13 @@ fn bevy_loop(
|
|||||||
app.run()
|
app.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cam_observer(
|
fn cam_settings(
|
||||||
trigger: Trigger<OnAdd, Camera3d>,
|
trigger: Trigger<OnAdd, Camera3d>,
|
||||||
mut query: Query<(&mut Projection, &mut Msaa), With<Camera3d>>,
|
mut query: Query<(Entity, &mut Projection, &mut Msaa, &mut Tonemapping), With<Camera3d>>,
|
||||||
|
mut cmds: Commands,
|
||||||
) {
|
) {
|
||||||
let Ok((mut projection, mut msaa)) = query.get_mut(trigger.target()) else {
|
let Ok((entity, mut projection, mut msaa, mut tonemapping)) = query.get_mut(trigger.target())
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
info!("modifying cam");
|
info!("modifying cam");
|
||||||
@@ -455,26 +482,9 @@ fn cam_observer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*msaa = Msaa::Off;
|
*msaa = Msaa::Off;
|
||||||
}
|
*tonemapping = Tonemapping::None;
|
||||||
|
cmds.entity(entity)
|
||||||
fn add_oit(
|
.insert(OrderIndependentTransparencySettings::default());
|
||||||
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) {
|
||||||
@@ -484,13 +494,6 @@ 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);
|
||||||
@@ -510,3 +513,13 @@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use super::{Aspect, AspectIdentifier, Node};
|
use super::{Aspect, AspectIdentifier, Node};
|
||||||
use crate::core::{client::Client, error::Result, registry::Registry};
|
use crate::core::{
|
||||||
|
client::Client, error::Result, registry::Registry, scenegraph::MethodResponseSender,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
ops::Add,
|
ops::Add,
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
@@ -71,9 +73,6 @@ 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>,
|
||||||
@@ -89,7 +88,7 @@ impl Aspect for Alias {
|
|||||||
_node: Arc<Node>,
|
_node: Arc<Node>,
|
||||||
_method: u64,
|
_method: u64,
|
||||||
_message: super::Message,
|
_message: super::Message,
|
||||||
_response: crate::core::scenegraph::MethodResponseSender,
|
_response: MethodResponseSender,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,6 +121,7 @@ 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()
|
||||||
|
|||||||
@@ -57,29 +57,27 @@ 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());
|
||||||
sound
|
let entity = cmds
|
||||||
.entity
|
.spawn((
|
||||||
.set(
|
Name::new("Audio Node"),
|
||||||
cmds.spawn((
|
SpatialNode(Arc::downgrade(&sound.spatial)),
|
||||||
Name::new("Audio Node"),
|
AudioPlayer::new(handle),
|
||||||
SpatialNode(Arc::downgrade(&sound.spatial)),
|
PlaybackSettings {
|
||||||
AudioPlayer::new(handle),
|
mode: PlaybackMode::Once,
|
||||||
PlaybackSettings {
|
volume: Volume::Linear(sound.volume),
|
||||||
mode: PlaybackMode::Once,
|
speed: 1.0,
|
||||||
volume: Volume::Linear(sound.volume),
|
paused: true,
|
||||||
speed: 1.0,
|
muted: false,
|
||||||
paused: true,
|
spatial: true,
|
||||||
muted: false,
|
spatial_scale: None,
|
||||||
spatial: true,
|
},
|
||||||
spatial_scale: None,
|
))
|
||||||
},
|
.id();
|
||||||
))
|
let entity = EntityHandle::new(entity);
|
||||||
.id()
|
sound.spatial.set_entity(entity.clone());
|
||||||
.into(),
|
sound.entity.set(entity).unwrap();
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
if let Some(sink) = sound.entity.get().and_then(|e| sinks.get(e.0).ok()) {
|
if let Some(sink) = sound.entity.get().and_then(|e| sinks.get(e.get()).ok()) {
|
||||||
if sound.play.lock().take().is_some() {
|
if sound.play.lock().take().is_some() {
|
||||||
sink.play();
|
sink.play();
|
||||||
}
|
}
|
||||||
|
|||||||
56
src/nodes/drawable/line.wgsl
Normal file
56
src/nodes/drawable/line.wgsl
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#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
|
||||||
|
}
|
||||||
@@ -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::{
|
nodes::{Node, drawable::LinePoint, spatial::Spatial},
|
||||||
Node,
|
|
||||||
spatial::{Spatial, SpatialNode},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use bevy::{
|
use bevy::{
|
||||||
asset::RenderAssetUsages,
|
asset::{AssetEvents, RenderAssetUsages, weak_handle},
|
||||||
|
pbr::{ExtendedMaterial, MaterialExtension},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
render::{
|
render::{
|
||||||
mesh::{Indices, MeshAabb, PrimitiveTopology},
|
mesh::{Indices, PrimitiveTopology, VertexAttributeValues},
|
||||||
primitives::Aabb,
|
primitives::Aabb,
|
||||||
|
render_resource::{AsBindGroup, ShaderRef},
|
||||||
|
view::VisibilitySystems,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use glam::Vec3;
|
use glam::Vec3;
|
||||||
@@ -24,25 +24,79 @@ 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(Update, build_line_mesh);
|
app.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
build_line_mesh
|
||||||
|
.after(TransformSystem::TransformPropagate)
|
||||||
|
.before(AssetEvents)
|
||||||
|
.after(VisibilitySystems::VisibilityPropagate)
|
||||||
|
.before(VisibilitySystems::CheckVisibility),
|
||||||
|
);
|
||||||
|
app.world_mut().resource_mut::<Assets<Shader>>().insert(
|
||||||
|
LINE_SHADER_HANDLE.id(),
|
||||||
|
Shader::from_wgsl(
|
||||||
|
include_str!("line.wgsl"),
|
||||||
|
std::path::Path::new(file!())
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.join("line.wgsl")
|
||||||
|
.to_string_lossy(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
app.add_plugins(MaterialPlugin::<LineMaterial>::default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_line_mesh(
|
fn build_line_mesh(
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut cmds: Commands,
|
mut cmds: Commands,
|
||||||
mut materials: ResMut<Assets<BevyMaterial>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<LineMaterial>>,
|
||||||
|
query: Query<(Ref<GlobalTransform>, &InheritedVisibility)>,
|
||||||
) {
|
) {
|
||||||
for lines in LINES_REGISTRY
|
for lines in LINES_REGISTRY.get_valid_contents().into_iter()
|
||||||
.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();
|
||||||
@@ -50,16 +104,14 @@ 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() = Aabb::default();
|
*lines.bounds.lock() = Some(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 => {
|
||||||
let e = cmds.spawn((
|
// if we couldn't get the lines entity then we need to gen the mesh later
|
||||||
Name::new("LinesNode"),
|
lines.gen_mesh.store(true, Ordering::Relaxed);
|
||||||
SpatialNode(Arc::downgrade(&lines.spatial)),
|
continue;
|
||||||
));
|
|
||||||
_ = lines.entity.set(e.id().into());
|
|
||||||
e
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.remove::<Mesh3d>();
|
.remove::<Mesh3d>();
|
||||||
@@ -68,15 +120,35 @@ 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
|
||||||
@@ -84,7 +156,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()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,6 +165,10 @@ 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(
|
||||||
@@ -115,6 +191,10 @@ 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(),
|
||||||
@@ -126,71 +206,81 @@ 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_srgba().to_f32_array(); 8]);
|
vertex_colors.extend([curr.color.to_bevy().to_linear().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;
|
||||||
}
|
}
|
||||||
// Handle the connection between start and end points:
|
if indices_set > 0 {
|
||||||
// - For cyclic lines: connect last segment back to first
|
// Handle the connection between start and end points:
|
||||||
// - For non-cyclic lines: add caps at both ends
|
// - For cyclic lines: connect last segment back to first
|
||||||
if line.cyclic {
|
// - For non-cyclic lines: add caps at both ends
|
||||||
vertex_indices.extend(cyclic_indices(start_set, indices_set - 1));
|
if line.cyclic {
|
||||||
} else {
|
vertex_indices.extend(cyclic_indices(start_set, indices_set - 1));
|
||||||
vertex_indices.extend(cap_indices(start_set, false));
|
} else {
|
||||||
vertex_indices.extend(cap_indices(indices_set - 1, true));
|
vertex_indices.extend(cap_indices(start_set, false));
|
||||||
|
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_COLOR, vertex_colors);
|
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_positions);
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_normals);
|
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_normals);
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_positions.clone());
|
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_colors);
|
||||||
if let Some(aabb) = mesh.compute_aabb() {
|
|
||||||
info!(?aabb);
|
|
||||||
*lines.bounds.lock() = aabb;
|
|
||||||
}
|
|
||||||
|
|
||||||
match lines.entity.get() {
|
let mut entity = 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"),
|
||||||
SpatialNode(Arc::downgrade(&lines.spatial)),
|
MeshMaterial3d(materials.add(ExtendedMaterial {
|
||||||
MeshMaterial3d(materials.add(BevyMaterial {
|
base: BevyMaterial {
|
||||||
base_color: Color::WHITE,
|
base_color: Color::WHITE,
|
||||||
perceptual_roughness: 1.0,
|
perceptual_roughness: 1.0,
|
||||||
// TODO: this should be Blend
|
alpha_mode: AlphaMode::Premultiplied,
|
||||||
alpha_mode: AlphaMode::Opaque,
|
emissive: Color::linear_rgba(0.25, 0.25, 0.25, 1.0).into(),
|
||||||
..default()
|
..default()
|
||||||
|
},
|
||||||
|
extension: LineExtension {},
|
||||||
})),
|
})),
|
||||||
));
|
));
|
||||||
_ = lines.entity.set(e.id().into());
|
_ = lines.entity.set(EntityHandle::new(e.id()));
|
||||||
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);
|
||||||
}
|
}
|
||||||
.insert(Mesh3d(meshes.add(mesh)));
|
entity
|
||||||
|
.insert(Mesh3d(meshes.add(mesh)))
|
||||||
|
.insert(*visibil)
|
||||||
|
.insert(match visibil.get() {
|
||||||
|
true => Visibility::Visible,
|
||||||
|
false => Visibility::Hidden,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +322,8 @@ 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<Aabb>,
|
bounds: Mutex<Option<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>> {
|
||||||
@@ -241,10 +332,19 @@ impl Lines {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.bounding_box_calc
|
.bounding_box_calc
|
||||||
.set(|node| {
|
.set(|node| {
|
||||||
node.get_aspect::<Lines>()
|
Box::pin(async {
|
||||||
.ok()
|
let Ok(lines) = node.get_aspect::<Lines>() else {
|
||||||
.map(|v| *v.bounds.lock())
|
return Default::default();
|
||||||
.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 {
|
||||||
@@ -252,7 +352,8 @@ 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(Aabb::default()),
|
bounds: Mutex::new(Some(Aabb::default())),
|
||||||
|
setup_complete: Notify::new(),
|
||||||
});
|
});
|
||||||
node.add_aspect_raw(lines.clone());
|
node.add_aspect_raw(lines.clone());
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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};
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ 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();
|
||||||
|
|
||||||
@@ -54,11 +55,7 @@ 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),
|
||||||
);
|
);
|
||||||
@@ -67,7 +64,14 @@ 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()
|
||||||
@@ -101,9 +105,13 @@ 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.bevy_scene_entity.set(entity.into()).unwrap();
|
model
|
||||||
|
.bevy_scene_entity
|
||||||
|
.set(EntityHandle::new(entity))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +148,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.space.node().unwrap().get_client().unwrap(),
|
&model_part.spatial.node().unwrap().get_client().unwrap(),
|
||||||
&mut new_mat,
|
&mut new_mat,
|
||||||
¶m_name,
|
¶m_name,
|
||||||
&asset_server,
|
&asset_server,
|
||||||
@@ -189,7 +197,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.space.clone())
|
.map(|p| p.spatial.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) =
|
||||||
@@ -199,7 +207,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),
|
Some(parent_spatial.clone()),
|
||||||
transform.compute_matrix(),
|
transform.compute_matrix(),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
@@ -207,8 +215,7 @@ fn gen_model_parts(
|
|||||||
entity: OnceLock::new(),
|
entity: OnceLock::new(),
|
||||||
mesh_entity: OnceLock::new(),
|
mesh_entity: OnceLock::new(),
|
||||||
path,
|
path,
|
||||||
space: spatial.clone(),
|
spatial: 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),
|
||||||
@@ -218,8 +225,8 @@ fn gen_model_parts(
|
|||||||
(spatial, model_part)
|
(spatial, model_part)
|
||||||
}
|
}
|
||||||
Some(part) => {
|
Some(part) => {
|
||||||
part.space.set_spatial_parent(&parent_spatial).unwrap();
|
part.spatial.set_spatial_parent(&parent_spatial).unwrap();
|
||||||
(part.space.clone(), part.clone())
|
(part.spatial.clone(), part.clone())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let aabb = Aabb::enclosing(
|
let aabb = Aabb::enclosing(
|
||||||
@@ -236,13 +243,17 @@ fn gen_model_parts(
|
|||||||
)
|
)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
_ = spatial.bounding_box_calc.set(move |n| {
|
_ = spatial.bounding_box_calc.set(move |n| {
|
||||||
n.get_aspect::<ModelPart>()
|
Box::pin(async {
|
||||||
.ok()
|
n.get_aspect::<ModelPart>()
|
||||||
.and_then(|v| v.bounds.get().copied())
|
.ok()
|
||||||
.unwrap_or_default()
|
.and_then(|v| v.bounds.get().copied())
|
||||||
|
.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
|
||||||
@@ -251,14 +262,20 @@ 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.into());
|
_ = model_part.entity.set(entity_handle);
|
||||||
_ = model_part.mesh_entity.set(mesh_entity.into());
|
_ = model_part.mesh_entity.set(EntityHandle::new(mesh_entity));
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,8 +332,6 @@ 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) {
|
||||||
@@ -454,8 +469,7 @@ pub struct ModelPart {
|
|||||||
entity: OnceLock<EntityHandle>,
|
entity: OnceLock<EntityHandle>,
|
||||||
mesh_entity: OnceLock<EntityHandle>,
|
mesh_entity: OnceLock<EntityHandle>,
|
||||||
path: String,
|
path: String,
|
||||||
space: Arc<Spatial>,
|
spatial: 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,
|
||||||
@@ -525,6 +539,8 @@ 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>> {
|
||||||
@@ -541,6 +557,18 @@ 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))
|
||||||
@@ -570,7 +598,6 @@ 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(
|
||||||
@@ -583,8 +610,7 @@ impl Model {
|
|||||||
entity: OnceLock::new(),
|
entity: OnceLock::new(),
|
||||||
mesh_entity: OnceLock::new(),
|
mesh_entity: OnceLock::new(),
|
||||||
path: part_path,
|
path: part_path,
|
||||||
space: spatial,
|
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),
|
||||||
@@ -609,7 +635,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.space.node().unwrap(),
|
&part.spatial.node().unwrap(),
|
||||||
&calling_client,
|
&calling_client,
|
||||||
id,
|
id,
|
||||||
MODEL_PART_ASPECT_ALIAS_INFO.clone(),
|
MODEL_PART_ASPECT_ALIAS_INFO.clone(),
|
||||||
@@ -620,6 +646,16 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
72
src/nodes/drawable/sky.rs
Normal file
72
src/nodes/drawable/sky.rs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,18 +11,17 @@ use crate::{
|
|||||||
},
|
},
|
||||||
nodes::{
|
nodes::{
|
||||||
Node,
|
Node,
|
||||||
drawable::XAlign,
|
drawable::{TextFit, XAlign},
|
||||||
spatial::{Spatial, SpatialNode},
|
spatial::{Spatial, SpatialNode},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use bevy::{platform::collections::HashMap, prelude::*, render::mesh::MeshAabb};
|
use bevy::{platform::collections::HashMap, prelude::*};
|
||||||
use bevy_mesh_text_3d::{
|
use bevy_mesh_text_3d::{
|
||||||
Align, Attrs, MeshTextPlugin, Settings as FontSettings, generate_meshes,
|
Align, Attrs, HorizontalAnchorPoint, MeshTextPlugin, Settings as FontSettings, VerticalAlign,
|
||||||
text_glyphs::TextGlyphs,
|
VerticalAnchorPoint, generate_meshes,
|
||||||
};
|
};
|
||||||
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};
|
||||||
|
|
||||||
@@ -33,8 +32,7 @@ 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
|
||||||
// 1.0 for font size in meters
|
app.add_plugins(MeshTextPlugin);
|
||||||
app.add_plugins(MeshTextPlugin::new(1.0));
|
|
||||||
app.world_mut()
|
app.world_mut()
|
||||||
.resource_mut::<FontSettings>()
|
.resource_mut::<FontSettings>()
|
||||||
.font_system
|
.font_system
|
||||||
@@ -72,21 +70,33 @@ 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.x);
|
let max_height = style.bounds.as_ref().map(|v| v.bounds.y);
|
||||||
let (width, _height) =
|
let horizontal_anchor_point = style
|
||||||
text_glyphs.measure(max_width, max_height, &mut font_settings.font_system);
|
.bounds
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| match v.anchor_align_x {
|
||||||
|
XAlign::Left => HorizontalAnchorPoint::Left,
|
||||||
|
XAlign::Center => HorizontalAnchorPoint::Middle,
|
||||||
|
XAlign::Right => HorizontalAnchorPoint::Right,
|
||||||
|
})
|
||||||
|
.unwrap_or(HorizontalAnchorPoint::Middle);
|
||||||
|
let vertical_anchor_point = style
|
||||||
|
.bounds
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| match v.anchor_align_y {
|
||||||
|
YAlign::Top => VerticalAnchorPoint::Top,
|
||||||
|
YAlign::Center => VerticalAnchorPoint::Middle,
|
||||||
|
YAlign::Bottom => VerticalAnchorPoint::Bottom,
|
||||||
|
})
|
||||||
|
.unwrap_or(VerticalAnchorPoint::Middle);
|
||||||
|
let wrap = matches!(style.bounds.as_ref().map(|v| v.fit), Some(TextFit::Wrap));
|
||||||
let 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,
|
||||||
@@ -96,8 +106,7 @@ 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,
|
||||||
// If alpha is supported on text we need to change this
|
alpha_mode: AlphaMode::Premultiplied,
|
||||||
alpha_mode: AlphaMode::Opaque,
|
|
||||||
double_sided: false,
|
double_sided: false,
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
@@ -109,55 +118,30 @@ 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,
|
line_height: style.character_height * 1.1,
|
||||||
alignment,
|
alignment,
|
||||||
max_width,
|
max_width: wrap.then_some(0).and(max_width),
|
||||||
max_height,
|
max_height: wrap.then_some(0).and(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) =
|
let Ok((char_meshes, _text_size)) =
|
||||||
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((
|
cmds.spawn((Mesh3d(v.mesh), MeshMaterial3d(v.material), v.transform))
|
||||||
Mesh3d(v.mesh),
|
.id()
|
||||||
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
|
||||||
@@ -167,7 +151,9 @@ fn spawn_text(
|
|||||||
))
|
))
|
||||||
.add_children(&letters)
|
.add_children(&letters)
|
||||||
.id();
|
.id();
|
||||||
text.entity.lock().replace(EntityHandle(entity));
|
let entity = EntityHandle::new(entity);
|
||||||
|
text.entity.lock().replace(entity.clone());
|
||||||
|
text.spatial.set_entity(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +171,7 @@ impl FontDatabaseRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use super::{TextAspect, TextStyle, model::MaterialRegistry};
|
use super::{TextAspect, TextStyle, YAlign, model::MaterialRegistry};
|
||||||
|
|
||||||
static TEXT_REGISTRY: Registry<Text> = Registry::new();
|
static TEXT_REGISTRY: Registry<Text> = Registry::new();
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ 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);
|
||||||
|
|||||||
@@ -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()
|
||||||
if !handler_field_node.enabled() {
|
&& !handler_field_node.enabled()
|
||||||
continue;
|
{
|
||||||
}
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let ser_span = debug_span!("serializing input").entered();
|
let ser_span = debug_span!("serializing input").entered();
|
||||||
|
|||||||
@@ -77,12 +77,12 @@ impl CameraItem {
|
|||||||
_message: Message,
|
_message: Message,
|
||||||
response: MethodResponseSender,
|
response: MethodResponseSender,
|
||||||
) {
|
) {
|
||||||
response.wrap_sync(move || {
|
response.wrap(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(())?.into())
|
Ok(serialize(())?)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
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::core::error::Result;
|
use crate::nodes::{
|
||||||
use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO;
|
Aspect, AspectIdentifier,
|
||||||
use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO;
|
items::{ITEM_ACCEPTOR_ASPECT_ALIAS_INFO, ITEM_ASPECT_ALIAS_INFO, ITEM_UI_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::{
|
||||||
|
|||||||
@@ -126,18 +126,16 @@ 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
|
spatial.visible()
|
||||||
.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() {
|
||||||
@@ -209,11 +207,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(
|
||||||
@@ -228,7 +226,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);
|
||||||
@@ -303,7 +301,6 @@ 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>,
|
||||||
@@ -337,7 +334,6 @@ 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>()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,21 @@ 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;
|
use parking_lot::{Mutex, RwLock};
|
||||||
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};
|
||||||
@@ -27,68 +30,60 @@ impl Plugin for SpatialNodePlugin {
|
|||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
update_spatial_nodes.before(TransformSystem::TransformPropagate),
|
(spawn_spatial_nodes, update_spatial_nodes)
|
||||||
|
.chain()
|
||||||
|
.before(TransformSystem::TransformPropagate),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_spatial_nodes(
|
fn spawn_spatial_nodes(mut cmds: Commands) {
|
||||||
mut query: Query<(
|
for spatial in SPATIAL_REGISTRY
|
||||||
&mut BevyTransform,
|
.get_valid_contents()
|
||||||
&SpatialNode,
|
.into_iter()
|
||||||
Option<&ChildOf>,
|
.filter(|v| v.entity.read().is_none())
|
||||||
&mut Visibility,
|
{
|
||||||
)>,
|
let entity = cmds
|
||||||
parent_query: Query<&GlobalTransform>,
|
.spawn((SpatialNode(Arc::downgrade(&spatial)), Name::new("Spatial")))
|
||||||
) {
|
.id();
|
||||||
query
|
spatial.set_entity(EntityHandle::new(entity));
|
||||||
.par_iter_mut()
|
}
|
||||||
.for_each(|(mut transform, spatial_node, child_of, mut vis)| {
|
|
||||||
let _span = debug_span!("updating spatial node").entered();
|
|
||||||
let Some(spatial) = spatial_node.0.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if spatial
|
|
||||||
.node()
|
|
||||||
.is_some_and(|v| !v.enabled.load(Ordering::Relaxed))
|
|
||||||
{
|
|
||||||
if !matches!(*vis, Visibility::Hidden) {
|
|
||||||
*vis = Visibility::Hidden;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mat4 =
|
|
||||||
debug_span!("getting global transform").in_scope(|| spatial.global_transform());
|
|
||||||
let (scale, _, _) = mat4.to_scale_rotation_translation();
|
|
||||||
match (*vis, scale == Vec3::ZERO) {
|
|
||||||
(Visibility::Inherited | Visibility::Visible, true) => {
|
|
||||||
*vis = Visibility::Hidden;
|
|
||||||
}
|
|
||||||
(Visibility::Hidden, false) => {
|
|
||||||
*vis = Visibility::Inherited;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
match child_of {
|
|
||||||
Some(child_of) => {
|
|
||||||
let Ok(parent) = parent_query.get(child_of.0) else {
|
|
||||||
warn!("SpatialNode bevy Parent doesn't have global transform");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
*transform =
|
|
||||||
BevyTransform::from_matrix(parent.compute_matrix().inverse() * mat4);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
*transform = BevyTransform::from_matrix(mat4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_spatial_nodes(
|
||||||
|
mut query: Query<(&mut BevyTransform, &mut Visibility, Option<&ChildOf>)>,
|
||||||
|
mut cmds: Commands,
|
||||||
|
) {
|
||||||
|
for (entity, (transform, parent_entity)) in UPDATED_SPATIALS_NODES.lock().drain() {
|
||||||
|
let _span = debug_span!("updating spatial node").entered();
|
||||||
|
let Ok((mut bevy_transform, mut vis, parent)) = query.get_mut(entity) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
// Set visibility based on node enabled state
|
||||||
|
if let Some(transform) = transform {
|
||||||
|
*vis = Visibility::Inherited;
|
||||||
|
*bevy_transform = transform;
|
||||||
|
} else {
|
||||||
|
*vis = Visibility::Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
if parent.map(|v| v.0) != parent_entity {
|
||||||
|
match parent_entity {
|
||||||
|
Some(e) => cmds.entity(entity).insert(ChildOf(e)),
|
||||||
|
None => cmds.entity(entity).remove::<ChildOf>(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
@@ -100,9 +95,16 @@ 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())
|
||||||
@@ -123,25 +125,40 @@ static ZONEABLE_REGISTRY: Registry<Spatial> = Registry::new();
|
|||||||
|
|
||||||
pub struct Spatial {
|
pub struct Spatial {
|
||||||
pub node: Weak<Node>,
|
pub node: Weak<Node>,
|
||||||
parent: Mutex<Option<Arc<Spatial>>>,
|
entity: RwLock<Option<EntityHandle>>,
|
||||||
old_parent: Mutex<Option<Arc<Spatial>>>,
|
parent: RwLock<Option<Arc<Spatial>>>,
|
||||||
transform: Mutex<Mat4>,
|
old_parent: RwLock<Option<Arc<Spatial>>>,
|
||||||
zone: Mutex<Weak<Zone>>,
|
transform: RwLock<Mat4>,
|
||||||
|
zone: RwLock<Weak<Zone>>,
|
||||||
children: Registry<Spatial>,
|
children: Registry<Spatial>,
|
||||||
pub bounding_box_calc: OnceLock<fn(&Node) -> Aabb>,
|
pub bounding_box_calc:
|
||||||
|
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> {
|
||||||
Arc::new(Spatial {
|
let spatial = SPATIAL_REGISTRY.add(Spatial {
|
||||||
node,
|
node,
|
||||||
parent: Mutex::new(parent),
|
entity: RwLock::new(None),
|
||||||
old_parent: Mutex::new(None),
|
parent: RwLock::new(parent),
|
||||||
transform: Mutex::new(transform),
|
old_parent: RwLock::new(None),
|
||||||
zone: Mutex::new(Weak::new()),
|
transform: RwLock::new(transform),
|
||||||
|
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>,
|
||||||
@@ -172,18 +189,17 @@ 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 fn get_bounding_box(&self) -> Aabb {
|
pub async 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 = self
|
let mut bounds = match self.bounding_box_calc.get() {
|
||||||
.bounding_box_calc
|
Some(f) => f(&node).await,
|
||||||
.get()
|
None => Aabb::default(),
|
||||||
.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 = child.get_bounding_box();
|
let child_aabb = Box::pin(child.get_bounding_box()).await;
|
||||||
bounds = Aabb::enclosing([
|
bounds = Aabb::enclosing([
|
||||||
bounds.min().into(),
|
bounds.min().into(),
|
||||||
bounds.max().into(),
|
bounds.max().into(),
|
||||||
@@ -194,9 +210,47 @@ 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.lock()
|
*self.transform.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_visible(&self) -> bool {
|
||||||
|
// Check our own scale by looking at matrix column lengths
|
||||||
|
let mat = self.local_transform();
|
||||||
|
let x_scale = mat.x_axis.length_squared();
|
||||||
|
let y_scale = mat.y_axis.length_squared();
|
||||||
|
let z_scale = mat.z_axis.length_squared();
|
||||||
|
|
||||||
|
x_scale >= EPSILON.powi(2) || y_scale >= EPSILON.powi(2) || z_scale >= EPSILON.powi(2)
|
||||||
|
}
|
||||||
|
/// Check if this node or any ancestor has zero scale (for visibility culling)
|
||||||
|
pub fn visible(&self) -> bool {
|
||||||
|
// Check parent chain
|
||||||
|
if let Some(parent) = self.get_parent()
|
||||||
|
&& !parent.visible()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check our own scale by looking at matrix column lengths
|
||||||
|
self.local_visible()
|
||||||
}
|
}
|
||||||
pub fn global_transform(&self) -> Mat4 {
|
pub fn global_transform(&self) -> Mat4 {
|
||||||
let parent_transform = self
|
let parent_transform = self
|
||||||
@@ -207,7 +261,8 @@ 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.lock() = transform;
|
*self.transform.write() = transform;
|
||||||
|
self.mark_dirty();
|
||||||
}
|
}
|
||||||
pub fn set_local_transform_components(
|
pub fn set_local_transform_components(
|
||||||
&self,
|
&self,
|
||||||
@@ -215,9 +270,7 @@ impl Spatial {
|
|||||||
transform: Transform,
|
transform: Transform,
|
||||||
) {
|
) {
|
||||||
if reference_space == Some(self) {
|
if reference_space == Some(self) {
|
||||||
self.set_local_transform(
|
self.set_local_transform(transform.to_mat4(true, true, true) * self.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
|
||||||
@@ -268,7 +321,7 @@ impl Spatial {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_parent(&self) -> Option<Arc<Spatial>> {
|
fn get_parent(&self) -> Option<Arc<Spatial>> {
|
||||||
self.parent.lock().clone()
|
self.parent.read().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() {
|
||||||
@@ -276,7 +329,8 @@ impl Spatial {
|
|||||||
}
|
}
|
||||||
new_parent.children.add_raw(self);
|
new_parent.children.add_raw(self);
|
||||||
|
|
||||||
*self.parent.lock() = Some(new_parent.clone());
|
*self.parent.write() = 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<()> {
|
||||||
@@ -300,13 +354,15 @@ impl Spatial {
|
|||||||
|
|
||||||
pub(self) fn zone_distance(&self) -> f32 {
|
pub(self) fn zone_distance(&self) -> f32 {
|
||||||
self.zone
|
self.zone
|
||||||
.lock()
|
.read()
|
||||||
.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! {}
|
||||||
}
|
}
|
||||||
@@ -396,6 +452,7 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,7 +469,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();
|
let bounds = this_spatial.get_bounding_box().await;
|
||||||
|
|
||||||
Ok(BoundingBox {
|
Ok(BoundingBox {
|
||||||
center: Vec3::from(bounds.center).into(),
|
center: Vec3::from(bounds.center).into(),
|
||||||
@@ -428,7 +485,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();
|
let bb = this_spatial.get_bounding_box().await;
|
||||||
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()),
|
||||||
@@ -463,23 +520,6 @@ 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>,
|
||||||
@@ -490,7 +530,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 = parse_transform(transform, true, true, true);
|
let transform = transform.to_mat4(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(())
|
||||||
@@ -504,7 +544,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 = parse_transform(transform, true, true, false);
|
let transform = transform.to_mat4(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()?;
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ pub fn capture(spatial: &Arc<Spatial>, zone: &Arc<Zone>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
release(spatial);
|
release(spatial);
|
||||||
*spatial.old_parent.lock() = spatial.get_parent();
|
*spatial.old_parent.write() = spatial.get_parent();
|
||||||
*spatial.zone.lock() = Arc::downgrade(zone);
|
*spatial.zone.write() = 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.lock().take() else {
|
let Some(old_parent) = spatial.old_parent.read().clone() 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.lock();
|
let mut spatial_zone = spatial.zone.write();
|
||||||
|
|
||||||
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());
|
||||||
|
|||||||
@@ -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,
|
resources::{OxrFrameState, Pipelined},
|
||||||
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, nodes::spatial::Spatial};
|
use crate::{DbusConnection, PreFrameWait, get_time, nodes::spatial::Spatial};
|
||||||
|
|
||||||
use super::{ObjectHandle, SpatialRef, input::mouse_pointer::FlatscreenCam};
|
use super::{ObjectHandle, SpatialRef, input::mouse_pointer::FlatscreenCam};
|
||||||
|
|
||||||
@@ -68,6 +68,7 @@ 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)
|
||||||
@@ -80,9 +81,9 @@ fn update_xr(
|
|||||||
// });
|
// });
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// this won't be correct with pipelined rendering
|
let time = get_time(pipelined.is_some(), &state);
|
||||||
let location = session
|
let location = session
|
||||||
.locate_space(&view, &ref_space, state.predicted_display_time)
|
.locate_space(&view, &ref_space, 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
|
||||||
|
|||||||
@@ -20,14 +20,13 @@ 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()
|
||||||
if !method
|
&& !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(
|
||||||
|
|||||||
@@ -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,
|
core::{client::INTERNAL_CLIENT, task},
|
||||||
nodes::{
|
nodes::{
|
||||||
Node, OwnedNode,
|
Node, OwnedNode,
|
||||||
fields::{EXPORTED_FIELDS, Field, FieldTrait, Ray},
|
fields::{EXPORTED_FIELDS, Field, FieldTrait, Ray},
|
||||||
@@ -9,6 +9,7 @@ use crate::{
|
|||||||
items::panel::KEYMAPS,
|
items::panel::KEYMAPS,
|
||||||
spatial::Spatial,
|
spatial::Spatial,
|
||||||
},
|
},
|
||||||
|
objects::FieldRef,
|
||||||
};
|
};
|
||||||
use bevy::{
|
use bevy::{
|
||||||
input::{
|
input::{
|
||||||
@@ -20,22 +21,43 @@ 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::{interfaces::FieldRefProxy, object_registry::ObjectRegistry},
|
schemas::dbus::{
|
||||||
|
ObjectInfo,
|
||||||
|
interfaces::FieldRefProxy,
|
||||||
|
list_query::{ListEvent, ObjectListQuery},
|
||||||
|
object_registry::ObjectRegistry,
|
||||||
|
query::{ObjectQuery, QueryContext, QueryEvent},
|
||||||
|
},
|
||||||
values::Datamap,
|
values::Datamap,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, Weak};
|
||||||
use tokio::task::JoinSet;
|
use tokio::sync::{Notify, mpsc, watch};
|
||||||
|
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};
|
||||||
|
|
||||||
pub struct FlatscreenInputPlugin;
|
#[derive(Clone)]
|
||||||
|
struct HandlerInfo {
|
||||||
|
handler: ObjectInfo,
|
||||||
|
field_ref: Arc<Field>,
|
||||||
|
keyboard_proxy: KeyboardHandlerProxy<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct InputEvent {
|
||||||
|
key: u32,
|
||||||
|
pressed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FlatscreenInputPlugin;
|
||||||
impl Plugin for FlatscreenInputPlugin {
|
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);
|
||||||
@@ -48,9 +70,9 @@ impl Plugin for FlatscreenInputPlugin {
|
|||||||
#[require(Camera3d)]
|
#[require(Camera3d)]
|
||||||
pub struct FlatscreenCam;
|
pub struct FlatscreenCam;
|
||||||
|
|
||||||
fn setup(mut cmds: Commands) {
|
fn setup(mut cmds: Commands, object_registry: Res<ObjectRegistryRes>) {
|
||||||
let Ok(pointer) =
|
let Ok(pointer) = MousePointer::new(object_registry.0.clone())
|
||||||
MousePointer::new().inspect_err(|err| error!("unable to create mouse pointer: {err}"))
|
.inspect_err(|err| error!("unable to create mouse pointer: {err}"))
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -159,6 +181,14 @@ 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,
|
||||||
@@ -167,9 +197,16 @@ 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() -> Result<Self> {
|
pub fn new(object_registry: Arc<ObjectRegistry>) -> 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(
|
||||||
@@ -186,6 +223,39 @@ 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,
|
||||||
@@ -193,6 +263,10 @@ 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(
|
||||||
@@ -247,7 +321,28 @@ 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| {
|
||||||
@@ -284,96 +379,145 @@ impl MousePointer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_keyboard_input(
|
async fn focus_tracking_task(
|
||||||
&mut self,
|
object_registry: Arc<ObjectRegistry>,
|
||||||
dbus_connection: &Connection,
|
focus_notify: Arc<Notify>,
|
||||||
object_registry: &ObjectRegistry,
|
spatial: Arc<Spatial>,
|
||||||
mut keyboard_input_events: EventReader<KeyboardInput>,
|
pointer: Arc<InputMethod>,
|
||||||
|
focused_handler_tx: watch::Sender<Option<HandlerInfo>>,
|
||||||
) {
|
) {
|
||||||
let keyboard_handlers = object_registry.get_objects("org.stardustxr.XKBv1");
|
info!("Focus tracking task started");
|
||||||
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();
|
|
||||||
|
|
||||||
async move {
|
// Create keyboard handler query inside the task
|
||||||
let mut closest_handler = None;
|
let mut keyboard_query = ObjectQuery::<
|
||||||
let mut closest_distance = f32::MAX;
|
(FieldRefProxy<'static>, KeyboardHandlerProxy<'static>),
|
||||||
|
_,
|
||||||
let mut join_set = JoinSet::new();
|
>::new(object_registry.clone(), ());
|
||||||
for handler in &keyboard_handlers {
|
let (keyboard_handlers, mapper) = keyboard_query.to_list_query();
|
||||||
let handler = handler.clone();
|
task::new(
|
||||||
let dbus_connection = dbus_connection.clone();
|
|| "Focus tracking mapper",
|
||||||
join_set.spawn(async move {
|
mapper.init(async |ev| match ev {
|
||||||
// TODO: refactor the whole thing so picking the keyboardhandler to send input to is separate from sending
|
ListEvent::NewMatch((field_ref, keyboard_proxy)) => {
|
||||||
timeout(Duration::from_millis(10), async {
|
info!("New keyboard handler found");
|
||||||
let field_ref = handler
|
let uid = timeout(Duration::from_millis(100), field_ref.uid())
|
||||||
.to_typed_proxy::<FieldRefProxy>(&dbus_connection)
|
|
||||||
.await
|
|
||||||
.ok()?;
|
|
||||||
let uid = field_ref.uid().await.ok()?;
|
|
||||||
Some((handler, uid))
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()?
|
||||||
.flatten()
|
.ok()?;
|
||||||
});
|
let field_node = EXPORTED_FIELDS.lock().get(&uid)?.upgrade()?;
|
||||||
|
let field = field_node.get_aspect::<Field>();
|
||||||
|
Some((field, keyboard_proxy))
|
||||||
}
|
}
|
||||||
while let Some(Ok(Some((handler, field_ref_id)))) = join_set.join_next().await {
|
ListEvent::Modified((field_ref, keyboard_proxy)) => {
|
||||||
let exported_fields = EXPORTED_FIELDS.lock();
|
let uid = timeout(Duration::from_millis(100), field_ref.uid())
|
||||||
let Some(field_ref_node) =
|
.await
|
||||||
exported_fields.get(&field_ref_id).and_then(|f| f.upgrade())
|
.ok()?
|
||||||
else {
|
.ok()?;
|
||||||
println!("didn't find a thing :(");
|
let field_node = EXPORTED_FIELDS.lock().get(&uid)?.upgrade()?;
|
||||||
continue;
|
let field = field_node.get_aspect::<Field>();
|
||||||
};
|
Some((field, keyboard_proxy))
|
||||||
// println!("still sendin stuff :)");
|
|
||||||
let Ok(field_ref) = field_ref_node.get_aspect::<Field>() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
drop(exported_fields);
|
|
||||||
|
|
||||||
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(handler);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
let Some(handler) = closest_handler else {
|
// Main focus calculation loop
|
||||||
return;
|
loop {
|
||||||
};
|
let mut closest_handler = None;
|
||||||
let Ok(keyboard_handler) = handler
|
let mut closest_distance = f32::MAX;
|
||||||
.to_typed_proxy::<KeyboardHandlerProxy>(&dbus_connection)
|
|
||||||
.await
|
// Find closest handler
|
||||||
else {
|
for (handler, (field_ref, keyboard_proxy)) in &*keyboard_handlers.iter().await {
|
||||||
return;
|
let Ok(field_ref) = field_ref else {
|
||||||
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register keymap first
|
let result = field_ref.ray_march(Ray {
|
||||||
let _ = keyboard_handler.keymap(keymap_id).await;
|
origin: vec3(0.0, 0.0, 0.0),
|
||||||
|
direction: vec3(0.0, 0.0, -1.0),
|
||||||
|
space: spatial.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
// Send key states
|
if result.deepest_point_distance > 0.0
|
||||||
for (key, state) in events.iter() {
|
&& result.min_distance < 0.05
|
||||||
let pressed = matches!(state, ButtonState::Pressed);
|
&& result.deepest_point_distance < closest_distance
|
||||||
let _ = keyboard_handler.key_state(key + 8, pressed).await;
|
{
|
||||||
|
closest_distance = result.deepest_point_distance;
|
||||||
|
closest_handler = Some(HandlerInfo {
|
||||||
|
handler: handler.clone(),
|
||||||
|
field_ref: field_ref.clone(),
|
||||||
|
keyboard_proxy: keyboard_proxy.clone(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
// Update focused handler
|
||||||
|
if let Some(ref handler_info) = closest_handler {
|
||||||
|
info!(
|
||||||
|
"Focus tracking task: Focused on handler at distance {}",
|
||||||
|
closest_distance
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
debug!("Focus tracking task: No handler in focus");
|
||||||
|
}
|
||||||
|
let _ = focused_handler_tx.send(closest_handler);
|
||||||
|
|
||||||
|
// Wait for next frame signal
|
||||||
|
focus_notify.notified().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn input_delivery_task(
|
||||||
|
dbus_connection: Connection,
|
||||||
|
mut focused_handler_rx: watch::Receiver<Option<HandlerInfo>>,
|
||||||
|
mut input_event_rx: mpsc::UnboundedReceiver<InputEvent>,
|
||||||
|
keymap_id: u64,
|
||||||
|
) {
|
||||||
|
info!("Input delivery task started");
|
||||||
|
loop {
|
||||||
|
// Handle input events
|
||||||
|
while let Some(input_event) = input_event_rx.recv().await {
|
||||||
|
info!(
|
||||||
|
"Input delivery task: Received input event key={}, pressed={}",
|
||||||
|
input_event.key, input_event.pressed
|
||||||
|
);
|
||||||
|
// Get current focused handler
|
||||||
|
let current_handler = focused_handler_rx.borrow().clone();
|
||||||
|
let Some(handler_info) = current_handler else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send input to handler using cached proxy
|
||||||
|
info!("Input delivery task: Sending to handler");
|
||||||
|
let keyboard_handler = &handler_info.keyboard_proxy;
|
||||||
|
|
||||||
|
// Register keymap first
|
||||||
|
if let Err(e) = keyboard_handler.keymap(keymap_id).await {
|
||||||
|
warn!("Input delivery task: Failed to register keymap: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send key state
|
||||||
|
if let Err(e) = keyboard_handler
|
||||||
|
.key_state(input_event.key + 8, input_event.pressed)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("Input delivery task: Failed to send key state: {}", e);
|
||||||
|
} else {
|
||||||
|
info!(
|
||||||
|
"Input delivery task: Successfully sent key {} (pressed={})",
|
||||||
|
input_event.key + 8,
|
||||||
|
input_event.pressed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,8 +529,11 @@ 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!()),
|
||||||
@@ -452,13 +599,25 @@ 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!()),
|
||||||
@@ -477,6 +636,34 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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::{
|
||||||
@@ -12,14 +13,14 @@ use crate::{
|
|||||||
input::{INPUT_HANDLER_REGISTRY, InputDataType, InputHandler, InputMethod, Tip},
|
input::{INPUT_HANDLER_REGISTRY, InputDataType, InputHandler, InputMethod, Tip},
|
||||||
spatial::Spatial,
|
spatial::Spatial,
|
||||||
},
|
},
|
||||||
objects::{ObjectHandle, SpatialRef, Tracked},
|
objects::{AsyncTracked, 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},
|
resources::{OxrFrameState, OxrInstance, Pipelined},
|
||||||
session::OxrSession,
|
session::OxrSession,
|
||||||
};
|
};
|
||||||
use bevy_mod_xr::{
|
use bevy_mod_xr::{
|
||||||
@@ -147,6 +148,7 @@ 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);
|
||||||
@@ -158,8 +160,7 @@ fn update(
|
|||||||
.sync_actions(&[ActiveActionSet::new(&actions.set)])
|
.sync_actions(&[ActiveActionSet::new(&actions.set)])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
});
|
});
|
||||||
let time = state.predicted_display_time;
|
let time = get_time(pipelined.is_some(), &state);
|
||||||
// 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);
|
||||||
@@ -268,8 +269,9 @@ pub struct OxrControllerInput {
|
|||||||
model_part: Arc<ModelPart>,
|
model_part: Arc<ModelPart>,
|
||||||
capture_manager: CaptureManager,
|
capture_manager: CaptureManager,
|
||||||
datamap: ControllerDatamap,
|
datamap: ControllerDatamap,
|
||||||
tracked: ObjectHandle<Tracked>,
|
tracked: AsyncTracked,
|
||||||
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> {
|
||||||
@@ -279,11 +281,19 @@ 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 = Tracked::new(connection, &path);
|
let tracked = AsyncTracked::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 = Model::add_to(&node, ResourceID::Direct(CURSOR_MODEL_PATH.into())).unwrap();
|
let model_node = Arc::new(Node::generate(&INTERNAL_CLIENT, true));
|
||||||
|
let model_spatial = Spatial::add_to(
|
||||||
|
&model_node,
|
||||||
|
Some(spatial.clone()),
|
||||||
|
Mat4::from_scale(Vec3::splat(0.02)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
let model =
|
||||||
|
Model::add_to(&model_node, ResourceID::Direct(CURSOR_MODEL_PATH.into())).unwrap();
|
||||||
let model_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,
|
||||||
@@ -300,6 +310,7 @@ 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))]
|
||||||
@@ -307,13 +318,7 @@ 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);
|
||||||
}
|
}
|
||||||
tokio::spawn({
|
self.tracked.set_tracked(enabled);
|
||||||
// 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,
|
||||||
@@ -355,9 +360,7 @@ impl OxrControllerInput {
|
|||||||
1.0,
|
1.0,
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
self.input
|
self.input.spatial.set_local_transform(world_transform);
|
||||||
.spatial
|
|
||||||
.set_local_transform(world_transform * Mat4::from_scale(Vec3::splat(0.02)));
|
|
||||||
}
|
}
|
||||||
let path = session
|
let path = session
|
||||||
.instance()
|
.instance()
|
||||||
@@ -366,13 +369,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)
|
||||||
if session.instance().path_to_string(path).unwrap()
|
&& let Ok(path) = session.instance().path_to_string(path)
|
||||||
== "/interaction_profiles/khr/simple_controller"
|
&& path == "/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,
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ use crate::nodes::{
|
|||||||
input::{Hand, InputMethod, Joint},
|
input::{Hand, InputMethod, Joint},
|
||||||
spatial::Spatial,
|
spatial::Spatial,
|
||||||
};
|
};
|
||||||
use crate::objects::{ObjectHandle, SpatialRef, Tracked};
|
use crate::objects::{AsyncTracked, ObjectHandle, SpatialRef, Tracked};
|
||||||
use crate::{BevyMaterial, DbusConnection, ObjectRegistryRes, PreFrameWait};
|
use crate::{BevyMaterial, DbusConnection, ObjectRegistryRes, PreFrameWait, get_time};
|
||||||
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;
|
use bevy_mod_openxr::resources::{OxrFrameState, Pipelined};
|
||||||
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};
|
use bevy_mod_xr::session::{XrPreDestroySession, XrSessionCreated, session_available};
|
||||||
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,8 +34,11 @@ 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(PostUpdate, update_hand_material);
|
app.add_systems(
|
||||||
app.add_systems(Startup, setup);
|
PostUpdate,
|
||||||
|
update_hand_material.run_if(resource_exists::<Hands>),
|
||||||
|
);
|
||||||
|
app.add_systems(Startup, setup.run_if(session_available));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn update_hands(
|
fn update_hands(
|
||||||
@@ -50,30 +53,22 @@ 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 {
|
||||||
tokio::task::spawn({
|
hands.left.tracked.set_tracked(false);
|
||||||
let left = hands.left.tracked.clone();
|
hands.right.tracked.set_tracked(false);
|
||||||
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);
|
||||||
let handle = hand.tracked.clone();
|
hand.tracked.set_tracked(false);
|
||||||
tokio::task::spawn(async move {
|
|
||||||
handle.set_tracked(false);
|
|
||||||
});
|
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
// this won't be correct with pipelined rendering
|
let time = get_time(pipelined.is_some(), &state);
|
||||||
session
|
session
|
||||||
.locate_hand_joints(tracker, &ref_space, state.predicted_display_time)
|
.locate_hand_joints(tracker, &ref_space, time)
|
||||||
.inspect_err(|err| error!("Error while locating hand joints"))
|
.inspect_err(|err| error!("Error while locating hand joints"))
|
||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
@@ -182,7 +177,7 @@ pub struct OxrHandInput {
|
|||||||
input: Arc<InputMethod>,
|
input: Arc<InputMethod>,
|
||||||
capture_manager: CaptureManager,
|
capture_manager: CaptureManager,
|
||||||
datamap: HandDatamap,
|
datamap: HandDatamap,
|
||||||
tracked: ObjectHandle<Tracked>,
|
tracked: AsyncTracked,
|
||||||
tracker: Option<openxr::HandTracker>,
|
tracker: Option<openxr::HandTracker>,
|
||||||
captured: bool,
|
captured: bool,
|
||||||
material: Handle<BevyMaterial>,
|
material: Handle<BevyMaterial>,
|
||||||
@@ -201,7 +196,7 @@ impl OxrHandInput {
|
|||||||
HandSide::Right => "right",
|
HandSide::Right => "right",
|
||||||
} + "/palm"),
|
} + "/palm"),
|
||||||
);
|
);
|
||||||
let tracked = Tracked::new(
|
let tracked = AsyncTracked::new(
|
||||||
connection,
|
connection,
|
||||||
&("/org/stardustxr/Hand/".to_string()
|
&("/org/stardustxr/Hand/".to_string()
|
||||||
+ match side {
|
+ match side {
|
||||||
@@ -243,13 +238,7 @@ 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);
|
||||||
}
|
}
|
||||||
tokio::spawn({
|
self.tracked.set_tracked(enabled);
|
||||||
// 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,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ 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;
|
||||||
@@ -43,6 +44,48 @@ 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>) {
|
||||||
|
|||||||
@@ -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,
|
resources::{OxrFrameState, Pipelined},
|
||||||
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, nodes::spatial::Spatial};
|
use crate::{DbusConnection, PreFrameWait, get_time, nodes::spatial::Spatial};
|
||||||
|
|
||||||
use super::{ObjectHandle, SpatialRef, Tracked};
|
use super::{AsyncTracked, 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 = Tracked::new(&connection, "/org/stardustxr/PlaySpace");
|
let tracked = AsyncTracked::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,26 +75,22 @@ 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(..);
|
||||||
tokio::task::spawn({
|
play_space.tracked_handle.set_tracked(false);
|
||||||
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;
|
||||||
};
|
};
|
||||||
// this won't be correct with pipelined rendering
|
let time = get_time(pipelined.is_some(), &state);
|
||||||
let location = session
|
let location = session
|
||||||
.locate_space(&stage.0, &ref_space, state.predicted_display_time)
|
.locate_space(&stage.0, &ref_space, 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(
|
||||||
@@ -103,12 +99,7 @@ fn update(
|
|||||||
| SpaceLocationFlags::ORIENTATION_VALID
|
| SpaceLocationFlags::ORIENTATION_VALID
|
||||||
| SpaceLocationFlags::ORIENTATION_TRACKED,
|
| SpaceLocationFlags::ORIENTATION_TRACKED,
|
||||||
);
|
);
|
||||||
tokio::task::spawn({
|
play_space.tracked_handle.set_tracked(is_tracked);
|
||||||
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
|
||||||
@@ -140,7 +131,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: ObjectHandle<Tracked>,
|
tracked_handle: AsyncTracked,
|
||||||
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)>>>);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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};
|
||||||
@@ -58,6 +59,7 @@ 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))
|
||||||
|
|||||||
39
src/spectator_cam.rs
Normal file
39
src/spectator_cam.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
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()
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
@@ -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,11 +8,9 @@ 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;
|
||||||
pub use waynest::server::protocol::core::wayland::wl_buffer::*;
|
use waynest::ObjectId;
|
||||||
use waynest::{
|
pub use waynest_protocols::server::core::wayland::wl_buffer::*;
|
||||||
server::{Client, Dispatcher, Result},
|
use waynest_server::{Client as _, RequestDispatcher};
|
||||||
wire::ObjectId,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BufferUsage {
|
pub struct BufferUsage {
|
||||||
@@ -41,7 +39,8 @@ pub enum BufferBacking {
|
|||||||
Dmabuf(DmabufBacking),
|
Dmabuf(DmabufBacking),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher)]
|
#[derive(Debug, RequestDispatcher)]
|
||||||
|
#[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,
|
||||||
@@ -49,8 +48,12 @@ pub struct Buffer {
|
|||||||
|
|
||||||
impl Buffer {
|
impl Buffer {
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
pub fn new(client: &mut Client, id: ObjectId, backing: BufferBacking) -> Arc<Self> {
|
pub fn new(
|
||||||
client.insert(id, Self { id, backing })
|
client: &mut Client,
|
||||||
|
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
|
||||||
@@ -62,11 +65,20 @@ 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(images),
|
BufferBacking::Shm(backing) => backing.update_tex(dmatexes, 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(),
|
||||||
@@ -81,13 +93,19 @@ impl Buffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn uses_buffer_usage(&self) -> bool {
|
pub fn uses_buffer_usage(&self) -> bool {
|
||||||
matches!(self.backing, BufferBacking::Dmabuf(_))
|
matches!(
|
||||||
|
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) -> Result<()> {
|
async fn destroy(&self, client: &mut Client, _sender_id: ObjectId) -> WaylandResult<()> {
|
||||||
|
client.remove(self.id);
|
||||||
tracing::info!("Destroying buffer {:?}", self.id);
|
tracing::info!("Destroying buffer {:?}", self.id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
pub use waynest::server::protocol::core::wayland::wl_callback::*;
|
use waynest::ObjectId;
|
||||||
use waynest::{
|
pub use waynest_protocols::server::core::wayland::wl_callback::*;
|
||||||
server::{Dispatcher, Result},
|
use waynest_server::RequestDispatcher;
|
||||||
wire::ObjectId,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher, Clone)]
|
#[derive(Debug, RequestDispatcher, 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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};
|
||||||
pub use waynest::server::protocol::core::wayland::wl_compositor::*;
|
use waynest::ObjectId;
|
||||||
use waynest::{
|
use waynest_protocols::server::core::wayland::wl_surface::WlSurface;
|
||||||
server::{
|
pub use waynest_protocols::server::core::wayland::{wl_compositor::*, wl_region::*};
|
||||||
Client, Dispatcher, Result,
|
use waynest_server::{Client as _, RequestDispatcher};
|
||||||
protocol::core::wayland::{wl_region::WlRegion, wl_surface::WlSurface},
|
|
||||||
},
|
|
||||||
wire::ObjectId,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher, Default)]
|
#[derive(Debug, waynest_server::RequestDispatcher, 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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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,46 +31,56 @@ 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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
client.insert(id, Region::default());
|
client.insert(id, Region { id })?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher, Default)]
|
#[derive(Debug, RequestDispatcher)]
|
||||||
pub struct Region {}
|
#[waynest(error = WaylandError, connection = crate::wayland::Client)]
|
||||||
|
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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_x: i32,
|
_x: i32,
|
||||||
_y: i32,
|
_y: i32,
|
||||||
_width: i32,
|
_width: i32,
|
||||||
_height: i32,
|
_height: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_x: i32,
|
_x: i32,
|
||||||
_y: i32,
|
_y: i32,
|
||||||
_width: i32,
|
_width: i32,
|
||||||
_height: i32,
|
_height: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://wayland.app/protocols/wayland#wl_region:request:destroy
|
/// https://wayland.app/protocols/wayland#wl_region:request:destroy
|
||||||
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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,26 @@
|
|||||||
|
use crate::wayland::{Client, WaylandResult};
|
||||||
use std::os::fd::OwnedFd;
|
use std::os::fd::OwnedFd;
|
||||||
use waynest::{
|
use waynest::ObjectId;
|
||||||
server::{
|
use waynest_protocols::server::core::wayland::{
|
||||||
Client, Dispatcher, Result,
|
wl_data_device::*, wl_data_device_manager::*, wl_data_offer::WlDataOffer, wl_data_source::*,
|
||||||
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, Dispatcher)]
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
client.insert(id, DataSource);
|
client.insert(id, DataSource { id })?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,116 +30,137 @@ impl WlDataDeviceManager for DataDeviceManager {
|
|||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
_seat: ObjectId,
|
_seat: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
client.insert(id, DataDevice);
|
client.insert(id, DataDevice)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher)]
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
pub struct DataSource;
|
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
|
||||||
|
pub struct DataSource {
|
||||||
|
id: ObjectId,
|
||||||
|
}
|
||||||
impl WlDataSource for DataSource {
|
impl WlDataSource for DataSource {
|
||||||
async fn send(
|
type Connection = Client;
|
||||||
&self,
|
|
||||||
_client: &mut Client,
|
|
||||||
_sender_id: ObjectId,
|
|
||||||
_mime_type: String,
|
|
||||||
_fd: OwnedFd,
|
|
||||||
) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn offer(
|
async fn offer(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_mime_type: String,
|
_mime_type: String,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
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 set_actions(
|
async fn set_actions(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_dnd_actions: DndAction,
|
_dnd_actions: DndAction,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher)]
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[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 Client,
|
_client: &mut Self::Connection,
|
||||||
_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,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_selection(
|
async fn set_selection(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_source: Option<ObjectId>,
|
_source: Option<ObjectId>,
|
||||||
_serial: u32,
|
_serial: u32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
async fn release(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher)]
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
pub struct DataOffer;
|
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
|
||||||
|
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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_serial: u32,
|
_serial: u32,
|
||||||
_mime_type: Option<String>,
|
_mime_type: Option<String>,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
async fn receive(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_mime_type: String,
|
_mime_type: String,
|
||||||
_fd: OwnedFd,
|
_fd: OwnedFd,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
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 finish(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
async fn finish(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_actions(
|
async fn set_actions(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_dnd_actions: DndAction,
|
_dnd_actions: DndAction,
|
||||||
_preferred_action: DndAction,
|
_preferred_action: DndAction,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
nodes::items::panel::KEYMAPS,
|
nodes::items::panel::KEYMAPS,
|
||||||
wayland::{core::surface::Surface, util::ClientExt},
|
wayland::{Client, WaylandResult, core::surface::Surface, util::ClientExt},
|
||||||
};
|
};
|
||||||
use dashmap::{DashMap, DashSet};
|
use dashmap::{DashMap, DashSet};
|
||||||
use memfd::MemfdOptions;
|
use memfd::MemfdOptions;
|
||||||
@@ -9,17 +9,14 @@ use std::{
|
|||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
io::Write,
|
io::Write,
|
||||||
os::{
|
os::{
|
||||||
fd::IntoRawFd,
|
fd::{AsFd, IntoRawFd},
|
||||||
unix::io::{FromRawFd, OwnedFd},
|
unix::io::{FromRawFd, OwnedFd},
|
||||||
},
|
},
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
};
|
};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
pub use waynest::server::protocol::core::wayland::wl_keyboard::*;
|
use waynest::ObjectId;
|
||||||
use waynest::{
|
pub use waynest_protocols::server::core::wayland::wl_keyboard::*;
|
||||||
server::{Client, Dispatcher, Result},
|
|
||||||
wire::ObjectId,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct ModifierState {
|
struct ModifierState {
|
||||||
@@ -56,7 +53,8 @@ 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!() | input_event_codes::KEY_RIGHTALT!() => mods |= 8,
|
input_event_codes::KEY_LEFTALT!() => 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
|
||||||
}
|
}
|
||||||
@@ -69,7 +67,8 @@ impl ModifierState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Dispatcher)]
|
#[derive(waynest_server::RequestDispatcher)]
|
||||||
|
#[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>>,
|
||||||
@@ -89,10 +88,9 @@ impl Keyboard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_keymap(&self, client: &mut Client, keymap: &[u8]) -> Result<()> {
|
async fn send_keymap(&self, client: &mut Client, keymap: &[u8]) -> WaylandResult<()> {
|
||||||
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)?;
|
||||||
@@ -105,7 +103,7 @@ impl Keyboard {
|
|||||||
client,
|
client,
|
||||||
self.id,
|
self.id,
|
||||||
KeymapFormat::XkbV1,
|
KeymapFormat::XkbV1,
|
||||||
fd,
|
fd.as_fd(),
|
||||||
keymap.len() as u32,
|
keymap.len() as u32,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -121,7 +119,7 @@ impl Keyboard {
|
|||||||
keymap_id: u64,
|
keymap_id: u64,
|
||||||
key: u32,
|
key: u32,
|
||||||
pressed: bool,
|
pressed: bool,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
// 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;
|
||||||
@@ -224,7 +222,7 @@ impl Keyboard {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn reset(&self, client: &mut Client) -> Result<()> {
|
pub async fn reset(&self, client: &mut Client) -> WaylandResult<()> {
|
||||||
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;
|
||||||
@@ -247,8 +245,14 @@ 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(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
async fn release(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
use waynest::{
|
use crate::wayland::{Client, WaylandResult};
|
||||||
server::{Client, Dispatcher, Result},
|
use waynest::ObjectId;
|
||||||
wire::ObjectId,
|
pub use waynest_protocols::server::core::wayland::wl_output::*;
|
||||||
};
|
|
||||||
|
|
||||||
pub use waynest::server::protocol::core::wayland::wl_output::*;
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[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) -> Result<()> {
|
pub async fn advertise_outputs(&self, client: &mut Client) -> WaylandResult<()> {
|
||||||
self.geometry(
|
self.geometry(
|
||||||
client,
|
client,
|
||||||
self.id,
|
self.id,
|
||||||
@@ -26,6 +24,16 @@ 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?;
|
||||||
|
|
||||||
@@ -36,8 +44,14 @@ 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(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
async fn release(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
use crate::wayland::core::{seat::fixed_from_f32, surface::Surface};
|
use super::surface::SurfaceRole;
|
||||||
|
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;
|
||||||
pub use waynest::server::protocol::core::wayland::wl_pointer::*;
|
use waynest::ObjectId;
|
||||||
use waynest::{
|
use waynest_server::Client as _;
|
||||||
server::{Client, Dispatcher, Result},
|
|
||||||
wire::ObjectId,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Dispatcher)]
|
pub use waynest_protocols::server::core::wayland::wl_pointer::*;
|
||||||
|
|
||||||
|
#[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 {
|
||||||
@@ -22,6 +26,7 @@ impl Pointer {
|
|||||||
id,
|
id,
|
||||||
version,
|
version,
|
||||||
focused_surface: Mutex::new(Weak::new()),
|
focused_surface: Mutex::new(Weak::new()),
|
||||||
|
cursor_surface: Mutex::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +35,7 @@ impl Pointer {
|
|||||||
client: &mut Client,
|
client: &mut Client,
|
||||||
surface: Arc<Surface>,
|
surface: Arc<Surface>,
|
||||||
position: Vector2<f32>,
|
position: Vector2<f32>,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Handling pointer motion at ({}, {})",
|
"Handling pointer motion at ({}, {})",
|
||||||
position.x,
|
position.x,
|
||||||
@@ -60,8 +65,8 @@ impl Pointer {
|
|||||||
self.id,
|
self.id,
|
||||||
serial,
|
serial,
|
||||||
surface.id,
|
surface.id,
|
||||||
fixed_from_f32(position.x),
|
(position.x as f64).into(),
|
||||||
fixed_from_f32(position.y),
|
(position.y as f64).into(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -75,8 +80,8 @@ impl Pointer {
|
|||||||
client,
|
client,
|
||||||
self.id,
|
self.id,
|
||||||
0, // time
|
0, // time
|
||||||
fixed_from_f32(position.x),
|
(position.x as f64).into(),
|
||||||
fixed_from_f32(position.y),
|
(position.y as f64).into(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
if self.version >= 5 {
|
if self.version >= 5 {
|
||||||
@@ -92,7 +97,7 @@ impl Pointer {
|
|||||||
surface: Arc<Surface>,
|
surface: Arc<Surface>,
|
||||||
button: u32,
|
button: u32,
|
||||||
pressed: bool,
|
pressed: bool,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Handling pointer button {} {} on surface {:?}",
|
"Handling pointer button {} {} on surface {:?}",
|
||||||
button,
|
button,
|
||||||
@@ -121,7 +126,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>>,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Handling pointer scroll: distance={:?}, steps={:?}",
|
"Handling pointer scroll: distance={:?}, steps={:?}",
|
||||||
scroll_distance,
|
scroll_distance,
|
||||||
@@ -133,7 +138,7 @@ impl Pointer {
|
|||||||
self.id,
|
self.id,
|
||||||
0, // time
|
0, // time
|
||||||
Axis::HorizontalScroll,
|
Axis::HorizontalScroll,
|
||||||
fixed_from_f32(distance.x),
|
(distance.x as f64).into(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
self.axis(
|
self.axis(
|
||||||
@@ -141,23 +146,44 @@ impl Pointer {
|
|||||||
self.id,
|
self.id,
|
||||||
0, // time
|
0, // time
|
||||||
Axis::VerticalScroll,
|
Axis::VerticalScroll,
|
||||||
fixed_from_f32(distance.y),
|
(distance.y as f64).into(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
if self.version < 8
|
||||||
|
&& self.version >= 5
|
||||||
|
&& let Some(steps) = scroll_steps
|
||||||
|
{
|
||||||
|
self.axis_discrete(client, self.id, Axis::HorizontalScroll, steps.x as i32)
|
||||||
|
.await?;
|
||||||
|
self.axis_discrete(client, self.id, Axis::VerticalScroll, steps.y as i32)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
if self.version >= 8
|
||||||
|
&& let Some(steps) = scroll_steps
|
||||||
|
{
|
||||||
|
self.axis_value120(
|
||||||
|
client,
|
||||||
|
self.id,
|
||||||
|
Axis::HorizontalScroll,
|
||||||
|
(steps.x * 120.) as i32,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
self.axis_value120(
|
||||||
|
client,
|
||||||
|
self.id,
|
||||||
|
Axis::VerticalScroll,
|
||||||
|
(steps.y * 120.) as i32,
|
||||||
)
|
)
|
||||||
.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) -> Result<()> {
|
pub async fn reset(&self, client: &mut Client) -> WaylandResult<()> {
|
||||||
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();
|
||||||
@@ -167,24 +193,60 @@ 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 Client,
|
client: &mut Self::Connection,
|
||||||
_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,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
|
if let Some(focused_surface) = self.focused_surface.lock().await.upgrade()
|
||||||
|
&& let Some(panel_item) = focused_surface.panel_item.lock().upgrade()
|
||||||
|
{
|
||||||
|
panel_item.set_cursor(surface.and_then(|s| client.get::<Surface>(s)).map(|s| {
|
||||||
|
let size = s
|
||||||
|
.current_state()
|
||||||
|
.buffer
|
||||||
|
.map(|b| b.buffer.size())
|
||||||
|
.unwrap_or([16; 2].into());
|
||||||
|
Geometry {
|
||||||
|
origin: [hotspot_x, hotspot_y].into(),
|
||||||
|
size: [size.x as u32, size.y as u32].into(),
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
let Some(surface) = surface else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let Some(surface) = client.get::<Surface>(surface) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
surface
|
||||||
|
.try_set_role(SurfaceRole::Cursor, Error::Role)
|
||||||
|
.await?;
|
||||||
|
self.cursor_surface.lock().await.replace(surface);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://wayland.app/protocols/wayland#wl_pointer:request:release
|
/// https://wayland.app/protocols/wayland#wl_pointer:request:release
|
||||||
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
async fn release(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
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;
|
||||||
pub use waynest::server::protocol::core::wayland::wl_seat::*;
|
use waynest::ObjectId;
|
||||||
use waynest::server::{Client, Dispatcher, Result};
|
pub use waynest_protocols::server::core::wayland::wl_seat::*;
|
||||||
use waynest::wire::{Fixed, ObjectId};
|
use waynest_server::Client as _;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SeatMessage {
|
pub enum SeatMessage {
|
||||||
@@ -43,11 +45,8 @@ pub enum SeatMessage {
|
|||||||
Reset,
|
Reset,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fixed_from_f32(f: f32) -> Fixed {
|
#[derive(Default, waynest_server::RequestDispatcher)]
|
||||||
unsafe { Fixed::from_raw((f * 256.0).round() as u32) }
|
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Dispatcher)]
|
|
||||||
pub struct Seat {
|
pub struct Seat {
|
||||||
version: u32,
|
version: u32,
|
||||||
pointer: OnceLock<Arc<Pointer>>,
|
pointer: OnceLock<Arc<Pointer>>,
|
||||||
@@ -56,7 +55,7 @@ pub struct Seat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Seat {
|
impl Seat {
|
||||||
pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> Result<Self> {
|
pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> WaylandResult<Self> {
|
||||||
let seat = Self {
|
let seat = Self {
|
||||||
version,
|
version,
|
||||||
pointer: OnceLock::new(),
|
pointer: OnceLock::new(),
|
||||||
@@ -76,7 +75,11 @@ impl Seat {
|
|||||||
Ok(seat)
|
Ok(seat)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_message(&self, client: &mut Client, message: SeatMessage) -> Result<()> {
|
pub async fn handle_message(
|
||||||
|
&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() {
|
||||||
@@ -154,16 +157,22 @@ 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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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(())
|
||||||
}
|
}
|
||||||
@@ -171,12 +180,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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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(())
|
||||||
}
|
}
|
||||||
@@ -184,17 +193,21 @@ 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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
async fn release(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
|
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 _;
|
||||||
|
|
||||||
use crate::wayland::core::shm_pool::ShmPool;
|
#[derive(Debug, waynest_server::RequestDispatcher, Default)]
|
||||||
pub use waynest::server::protocol::core::wayland::wl_shm::*;
|
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
|
||||||
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(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
|
pub async fn advertise_formats(
|
||||||
|
&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?;
|
||||||
|
|
||||||
@@ -18,22 +20,28 @@ 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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
pool_id: ObjectId,
|
pool_id: ObjectId,
|
||||||
fd: OwnedFd,
|
fd: OwnedFd,
|
||||||
size: i32,
|
size: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
client.insert(pool_id, ShmPool::new(fd, size)?);
|
client.insert(pool_id, ShmPool::new(fd, size, pool_id)?)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://wayland.app/protocols/wayland#wl_shm:request:release
|
/// https://wayland.app/protocols/wayland#wl_shm:request:release
|
||||||
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
async fn release(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,64 @@
|
|||||||
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, RenderAssetUsages},
|
asset::{Assets, Handle},
|
||||||
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 std::sync::Arc;
|
use parking_lot::Mutex;
|
||||||
use waynest::server::protocol::core::wayland::wl_shm::Format;
|
use std::{
|
||||||
|
os::fd::OwnedFd,
|
||||||
|
sync::{Arc, OnceLock},
|
||||||
|
};
|
||||||
|
use tracing::debug_span;
|
||||||
|
use vulkano::{
|
||||||
|
buffer::BufferUsage,
|
||||||
|
command_buffer::{
|
||||||
|
AutoCommandBufferBuilder, CommandBufferUsage, CopyBufferToImageInfo,
|
||||||
|
PrimaryCommandBufferAbstract,
|
||||||
|
},
|
||||||
|
image::{
|
||||||
|
ImageAspect, ImageCreateFlags, ImageCreateInfo, ImageMemory, ImageTiling, ImageUsage,
|
||||||
|
sys::RawImage,
|
||||||
|
},
|
||||||
|
memory::{
|
||||||
|
DedicatedAllocation, DeviceMemory, ExternalMemoryHandleType, MemoryAllocateInfo,
|
||||||
|
ResourceMemory,
|
||||||
|
allocator::{AllocationCreateInfo, MemoryTypeFilter},
|
||||||
|
},
|
||||||
|
sync::GpuFuture,
|
||||||
|
};
|
||||||
|
use waynest_protocols::server::core::wayland::wl_shm::Format;
|
||||||
|
|
||||||
/// 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>,
|
||||||
format: Format,
|
wl_format: Format,
|
||||||
|
image: Arc<vulkano::image::Image>,
|
||||||
|
tex: OnceLock<Handle<Image>>,
|
||||||
|
pending_imported_dmatex: Mutex<Option<ImportedTexture>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for ShmBufferBacking {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("ShmBufferBacking")
|
||||||
|
.field("pool", &self.pool)
|
||||||
|
.field("offset", &self.offset)
|
||||||
|
.field("stride", &self.stride)
|
||||||
|
.field("size", &self.size)
|
||||||
|
.field("wl_format", &self.wl_format)
|
||||||
|
.field("image", &self.image)
|
||||||
|
.field("tex", &self.tex)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShmBufferBacking {
|
impl ShmBufferBacking {
|
||||||
@@ -23,70 +67,203 @@ impl ShmBufferBacking {
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
stride: usize,
|
stride: usize,
|
||||||
size: Vector2<usize>,
|
size: Vector2<usize>,
|
||||||
format: Format,
|
wl_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,
|
||||||
format,
|
wl_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(&self, images: &mut Assets<Image>) -> Option<Handle<Image>> {
|
pub fn update_tex(
|
||||||
let src_data_lock = self.pool.data_lock();
|
&self,
|
||||||
let mut src_cursor = self.offset;
|
dmatexes: &ImportedDmatexs,
|
||||||
|
images: &mut Assets<Image>,
|
||||||
// Calculate maximum cursor position needed - stride is already in bytes
|
) -> Option<Handle<Image>> {
|
||||||
let max_cursor = self.offset + (self.size.y * self.stride);
|
self.pending_imported_dmatex
|
||||||
|
.lock()
|
||||||
// Check if we have enough data
|
.take()
|
||||||
if max_cursor > src_data_lock.len() {
|
.map(|tex| dmatexes.insert_imported_dmatex(images, tex))
|
||||||
return None;
|
.inspect(|handle| {
|
||||||
}
|
_ = self.tex.set(handle.clone());
|
||||||
let data_len = self.size.x * self.size.y * 4;
|
});
|
||||||
if src_data_lock.len() != data_len {
|
self.tex.get().cloned()
|
||||||
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.format {
|
match self.wl_format {
|
||||||
Format::Xrgb8888 => false,
|
Format::Xrgb8888 => false,
|
||||||
Format::Argb8888 => true,
|
Format::Argb8888 => true,
|
||||||
_ => true,
|
_ => true,
|
||||||
|
|||||||
@@ -1,48 +1,52 @@
|
|||||||
|
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::{IntoRawFd, OwnedFd};
|
use std::os::fd::{AsRawFd, OwnedFd};
|
||||||
use waynest::{
|
use waynest::ObjectId;
|
||||||
server::{Client, Dispatcher, Result, protocol::core::wayland::wl_shm::Format},
|
use waynest_protocols::server::core::wayland::wl_shm::Format;
|
||||||
wire::ObjectId,
|
pub use waynest_protocols::server::core::wayland::wl_shm_pool::*;
|
||||||
};
|
use waynest_server::Client as _;
|
||||||
|
|
||||||
use crate::wayland::core::buffer::{Buffer, BufferBacking};
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[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) -> Result<Self> {
|
pub fn new(fd: OwnedFd, size: i32, id: ObjectId) -> WaylandResult<Self> {
|
||||||
let map = unsafe {
|
let map = unsafe {
|
||||||
MmapOptions::new()
|
MmapOptions::new()
|
||||||
.len(size as usize)
|
.len(size as usize)
|
||||||
.map_mut(fd.into_raw_fd())?
|
.map_mut(fd.as_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 Client,
|
client: &mut Self::Connection,
|
||||||
sender_id: ObjectId,
|
sender_id: ObjectId,
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
offset: i32,
|
offset: i32,
|
||||||
@@ -50,7 +54,7 @@ impl WlShmPool for ShmPool {
|
|||||||
height: i32,
|
height: i32,
|
||||||
stride: i32,
|
stride: i32,
|
||||||
format: Format,
|
format: Format,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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,
|
||||||
@@ -59,13 +63,18 @@ 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(&self, _client: &mut Client, _sender_id: ObjectId, size: i32) -> Result<()> {
|
async fn resize(
|
||||||
|
&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(())
|
||||||
@@ -73,7 +82,12 @@ 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(&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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ use super::{buffer::Buffer, callback::Callback};
|
|||||||
use crate::{
|
use crate::{
|
||||||
BevyMaterial,
|
BevyMaterial,
|
||||||
core::registry::Registry,
|
core::registry::Registry,
|
||||||
nodes::{drawable::model::ModelPart, items::panel::Geometry},
|
nodes::{
|
||||||
|
drawable::model::ModelPart,
|
||||||
|
items::panel::{Geometry, PanelItem, SurfaceId},
|
||||||
|
},
|
||||||
wayland::{
|
wayland::{
|
||||||
Message, MessageSink,
|
Client, Message, MessageSink, WaylandError, WaylandResult,
|
||||||
core::buffer::BufferUsage,
|
core::buffer::BufferUsage,
|
||||||
presentation::{MonotonicTimestamp, PresentationFeedback},
|
presentation::{MonotonicTimestamp, PresentationFeedback},
|
||||||
util::{ClientExt, DoubleBuffer},
|
util::{ClientExt, DoubleBuffer},
|
||||||
xdg::{popup::Popup, toplevel::Toplevel},
|
xdg::backend::XdgBackend,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use bevy::{
|
use bevy::{
|
||||||
@@ -19,24 +22,35 @@ 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::sync::{Arc, OnceLock};
|
use std::{
|
||||||
use waynest::{
|
fmt::Display,
|
||||||
server::{
|
sync::{Arc, OnceLock, Weak},
|
||||||
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum SurfaceRole {
|
pub enum SurfaceRole {
|
||||||
XdgToplevel(Arc<Toplevel>),
|
Cursor,
|
||||||
XDGPopup(Arc<Popup>),
|
Subsurface,
|
||||||
|
XdgToplevel,
|
||||||
|
XdgPopup,
|
||||||
|
}
|
||||||
|
impl Display for SurfaceRole {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
SurfaceRole::Cursor => f.write_str("SurfaceRole::Cursor"),
|
||||||
|
SurfaceRole::Subsurface => f.write_str("SurfaceRole::Subsurface"),
|
||||||
|
SurfaceRole::XdgToplevel => f.write_str("SurfaceRole::XdgToplevel"),
|
||||||
|
SurfaceRole::XdgPopup => f.write_str("SurfaceRole::XdgPopup"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -66,18 +80,27 @@ 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 Fn(&Surface, &SurfaceState) -> bool + Send + Sync>;
|
pub type OnCommitCallback = Box<dyn FnMut(&Surface, &SurfaceState) -> bool + Send + Sync>;
|
||||||
|
#[derive(waynest_server::RequestDispatcher)]
|
||||||
#[derive(Dispatcher)]
|
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
|
||||||
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,
|
||||||
// TODO: This should probably be a OnceLock? wayland doesn't support changing the surface role
|
pub role: OnceLock<SurfaceRole>,
|
||||||
pub role: Mutex<Option<SurfaceRole>>,
|
pub panel_item: Mutex<Weak<PanelItem<XdgBackend>>>,
|
||||||
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>>>,
|
||||||
@@ -85,6 +108,8 @@ 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)
|
||||||
@@ -92,6 +117,8 @@ 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,23 +127,50 @@ 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: Mutex::new(None),
|
role: OnceLock::new(),
|
||||||
|
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 pending_state(&self) -> parking_lot::MutexGuard<'_, DoubleBuffer<SurfaceState>> {
|
pub fn state_lock(&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: Fn(&Surface, &SurfaceState) -> bool + Send + Sync + 'static>(
|
pub fn add_commit_handler<F: FnMut(&Surface, &SurfaceState) -> bool + Send + Sync + 'static>(
|
||||||
&self,
|
&self,
|
||||||
handler: F,
|
handler: F,
|
||||||
) {
|
) {
|
||||||
@@ -187,8 +241,9 @@ impl Surface {
|
|||||||
}
|
}
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
pub fn frame_event(&self) {
|
pub fn frame_event(&self) {
|
||||||
for callback in self.state.lock().current.frame_callbacks.drain(..) {
|
let callbacks = std::mem::take(&mut *self.frame_callbacks.lock());
|
||||||
let _ = self.message_sink.send(Message::Frame(callback));
|
if !callbacks.is_empty() {
|
||||||
|
let _ = self.message_sink.send(Message::Frame(callbacks));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// pub fn size(&self) -> Option<Vector2<u32>> {
|
// pub fn size(&self) -> Option<Vector2<u32>> {
|
||||||
@@ -200,7 +255,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 Client) -> Result<()> {
|
// pub async fn release_old_buffer(&self, client: &mut Self::Connection) -> Result<()> {
|
||||||
// let (old_buffer, object) = {
|
// let (old_buffer, object) = {
|
||||||
// let lock = self.state.lock();
|
// let lock = self.state.lock();
|
||||||
|
|
||||||
@@ -230,13 +285,11 @@ impl Surface {
|
|||||||
display_timestamp: MonotonicTimestamp,
|
display_timestamp: MonotonicTimestamp,
|
||||||
refresh_cycle: u64,
|
refresh_cycle: u64,
|
||||||
) {
|
) {
|
||||||
self.message_sink
|
let _ = self.message_sink.send(Message::SendPresentationFeedback {
|
||||||
.send(Message::SendPresentationFeedback {
|
surface: self.clone(),
|
||||||
surface: self.clone(),
|
display_timestamp,
|
||||||
display_timestamp,
|
refresh_cycle,
|
||||||
refresh_cycle,
|
});
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
@@ -245,20 +298,16 @@ impl Surface {
|
|||||||
client: &mut Client,
|
client: &mut Client,
|
||||||
display_timestamp: MonotonicTimestamp,
|
display_timestamp: MonotonicTimestamp,
|
||||||
refresh_cycle: u64,
|
refresh_cycle: u64,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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 {
|
||||||
feedback
|
if let Some(display_id) = client.display().output.get().map(|display| display.id) {
|
||||||
.sync_output(
|
feedback.sync_output(client, feedback.0, display_id).await?;
|
||||||
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
|
||||||
@@ -279,16 +328,18 @@ 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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
buffer: Option<ObjectId>,
|
buffer: Option<ObjectId>,
|
||||||
_x: i32,
|
_x: i32,
|
||||||
_y: i32,
|
_y: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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));
|
||||||
@@ -304,13 +355,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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_x: i32,
|
_x: i32,
|
||||||
_y: i32,
|
_y: i32,
|
||||||
_width: i32,
|
_width: i32,
|
||||||
_height: i32,
|
_height: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,11 +369,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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
callback_id: ObjectId,
|
callback_id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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(())
|
||||||
}
|
}
|
||||||
@@ -331,10 +382,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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_region: Option<ObjectId>,
|
_region: Option<ObjectId>,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
// 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(())
|
||||||
}
|
}
|
||||||
@@ -343,22 +394,42 @@ 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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_region: Option<ObjectId>,
|
_region: Option<ObjectId>,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
// 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(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
async fn commit(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
|
// we want the upload to complete before we give the image to bevy
|
||||||
|
let buffer_option = self
|
||||||
|
.state_lock()
|
||||||
|
.pending
|
||||||
|
.buffer
|
||||||
|
.as_ref()
|
||||||
|
.map(|b| b.buffer.clone());
|
||||||
|
if let Some(buffer) = buffer_option {
|
||||||
|
tokio::task::spawn_blocking(move || buffer.on_commit())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
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(|f| (f)(self, ¤t_state));
|
handlers.retain_mut(|f| (f)(self, ¤t_state));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,10 +437,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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_transform: Transform,
|
_transform: Transform,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
// 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(())
|
||||||
}
|
}
|
||||||
@@ -378,10 +449,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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
scale: i32,
|
scale: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
self.state.lock().pending.density = scale as f32;
|
self.state.lock().pending.density = scale as f32;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -390,13 +461,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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_x: i32,
|
_x: i32,
|
||||||
_y: i32,
|
_y: i32,
|
||||||
_width: i32,
|
_width: i32,
|
||||||
_height: i32,
|
_height: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,23 +475,27 @@ 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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_x: i32,
|
_x: i32,
|
||||||
_y: i32,
|
_y: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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(&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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Surface {
|
impl Drop for Surface {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.role.lock().take();
|
self.role.take();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
use crate::wayland::core::surface::Surface;
|
use crate::wayland::{Client, WaylandResult, core::surface::Surface};
|
||||||
use mint::Vector2;
|
use mint::Vector2;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
pub use waynest::server::protocol::core::wayland::wl_touch::*;
|
use waynest::ObjectId;
|
||||||
use waynest::{
|
pub use waynest_protocols::server::core::wayland::wl_touch::*;
|
||||||
server::{Client, Dispatcher, Result},
|
|
||||||
wire::ObjectId,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::seat::fixed_from_f32;
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[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(
|
||||||
@@ -18,7 +14,7 @@ impl Touch {
|
|||||||
surface: Arc<Surface>,
|
surface: Arc<Surface>,
|
||||||
id: u32,
|
id: u32,
|
||||||
position: Vector2<f32>,
|
position: Vector2<f32>,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
let serial = client.next_event_serial();
|
let serial = client.next_event_serial();
|
||||||
self.down(
|
self.down(
|
||||||
client,
|
client,
|
||||||
@@ -27,8 +23,8 @@ impl Touch {
|
|||||||
0,
|
0,
|
||||||
surface.id,
|
surface.id,
|
||||||
id as i32,
|
id as i32,
|
||||||
fixed_from_f32(position.x),
|
(position.x as f64).into(),
|
||||||
fixed_from_f32(position.y),
|
(position.y as f64).into(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
self.frame(client, self.0).await
|
self.frame(client, self.0).await
|
||||||
@@ -39,37 +35,39 @@ impl Touch {
|
|||||||
client: &mut Client,
|
client: &mut Client,
|
||||||
id: u32,
|
id: u32,
|
||||||
position: Vector2<f32>,
|
position: Vector2<f32>,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
self.motion(
|
self.motion(
|
||||||
client,
|
client,
|
||||||
self.0,
|
self.0,
|
||||||
0,
|
0,
|
||||||
id as i32,
|
id as i32,
|
||||||
fixed_from_f32(position.x),
|
(position.x as f64).into(),
|
||||||
fixed_from_f32(position.y),
|
(position.y as f64).into(),
|
||||||
)
|
)
|
||||||
.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) -> Result<()> {
|
pub async fn handle_touch_up(&self, client: &mut Client, id: u32) -> WaylandResult<()> {
|
||||||
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) -> Result<()> {
|
pub async fn reset(&self, client: &mut Client) -> WaylandResult<()> {
|
||||||
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 waynest::server::Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: waynest::wire::ObjectId,
|
_sender_id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
|
|
||||||
use crate::wayland::{
|
use crate::wayland::{
|
||||||
MessageSink,
|
MessageSink, WaylandResult,
|
||||||
core::{
|
core::{
|
||||||
callback::{Callback, WlCallback},
|
callback::{Callback, WlCallback},
|
||||||
output::Output,
|
output::Output,
|
||||||
@@ -14,13 +14,12 @@ use std::{
|
|||||||
sync::{Arc, OnceLock},
|
sync::{Arc, OnceLock},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
pub use waynest::server::protocol::core::wayland::wl_display::*;
|
use waynest::ObjectId;
|
||||||
use waynest::{
|
pub use waynest_protocols::server::core::wayland::wl_display::*;
|
||||||
server::{Client, Dispatcher, Result},
|
use waynest_server::Client as _;
|
||||||
wire::ObjectId,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Dispatcher)]
|
#[derive(waynest_server::RequestDispatcher)]
|
||||||
|
#[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>,
|
||||||
@@ -45,13 +44,15 @@ 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 Client,
|
client: &mut Self::Connection,
|
||||||
sender_id: ObjectId,
|
sender_id: ObjectId,
|
||||||
callback_id: ObjectId,
|
callback_id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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)
|
||||||
@@ -65,11 +66,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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
registry_id: ObjectId,
|
registry_id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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?;
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,15 @@ use bevy::{
|
|||||||
};
|
};
|
||||||
use bevy_dmabuf::{
|
use bevy_dmabuf::{
|
||||||
dmatex::{Dmatex, Resolution},
|
dmatex::{Dmatex, Resolution},
|
||||||
import::{DropCallback, ImportError, ImportedDmatexs, ImportedTexture, import_texture},
|
import::{
|
||||||
|
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 tracing::info;
|
use waynest_protocols::server::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::Flags;
|
||||||
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 {
|
||||||
@@ -46,6 +47,7 @@ impl DmabufBacking {
|
|||||||
dev,
|
dev,
|
||||||
dmatex,
|
dmatex,
|
||||||
DropCallback(None),
|
DropCallback(None),
|
||||||
|
DmatexUsage::Sampling,
|
||||||
)?)),
|
)?)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -82,14 +84,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 {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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,
|
||||||
};
|
};
|
||||||
@@ -8,22 +9,19 @@ 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::{
|
use waynest::ObjectId;
|
||||||
server::{
|
use waynest_protocols::server::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::{
|
||||||
Client, Dispatcher, Result,
|
Error, Flags, ZwpLinuxBufferParamsV1,
|
||||||
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, Dispatcher)]
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[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>>,
|
||||||
@@ -41,7 +39,13 @@ impl BufferParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ZwpLinuxBufferParamsV1 for BufferParams {
|
impl ZwpLinuxBufferParamsV1 for BufferParams {
|
||||||
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
type Connection = Client;
|
||||||
|
|
||||||
|
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(())
|
||||||
}
|
}
|
||||||
@@ -49,7 +53,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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
fd: OwnedFd,
|
fd: OwnedFd,
|
||||||
plane_idx: u32,
|
plane_idx: u32,
|
||||||
@@ -57,7 +61,7 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
|
|||||||
stride: u32,
|
stride: u32,
|
||||||
modifier_hi: u32,
|
modifier_hi: u32,
|
||||||
modifier_lo: u32,
|
modifier_lo: u32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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 {:?}",
|
||||||
@@ -75,7 +79,7 @@ impl ZwpLinuxBufferParamsV1 for BufferParams {
|
|||||||
plane_idx,
|
plane_idx,
|
||||||
self.id
|
self.id
|
||||||
);
|
);
|
||||||
return Err(waynest::server::Error::MissingObject(self.id));
|
return Err(crate::wayland::WaylandError::MissingObject(self.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create plane with the provided parameters
|
// Create plane with the provided parameters
|
||||||
@@ -94,13 +98,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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
width: i32,
|
width: i32,
|
||||||
height: i32,
|
height: i32,
|
||||||
format: u32,
|
format: u32,
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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();
|
||||||
@@ -117,7 +121,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
|
||||||
@@ -128,14 +132,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 Client,
|
client: &mut Self::Connection,
|
||||||
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,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
// 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(
|
||||||
@@ -145,18 +149,15 @@ 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(())
|
||||||
|
|||||||
@@ -1,26 +1,22 @@
|
|||||||
use super::Dmabuf;
|
use super::Dmabuf;
|
||||||
use crate::wayland::vulkano_data::VULKANO_CONTEXT;
|
use crate::wayland::{Client, WaylandResult, vulkano_data::VULKANO_CONTEXT};
|
||||||
use memfd::MemfdOptions;
|
use memfd::MemfdOptions;
|
||||||
use std::{
|
use std::{
|
||||||
io::Write,
|
io::Write,
|
||||||
os::fd::{FromRawFd, IntoRawFd, OwnedFd},
|
os::fd::{AsFd as _, FromRawFd, IntoRawFd, OwnedFd},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use waynest::{
|
use waynest::ObjectId;
|
||||||
server::{
|
use waynest_protocols::server::stable::linux_dmabuf_v1::zwp_linux_dmabuf_feedback_v1::{
|
||||||
Client, Dispatcher, Result,
|
TrancheFlags, ZwpLinuxDmabufFeedbackV1,
|
||||||
protocol::stable::linux_dmabuf_v1::zwp_linux_dmabuf_feedback_v1::{
|
|
||||||
TrancheFlags, ZwpLinuxDmabufFeedbackV1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wire::ObjectId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher)]
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[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) -> Result<()> {
|
pub async fn send_params(&self, client: &mut Client, sender_id: ObjectId) -> WaylandResult<()> {
|
||||||
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?;
|
||||||
@@ -63,16 +59,18 @@ impl DmabufFeedback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
pub async fn send_format_table(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
|
pub async fn send_format_table(
|
||||||
|
&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()
|
let mfd = MemfdOptions::default().create("stardustxr-format-table")?;
|
||||||
.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)?;
|
||||||
|
|
||||||
@@ -83,20 +81,21 @@ 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(
|
self.format_table(client, sender_id, fd.as_fd(), size)
|
||||||
client,
|
.await?;
|
||||||
sender_id,
|
|
||||||
unsafe { OwnedFd::from_raw_fd(mfd.into_raw_fd()) },
|
|
||||||
size,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ZwpLinuxDmabufFeedbackV1 for DmabufFeedback {
|
impl ZwpLinuxDmabufFeedbackV1 for DmabufFeedback {
|
||||||
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
type Connection = crate::wayland::Client;
|
||||||
|
|
||||||
|
async fn destroy(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,23 +2,24 @@ pub mod buffer_backing;
|
|||||||
pub mod buffer_params;
|
pub mod buffer_params;
|
||||||
pub mod feedback;
|
pub mod feedback;
|
||||||
|
|
||||||
use std::sync::LazyLock;
|
use super::vulkano_data::VULKANO_CONTEXT;
|
||||||
|
use crate::{
|
||||||
use super::{util::ClientExt, vulkano_data::VULKANO_CONTEXT};
|
core::registry::Registry,
|
||||||
use crate::core::registry::Registry;
|
wayland::{Client, WaylandError, WaylandResult},
|
||||||
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::{
|
use waynest::ObjectId;
|
||||||
server::{
|
use waynest_protocols::server::stable::linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1;
|
||||||
Client, Dispatcher, Error, Result,
|
use waynest_server::Client as _;
|
||||||
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();
|
||||||
@@ -28,6 +29,7 @@ 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,
|
||||||
@@ -72,7 +74,8 @@ 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, Dispatcher)]
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[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>>,
|
||||||
@@ -84,7 +87,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) -> Result<Self> {
|
pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> WaylandResult<Self> {
|
||||||
let dmabuf = Self {
|
let dmabuf = Self {
|
||||||
active_params: Registry::new(),
|
active_params: Registry::new(),
|
||||||
version,
|
version,
|
||||||
@@ -118,53 +121,56 @@ impl Dmabuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ZwpLinuxDmabufV1 for Dmabuf {
|
impl ZwpLinuxDmabufV1 for Dmabuf {
|
||||||
async fn destroy(&self, _client: &mut Client, sender_id: ObjectId) -> Result<()> {
|
type Connection = crate::wayland::Client;
|
||||||
|
|
||||||
|
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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
params_id: ObjectId,
|
params_id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
// 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(¶ms);
|
self.active_params.add_raw(¶ms);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_default_feedback(
|
async fn get_default_feedback(
|
||||||
&self,
|
&self,
|
||||||
client: &mut Client,
|
client: &mut Self::Connection,
|
||||||
sender_id: ObjectId,
|
sender_id: ObjectId,
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
if self.version < 3 {
|
if self.version < 3 {
|
||||||
client
|
return Err(WaylandError::Fatal {
|
||||||
.protocol_error(
|
object_id: id,
|
||||||
sender_id,
|
code: 71,
|
||||||
id,
|
message: "Can't call get_default_feedback on version < 4 of dmabuf",
|
||||||
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 = client.insert(id, DmabufFeedback(client.get::<Dmabuf>(sender_id).unwrap()));
|
let feedback =
|
||||||
|
client.insert(id, DmabufFeedback(client.get::<Dmabuf>(sender_id).unwrap()))?;
|
||||||
feedback.send_params(client, id).await?;
|
feedback.send_params(client, id).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_surface_feedback(
|
async fn get_surface_feedback(
|
||||||
&self,
|
&self,
|
||||||
client: &mut Client,
|
client: &mut Self::Connection,
|
||||||
sender_id: ObjectId,
|
sender_id: ObjectId,
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
_surface: ObjectId,
|
_surface: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
// 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
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
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,
|
||||||
@@ -6,17 +7,16 @@ 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::{
|
use waynest::ObjectId;
|
||||||
server::{Client, Dispatcher, Result, protocol::external::drm::wl_drm::*},
|
use waynest_protocols::server::mesa::drm::wl_drm::*;
|
||||||
wire::ObjectId,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher, Default)]
|
#[derive(Debug, waynest_server::RequestDispatcher, 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) -> Result<MesaDrm> {
|
pub async fn new(client: &mut Client, id: ObjectId, version: u32) -> WaylandResult<MesaDrm> {
|
||||||
let drm = MesaDrm { version };
|
let drm = MesaDrm { version };
|
||||||
|
|
||||||
let path = {
|
let path = {
|
||||||
@@ -46,13 +46,20 @@ impl MesaDrm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl WlDrm for MesaDrm {
|
impl WlDrm for MesaDrm {
|
||||||
async fn authenticate(&self, client: &mut Client, sender_id: ObjectId, _id: u32) -> Result<()> {
|
type Connection = Client;
|
||||||
|
|
||||||
|
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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_id: ObjectId,
|
_id: ObjectId,
|
||||||
_name: u32,
|
_name: u32,
|
||||||
@@ -60,14 +67,14 @@ impl WlDrm for MesaDrm {
|
|||||||
_height: i32,
|
_height: i32,
|
||||||
_stride: u32,
|
_stride: u32,
|
||||||
_format: u32,
|
_format: u32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_id: ObjectId,
|
_id: ObjectId,
|
||||||
_name: u32,
|
_name: u32,
|
||||||
@@ -80,14 +87,14 @@ impl WlDrm for MesaDrm {
|
|||||||
_stride1: i32,
|
_stride1: i32,
|
||||||
_offset2: i32,
|
_offset2: i32,
|
||||||
_stride2: i32,
|
_stride2: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
buffer_id: ObjectId,
|
buffer_id: ObjectId,
|
||||||
name: OwnedFd,
|
name: OwnedFd,
|
||||||
@@ -100,7 +107,7 @@ impl WlDrm for MesaDrm {
|
|||||||
_stride1: i32,
|
_stride1: i32,
|
||||||
_offset2: i32,
|
_offset2: i32,
|
||||||
_stride2: i32,
|
_stride2: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
// TODO: actual error checking
|
// TODO: actual error checking
|
||||||
|
|
||||||
let _ = DmabufBacking::new(Dmatex {
|
let _ = DmabufBacking::new(Dmatex {
|
||||||
|
|||||||
@@ -5,21 +5,19 @@ 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::{
|
use crate::wayland::util::ClientExt;
|
||||||
BevyMaterial,
|
use crate::{BevyMaterial, core::task};
|
||||||
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};
|
||||||
@@ -29,46 +27,152 @@ 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::{
|
||||||
fs::{self, OpenOptions},
|
io,
|
||||||
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::StreamExt;
|
use tokio_stream::{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::{
|
use waynest::{Connection, Socket};
|
||||||
server::{
|
use waynest::{ObjectId, ProtocolError};
|
||||||
self,
|
use waynest_protocols::server::core::wayland::wl_display::WlDisplay;
|
||||||
protocol::{
|
use waynest_server::{Client as _, Listener, Store, StoreError};
|
||||||
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();
|
||||||
|
|
||||||
impl From<waynest::server::Error> for ServerError {
|
#[derive(thiserror::Error, Debug)]
|
||||||
fn from(err: waynest::server::Error) -> Self {
|
pub enum WaylandError {
|
||||||
ServerError::WaylandError(err)
|
// #[error("Listener error: {0}")]
|
||||||
|
// Listener(#[from] waynest_server::ListenerError),
|
||||||
|
#[error("I/O error: {0}")]
|
||||||
|
Io(#[from] io::Error),
|
||||||
|
#[error("Decode error: {0}")]
|
||||||
|
DecodeError(#[from] waynest::ProtocolError),
|
||||||
|
#[error("Client requested unknown global: {0}")]
|
||||||
|
UnknownGlobal(u32),
|
||||||
|
#[error("No object found with ID {0}")]
|
||||||
|
MissingObject(ObjectId),
|
||||||
|
#[error("Fatal error on object {object_id} with code {code}: {message}")]
|
||||||
|
Fatal {
|
||||||
|
object_id: ObjectId,
|
||||||
|
code: u32,
|
||||||
|
message: &'static str,
|
||||||
|
},
|
||||||
|
#[error("Memfd error: {0}")]
|
||||||
|
MemfdError(#[from] memfd::Error),
|
||||||
|
#[error("Dmabuf import error: {0}")]
|
||||||
|
DmabufImport(#[from] bevy_dmabuf::import::ImportError),
|
||||||
|
#[error("Server error: {0}")]
|
||||||
|
Server(#[from] ServerError),
|
||||||
|
#[error("Failed to Insert Object")]
|
||||||
|
FailedToInsertObject,
|
||||||
|
}
|
||||||
|
impl<T: Clone> From<StoreError<T>> for WaylandError {
|
||||||
|
fn from(_value: StoreError<T>) -> Self {
|
||||||
|
Self::FailedToInsertObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_free_wayland_socket_path() -> Option<(PathBuf, FlockLock<File>)> {
|
pin_project! {
|
||||||
|
pub struct Client {
|
||||||
|
store: Store<Client, WaylandError>,
|
||||||
|
#[pin]
|
||||||
|
connection: Socket,
|
||||||
|
next_event_serial: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Connection for Client {
|
||||||
|
type Error = WaylandError;
|
||||||
|
|
||||||
|
fn fd(&mut self) -> Result<std::os::unix::prelude::OwnedFd, <Self as Connection>::Error> {
|
||||||
|
Ok(self.connection.fd()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Stream for Client {
|
||||||
|
type Item = <Socket as Stream>::Item;
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Option<Self::Item>> {
|
||||||
|
// <Socket as Stream>::poll_next(self.project().connection, cx)
|
||||||
|
self.project().connection.poll_next(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl futures_sink::Sink<waynest::Message> for Client {
|
||||||
|
type Error = ProtocolError;
|
||||||
|
|
||||||
|
fn poll_ready(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||||
|
self.project().connection.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_send(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
item: waynest::Message,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
self.project().connection.start_send(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||||
|
self.project().connection.poll_flush(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_close(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||||
|
self.project().connection.poll_close(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Client {
|
||||||
|
fn new(unix_stream: UnixStream) -> tokio::io::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
store: Store::new(),
|
||||||
|
connection: Socket::new(unix_stream.into_std()?)?,
|
||||||
|
next_event_serial: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn next_event_serial(&mut self) -> u32 {
|
||||||
|
let prev = self.next_event_serial;
|
||||||
|
self.next_event_serial = self.next_event_serial.wrapping_add(1);
|
||||||
|
prev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl waynest_server::Client for Client {
|
||||||
|
type Store = Store<Client, WaylandError>;
|
||||||
|
|
||||||
|
fn store(&self) -> &Self::Store {
|
||||||
|
&self.store
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_mut(&mut self) -> &mut Self::Store {
|
||||||
|
&mut self.store
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_free_wayland_socket_path() -> Option<(PathBuf, File)> {
|
||||||
// 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()?;
|
||||||
@@ -78,21 +182,12 @@ pub fn get_free_wayland_socket_path() -> Option<(PathBuf, FlockLock<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"));
|
||||||
|
|
||||||
// Open lock file without truncation to preserve existing locks
|
let Ok(lock) = File::create(&socket_lock_path) else {
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Atomic mutual exclusion: fail if another process holds the lock\
|
if lock.try_lock().is_err() {
|
||||||
let Ok(lock) = lock.try_exclusive_lock() else {
|
continue;
|
||||||
continue; // Lock held by active compositor
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check for zombie sockets (file exists but nothing listening)
|
// Check for zombie sockets (file exists but nothing listening)
|
||||||
@@ -101,7 +196,7 @@ pub fn get_free_wayland_socket_path() -> Option<(PathBuf, FlockLock<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 _ = fs::remove_file(&socket_path);
|
let _ = std::fs::remove_file(&socket_path);
|
||||||
}
|
}
|
||||||
Err(_) => continue, // Transient error - conservative skip
|
Err(_) => continue, // Transient error - conservative skip
|
||||||
}
|
}
|
||||||
@@ -114,15 +209,17 @@ pub fn get_free_wayland_socket_path() -> Option<(PathBuf, FlockLock<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 {
|
||||||
Disconnect,
|
Frame(Vec<Arc<Callback>>),
|
||||||
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,
|
||||||
@@ -142,87 +239,92 @@ struct WaylandClient {
|
|||||||
abort_handle: AbortHandle,
|
abort_handle: AbortHandle,
|
||||||
}
|
}
|
||||||
impl WaylandClient {
|
impl WaylandClient {
|
||||||
pub fn from_stream(socket: UnixStream) -> Result<Self> {
|
pub fn from_stream(socket: UnixStream) -> WaylandResult<Self> {
|
||||||
let pid = socket.peer_cred().ok().and_then(|c| c.pid());
|
let pid = socket.peer_cred().ok().and_then(|c| c.pid());
|
||||||
let mut client = server::Client::new(socket)?;
|
let exe = pid.and_then(|pid| std::fs::read_link(format!("/proc/{pid}/exe")).ok());
|
||||||
|
|
||||||
|
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(
|
||||||
|| "wayland client",
|
|| format!("Wayland client \"{exe_printable}\" dispatch, pid={pid_printable}"),
|
||||||
Self::handle_client_messages(client, message_source),
|
Self::dispatch_loop(client, message_source),
|
||||||
)?
|
)?
|
||||||
.abort_handle();
|
.abort_handle();
|
||||||
|
|
||||||
Ok(WaylandClient { abort_handle })
|
Ok(WaylandClient { abort_handle })
|
||||||
}
|
}
|
||||||
async fn handle_client_messages(
|
|
||||||
mut client: server::Client,
|
async fn dispatch_loop(
|
||||||
|
mut client: Client,
|
||||||
mut render_message_rx: mpsc::UnboundedReceiver<Message>,
|
mut render_message_rx: mpsc::UnboundedReceiver<Message>,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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() => {
|
||||||
if let Some(msg) = msg {
|
let Some(msg) = msg else {
|
||||||
Self::handle_render_message(&mut client, msg).await?;
|
// Render message channel closed, end the dispatch loop
|
||||||
}
|
return Ok(());
|
||||||
|
};
|
||||||
|
Self::handle_render_message(&mut client, msg).await?;
|
||||||
}
|
}
|
||||||
// handle the next message
|
// handle the next message
|
||||||
msg = client.next_message() => {
|
msg = client.try_next() => {
|
||||||
match msg {
|
let Some(mut msg) = msg? else {
|
||||||
Ok(Some(mut msg)) => {
|
// Client disconnected, end the dispatch loop
|
||||||
if let Err(e) = client.handle_message(&mut msg).await {
|
return Ok(());
|
||||||
tracing::error!("Wayland: Error handling message: {:?}", e);
|
};
|
||||||
break;
|
if let Err(e) = client
|
||||||
}
|
.get_raw(msg.object_id())
|
||||||
}
|
.ok_or(WaylandError::MissingObject(msg.object_id()))?
|
||||||
Err(e) => {
|
.dispatch_request(&mut client, msg.object_id(), &mut msg)
|
||||||
// wayland clients really aren't nice when disconnecting properly, are they? :p
|
.await
|
||||||
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) {
|
if let WaylandError::Fatal { object_id, code, message } = e {
|
||||||
tracing::info!("Wayland: Client with pid: {pid} disconnected from server");
|
client.display().error(&mut client, ObjectId::DISPLAY, object_id, code, message.to_string()).await?;
|
||||||
} 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;
|
|
||||||
}
|
}
|
||||||
|
tracing::error!("Wayland: {e}");
|
||||||
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_render_message(
|
async fn handle_render_message(client: &mut Client, message: Message) -> WaylandResult<()> {
|
||||||
client: &mut server::Client,
|
use waynest_protocols::server::core::wayland::wl_buffer::WlBuffer;
|
||||||
message: Message,
|
use waynest_protocols::server::core::wayland::wl_callback::WlCallback;
|
||||||
) -> Result<bool, waynest::server::Error> {
|
use waynest_protocols::server::core::wayland::wl_display::WlDisplay;
|
||||||
|
use waynest_protocols::server::stable::xdg_shell::xdg_toplevel::XdgToplevel;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
Message::Disconnect => return Ok(true),
|
Message::Frame(callbacks) => {
|
||||||
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;
|
||||||
callback.done(client, callback.0, ms).await?;
|
for callback in callbacks {
|
||||||
client
|
callback.done(client, callback.0, ms).await?;
|
||||||
.get::<Display>(ObjectId::DISPLAY)
|
client
|
||||||
.unwrap()
|
.get::<Display>(ObjectId::DISPLAY)
|
||||||
.delete_id(client, ObjectId::DISPLAY, callback.0.as_raw())
|
.unwrap()
|
||||||
.await?;
|
.delete_id(client, ObjectId::DISPLAY, callback.0.as_raw())
|
||||||
client.remove(callback.0);
|
.await?;
|
||||||
|
client.remove(callback.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Message::ReleaseBuffer(buffer) => {
|
Message::ReleaseBuffer(buffer) => {
|
||||||
buffer.release(client, buffer.id).await?;
|
buffer.release(client, buffer.id).await?;
|
||||||
@@ -234,6 +336,9 @@ 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?;
|
||||||
@@ -253,7 +358,7 @@ impl WaylandClient {
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(false)
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Drop for WaylandClient {
|
impl Drop for WaylandClient {
|
||||||
@@ -264,30 +369,32 @@ impl Drop for WaylandClient {
|
|||||||
|
|
||||||
#[derive(Debug, Resource)]
|
#[derive(Debug, Resource)]
|
||||||
pub struct Wayland {
|
pub struct Wayland {
|
||||||
_lockfile: FlockLock<File>,
|
_lockfile: File,
|
||||||
abort_handle: AbortHandle,
|
abort_handle: AbortHandle,
|
||||||
}
|
}
|
||||||
impl Wayland {
|
impl Wayland {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> color_eyre::eyre::Result<Self> {
|
||||||
let (socket_path, _lockfile) =
|
let (socket_path, _lockfile) = get_free_wayland_socket_path().ok_or(WaylandError::Io(
|
||||||
get_free_wayland_socket_path().ok_or(ServerError::WaylandError(
|
std::io::ErrorKind::AddrNotAvailable.into(),
|
||||||
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 =
|
let listener = waynest_server::Listener::new_with_path(&socket_path)?;
|
||||||
server::Listener::new_with_path(&socket_path).map_err(ServerError::WaylandError)?;
|
let _ = WAYLAND_DISPLAY.set(listener.socket_path().to_path_buf());
|
||||||
|
|
||||||
let abort_handle =
|
let abort_handle = task::new(
|
||||||
task::new(|| "wayland loop", Self::handle_wayland_loop(listener))?.abort_handle();
|
|| "Wayland socket accept loop",
|
||||||
|
Self::handle_wayland_loop(listener),
|
||||||
|
)?
|
||||||
|
.abort_handle();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
_lockfile,
|
_lockfile,
|
||||||
abort_handle,
|
abort_handle,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
async fn handle_wayland_loop(mut listener: server::Listener) -> Result<()> {
|
async fn handle_wayland_loop(mut listener: Listener) -> WaylandResult<()> {
|
||||||
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 {
|
||||||
@@ -380,11 +487,39 @@ fn update_graphics(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", name = "Wayland frame", skip_all)]
|
#[instrument(level = "debug", name = "Wayland frame", skip_all)]
|
||||||
fn submit_frame_timings(mut frame_count: Local<u64>) {
|
fn submit_frame_timings(
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
use rustix::fs::Timespec;
|
use crate::wayland::WaylandResult;
|
||||||
use waynest::{
|
|
||||||
server::{
|
|
||||||
Client, Dispatcher, Result,
|
|
||||||
protocol::stable::presentation_time::{
|
|
||||||
wp_presentation::WpPresentation, wp_presentation_feedback::WpPresentationFeedback,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wire::ObjectId,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::wayland::core::surface::Surface;
|
use crate::wayland::core::surface::Surface;
|
||||||
|
use rustix::fs::Timespec;
|
||||||
|
use waynest::ObjectId;
|
||||||
|
use waynest_protocols::server::stable::presentation_time::{
|
||||||
|
wp_presentation::*, wp_presentation_feedback::*,
|
||||||
|
};
|
||||||
|
use waynest_server::Client as _;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct MonotonicTimestamp {
|
pub struct MonotonicTimestamp {
|
||||||
secs: u64,
|
secs: u64,
|
||||||
subsec_nanos: u32,
|
subsec_nanos: u32,
|
||||||
@@ -36,31 +33,49 @@ impl From<Timespec> for MonotonicTimestamp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher)]
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
pub struct Presentation;
|
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
|
||||||
|
pub struct Presentation {
|
||||||
|
id: ObjectId,
|
||||||
|
}
|
||||||
|
impl Presentation {
|
||||||
|
pub fn new(id: ObjectId) -> Self {
|
||||||
|
Self { id }
|
||||||
|
}
|
||||||
|
}
|
||||||
impl WpPresentation for Presentation {
|
impl WpPresentation for Presentation {
|
||||||
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
type Connection = crate::wayland::Client;
|
||||||
|
|
||||||
|
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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
surface: ObjectId,
|
surface: ObjectId,
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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, Dispatcher)]
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
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,
|
||||||
@@ -10,22 +12,20 @@ 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::{
|
use waynest::{NewId, ObjectId};
|
||||||
server::{
|
use waynest_protocols::server::{
|
||||||
Client, Dispatcher, Error, Result,
|
core::wayland::{wl_data_device_manager::WlDataDeviceManager, wl_registry::*},
|
||||||
protocol::{
|
mesa::drm::wl_drm::WlDrm,
|
||||||
core::wayland::{wl_data_device_manager::WlDataDeviceManager, wl_registry::*},
|
stable::{
|
||||||
external::drm::wl_drm::WlDrm,
|
linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||||
stable::{
|
presentation_time::wp_presentation::WpPresentation,
|
||||||
linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
viewporter::wp_viewporter::WpViewporter,
|
||||||
presentation_time::wp_presentation::WpPresentation,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
wire::{NewId, ObjectId},
|
|
||||||
};
|
};
|
||||||
|
use waynest_server::Client as _;
|
||||||
|
|
||||||
struct RegistryGlobals;
|
struct RegistryGlobals;
|
||||||
impl RegistryGlobals {
|
impl RegistryGlobals {
|
||||||
@@ -38,13 +38,19 @@ 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, Dispatcher, Default)]
|
#[derive(Debug, waynest_server::RequestDispatcher, 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(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
|
pub async fn advertise_globals(
|
||||||
|
&self,
|
||||||
|
client: &mut Client,
|
||||||
|
sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
self.global(
|
self.global(
|
||||||
client,
|
client,
|
||||||
sender_id,
|
sender_id,
|
||||||
@@ -126,48 +132,57 @@ 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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
name: u32,
|
name: u32,
|
||||||
new_id: NewId,
|
new_id: NewId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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 {
|
WmBase::new(new_id.object_id, new_id.version),
|
||||||
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");
|
||||||
@@ -177,7 +192,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?;
|
||||||
}
|
}
|
||||||
@@ -185,22 +200,27 @@ 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);
|
client.insert(new_id.object_id, Presentation::new(new_id.object_id))?;
|
||||||
|
}
|
||||||
|
RegistryGlobals::VIEWPORTER => {
|
||||||
|
tracing::info!("Binding wp_viewporter");
|
||||||
|
|
||||||
|
client.insert(new_id.object_id, Viewporter::new(new_id.object_id))?;
|
||||||
}
|
}
|
||||||
id => {
|
id => {
|
||||||
tracing::error!(id, "Wayland: failed to bind to registry global");
|
tracing::error!(id, "Wayland: failed to bind to registry global");
|
||||||
return Err(Error::MissingObject(unsafe { ObjectId::from_raw(name) }));
|
return Err(WaylandError::UnknownGlobal(name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,16 @@
|
|||||||
#![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::{
|
use waynest::ObjectId;
|
||||||
server::{Client, Result, protocol::core::wayland::wl_display::WlDisplay},
|
use waynest_protocols::server::core::wayland::wl_display::WlDisplay;
|
||||||
wire::ObjectId,
|
use waynest_server::{Client as _, RequestDispatcher};
|
||||||
};
|
|
||||||
|
|
||||||
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>;
|
||||||
async fn protocol_error(
|
fn try_get<D: RequestDispatcher>(&self, id: ObjectId) -> WaylandResult<Arc<D>>;
|
||||||
&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 {
|
||||||
@@ -30,19 +24,8 @@ impl ClientExt for Client {
|
|||||||
self.get::<Display>(ObjectId::DISPLAY).unwrap()
|
self.get::<Display>(ObjectId::DISPLAY).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn protocol_error(
|
fn try_get<D: RequestDispatcher>(&self, id: ObjectId) -> WaylandResult<Arc<D>> {
|
||||||
&mut self,
|
self.get::<D>(id).ok_or(WaylandError::MissingObject(id))
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
96
src/wayland/viewporter.rs
Normal file
96
src/wayland/viewporter.rs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,21 @@
|
|||||||
use super::toplevel::Toplevel;
|
use super::toplevel::Toplevel;
|
||||||
use crate::{
|
use crate::{
|
||||||
core::error::Result,
|
core::{error::Result, task},
|
||||||
nodes::{
|
nodes::{
|
||||||
drawable::model::ModelPart,
|
drawable::model::ModelPart,
|
||||||
items::panel::{Backend, Geometry, PanelItemInitData, SurfaceId, ToplevelInfo},
|
items::panel::{
|
||||||
|
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wayland::{
|
||||||
|
Message,
|
||||||
|
core::{
|
||||||
|
seat::{Seat, SeatMessage},
|
||||||
|
surface::Surface,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
wayland::{Message, core::surface::Surface},
|
|
||||||
};
|
};
|
||||||
|
use dashmap::DashMap;
|
||||||
use mint::Vector2;
|
use mint::Vector2;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Weak;
|
use std::sync::Weak;
|
||||||
@@ -14,13 +23,17 @@ use tracing;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct XdgBackend {
|
pub struct XdgBackend {
|
||||||
|
seat: Weak<Seat>,
|
||||||
toplevel: Weak<Toplevel>,
|
toplevel: Weak<Toplevel>,
|
||||||
|
children: DashMap<u64, (Weak<Surface>, ChildInfo)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XdgBackend {
|
impl XdgBackend {
|
||||||
pub fn new(toplevel: Arc<Toplevel>) -> Self {
|
pub fn new(seat: &Arc<Seat>, toplevel: &Arc<Toplevel>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
toplevel: Arc::downgrade(&toplevel),
|
seat: Arc::downgrade(seat),
|
||||||
|
toplevel: Arc::downgrade(toplevel),
|
||||||
|
children: DashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,17 +45,62 @@ impl XdgBackend {
|
|||||||
.expect("Toplevel should always be valid while XdgBackend exists")
|
.expect("Toplevel should always be valid while XdgBackend exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn surface_from_id(&self, id: SurfaceId) -> Option<Arc<Surface>> {
|
pub fn panel_item(&self) -> Option<Arc<PanelItem<XdgBackend>>> {
|
||||||
|
self.toplevel().wl_surface().panel_item.lock().upgrade()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn surface_from_id(&self, id: &SurfaceId) -> Option<Arc<Surface>> {
|
||||||
match id {
|
match id {
|
||||||
SurfaceId::Toplevel(_) => Some(self.toplevel().surface()),
|
SurfaceId::Toplevel(_) => Some(self.toplevel().wl_surface().clone()),
|
||||||
SurfaceId::Child(_) => None,
|
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) {
|
||||||
|
let Some(SurfaceId::Child(id)) = surface.surface_id.get().cloned() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.children
|
||||||
|
.insert(id, (Arc::downgrade(surface), info.clone()));
|
||||||
|
|
||||||
|
let Some(panel_item) = self.panel_item() else {
|
||||||
|
tracing::error!("Couldn't find panel item in add_child");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
panel_item.create_child(id, &info);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reposition_child(&self, surface: &Arc<Surface>, geometry: Geometry) {
|
||||||
|
let Some(SurfaceId::Child(id)) = surface.surface_id.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(mut child) = self.children.get_mut(id) {
|
||||||
|
child.1.geometry = geometry;
|
||||||
|
}
|
||||||
|
let Some(panel_item) = self.panel_item() else {
|
||||||
|
tracing::error!("Couldn't find panel item in reposition_child");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
panel_item.reposition_child(*id, &geometry);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_child(&self, surface: &Surface) {
|
||||||
|
let Some(SurfaceId::Child(id)) = surface.surface_id.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.children.remove(id);
|
||||||
|
|
||||||
|
let Some(panel_item) = self.panel_item() else {
|
||||||
|
tracing::error!("Couldn't find panel item in remove_child");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
panel_item.destroy_child(*id);
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Backend for XdgBackend {
|
impl Backend for XdgBackend {
|
||||||
fn start_data(&self) -> Result<PanelItemInitData> {
|
fn start_data(&self) -> Result<PanelItemInitData> {
|
||||||
let surface_state = self.toplevel().surface().current_state();
|
let surface_state = self.toplevel().wl_surface().current_state();
|
||||||
|
|
||||||
let size = surface_state
|
let size = surface_state
|
||||||
.buffer
|
.buffer
|
||||||
@@ -74,9 +132,20 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +153,7 @@ impl Backend for XdgBackend {
|
|||||||
fn close_toplevel(&self) {
|
fn close_toplevel(&self) {
|
||||||
let _ = self
|
let _ = self
|
||||||
.toplevel()
|
.toplevel()
|
||||||
.surface()
|
.wl_surface()
|
||||||
.message_sink
|
.message_sink
|
||||||
.send(Message::CloseToplevel(self.toplevel().clone()));
|
.send(Message::CloseToplevel(self.toplevel().clone()));
|
||||||
}
|
}
|
||||||
@@ -92,7 +161,7 @@ impl Backend for XdgBackend {
|
|||||||
fn auto_size_toplevel(&self) {
|
fn auto_size_toplevel(&self) {
|
||||||
let _ = self
|
let _ = self
|
||||||
.toplevel()
|
.toplevel()
|
||||||
.surface()
|
.wl_surface()
|
||||||
.message_sink
|
.message_sink
|
||||||
.send(Message::ResizeToplevel {
|
.send(Message::ResizeToplevel {
|
||||||
toplevel: self.toplevel().clone(),
|
toplevel: self.toplevel().clone(),
|
||||||
@@ -103,7 +172,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()
|
||||||
.surface()
|
.wl_surface()
|
||||||
.message_sink
|
.message_sink
|
||||||
.send(Message::ResizeToplevel {
|
.send(Message::ResizeToplevel {
|
||||||
toplevel: self.toplevel().clone(),
|
toplevel: self.toplevel().clone(),
|
||||||
@@ -114,7 +183,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()
|
||||||
.surface()
|
.wl_surface()
|
||||||
.message_sink
|
.message_sink
|
||||||
.send(Message::SetToplevelVisualActive {
|
.send(Message::SetToplevelVisualActive {
|
||||||
toplevel: self.toplevel().clone(),
|
toplevel: self.toplevel().clone(),
|
||||||
@@ -123,22 +192,29 @@ 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.clone()) {
|
if let Some(surface) = self.surface_from_id(surface) {
|
||||||
let _ = self.toplevel().surface().message_sink.send(Message::Seat(
|
let _ = self
|
||||||
crate::wayland::core::seat::SeatMessage::PointerMotion { surface, position },
|
.toplevel()
|
||||||
));
|
.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.clone()) {
|
if let Some(surface) = self.surface_from_id(surface) {
|
||||||
let _ = self.toplevel().surface().message_sink.send(Message::Seat(
|
let _ = self
|
||||||
crate::wayland::core::seat::SeatMessage::PointerButton {
|
.toplevel()
|
||||||
|
.wl_surface()
|
||||||
|
.message_sink
|
||||||
|
.send(Message::Seat(SeatMessage::PointerButton {
|
||||||
surface,
|
surface,
|
||||||
button,
|
button,
|
||||||
pressed,
|
pressed,
|
||||||
},
|
}));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,14 +224,16 @@ 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.clone()) {
|
if let Some(surface) = self.surface_from_id(surface) {
|
||||||
let _ = self.toplevel().surface().message_sink.send(Message::Seat(
|
let _ = self
|
||||||
crate::wayland::core::seat::SeatMessage::PointerScroll {
|
.toplevel()
|
||||||
|
.wl_surface()
|
||||||
|
.message_sink
|
||||||
|
.send(Message::Seat(SeatMessage::PointerScroll {
|
||||||
surface,
|
surface,
|
||||||
scroll_distance,
|
scroll_distance,
|
||||||
scroll_steps,
|
scroll_steps,
|
||||||
},
|
}));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,15 +243,17 @@ 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.clone()) {
|
if let Some(surface) = self.surface_from_id(surface) {
|
||||||
let _ = self.toplevel().surface().message_sink.send(Message::Seat(
|
let _ = self
|
||||||
crate::wayland::core::seat::SeatMessage::KeyboardKey {
|
.toplevel()
|
||||||
|
.wl_surface()
|
||||||
|
.message_sink
|
||||||
|
.send(Message::Seat(SeatMessage::KeyboardKey {
|
||||||
surface,
|
surface,
|
||||||
keymap_id,
|
keymap_id,
|
||||||
key,
|
key,
|
||||||
pressed,
|
pressed,
|
||||||
},
|
}));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,14 +264,16 @@ impl Backend for XdgBackend {
|
|||||||
position.x,
|
position.x,
|
||||||
position.y
|
position.y
|
||||||
);
|
);
|
||||||
if let Some(surface) = self.surface_from_id(surface.clone()) {
|
if let Some(surface) = self.surface_from_id(surface) {
|
||||||
let _ = self.toplevel().surface().message_sink.send(Message::Seat(
|
let _ = self
|
||||||
crate::wayland::core::seat::SeatMessage::TouchDown {
|
.toplevel()
|
||||||
|
.wl_surface()
|
||||||
|
.message_sink
|
||||||
|
.send(Message::Seat(SeatMessage::TouchDown {
|
||||||
surface,
|
surface,
|
||||||
id,
|
id,
|
||||||
position,
|
position,
|
||||||
},
|
}));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,25 +284,28 @@ impl Backend for XdgBackend {
|
|||||||
position.x,
|
position.x,
|
||||||
position.y
|
position.y
|
||||||
);
|
);
|
||||||
let surface = self.toplevel().surface();
|
let toplevel = self.toplevel();
|
||||||
let _ = surface.message_sink.send(Message::Seat(
|
let _ = toplevel
|
||||||
crate::wayland::core::seat::SeatMessage::TouchMove { id, position },
|
.wl_surface()
|
||||||
));
|
.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 surface = self.toplevel().surface();
|
let toplevel = self.toplevel();
|
||||||
let _ = surface.message_sink.send(Message::Seat(
|
let _ = toplevel
|
||||||
crate::wayland::core::seat::SeatMessage::TouchUp { id },
|
.wl_surface()
|
||||||
));
|
.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 surface = self.toplevel().surface();
|
let toplevel = self.toplevel();
|
||||||
let _ = surface.message_sink.send(Message::Seat(
|
let _ = toplevel
|
||||||
crate::wayland::core::seat::SeatMessage::Reset,
|
.wl_surface()
|
||||||
));
|
.message_sink
|
||||||
|
.send(Message::Seat(SeatMessage::Reset));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +1,62 @@
|
|||||||
use super::{
|
use super::{
|
||||||
backend::XdgBackend,
|
|
||||||
positioner::{Positioner, PositionerData},
|
positioner::{Positioner, PositionerData},
|
||||||
surface::Surface,
|
surface::Surface,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::nodes::items::panel::SurfaceId;
|
||||||
nodes::items::panel::{Geometry, PanelItem, SurfaceId},
|
use crate::wayland::WaylandResult;
|
||||||
wayland::util::DoubleBuffer,
|
|
||||||
};
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::{
|
use std::sync::Arc;
|
||||||
sync::{Arc, Weak, atomic::AtomicBool},
|
use waynest::ObjectId;
|
||||||
u64,
|
use waynest_protocols::server::stable::xdg_shell::xdg_popup::XdgPopup;
|
||||||
};
|
use waynest_server::Client as _;
|
||||||
use waynest::{
|
|
||||||
server::{Client, Dispatcher, Result, protocol::stable::xdg_shell::xdg_popup::XdgPopup},
|
|
||||||
wire::ObjectId,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher)]
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
|
||||||
pub struct Popup {
|
pub struct Popup {
|
||||||
id: ObjectId,
|
|
||||||
version: u32,
|
version: u32,
|
||||||
surface_id: SurfaceId,
|
pub surface: Arc<Surface>,
|
||||||
parent: Arc<Surface>,
|
|
||||||
surface: Weak<Surface>,
|
|
||||||
pub panel_item: Weak<PanelItem<XdgBackend>>,
|
|
||||||
positioner_data: Mutex<PositionerData>,
|
positioner_data: Mutex<PositionerData>,
|
||||||
geometry: DoubleBuffer<Geometry>,
|
id: ObjectId,
|
||||||
mapped: AtomicBool,
|
|
||||||
}
|
}
|
||||||
impl Popup {
|
impl Popup {
|
||||||
pub fn new(
|
pub fn new(version: u32, surface: Arc<Surface>, positioner: &Positioner, id: ObjectId) -> Self {
|
||||||
id: ObjectId,
|
let _ = surface
|
||||||
version: u32,
|
.wl_surface
|
||||||
parent: Arc<Surface>,
|
.surface_id
|
||||||
panel_item: &Arc<PanelItem<XdgBackend>>,
|
.set(SurfaceId::Child(rand::rng().random()));
|
||||||
xdg_surface: &Arc<Surface>,
|
|
||||||
positioner: &Positioner,
|
|
||||||
) -> Self {
|
|
||||||
let positioner_data = positioner.data();
|
let positioner_data = positioner.data();
|
||||||
Self {
|
Self {
|
||||||
id,
|
|
||||||
version,
|
version,
|
||||||
surface_id: SurfaceId::Child(rand::thread_rng().gen_range(0..u64::MAX)),
|
surface,
|
||||||
parent,
|
|
||||||
surface: Arc::downgrade(xdg_surface),
|
|
||||||
panel_item: Arc::downgrade(panel_item),
|
|
||||||
positioner_data: Mutex::new(positioner_data),
|
positioner_data: Mutex::new(positioner_data),
|
||||||
geometry: DoubleBuffer::new(positioner_data.infinite_geometry()),
|
id,
|
||||||
mapped: AtomicBool::new(false),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_seat: ObjectId,
|
_seat: ObjectId,
|
||||||
_serial: u32,
|
_serial: u32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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 Client,
|
client: &mut Self::Connection,
|
||||||
sender_id: ObjectId,
|
sender_id: ObjectId,
|
||||||
positioner: ObjectId,
|
positioner: ObjectId,
|
||||||
token: u32,
|
token: u32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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;
|
||||||
@@ -89,12 +73,32 @@ impl XdgPopup for Popup {
|
|||||||
geometry.size.y as i32,
|
geometry.size.y as i32,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
self.surface.upgrade().unwrap().reconfigure(client).await?;
|
self.surface.reconfigure(client).await?;
|
||||||
|
|
||||||
|
let Some(panel_item) = self.surface.wl_surface.panel_item.lock().upgrade() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
panel_item
|
||||||
|
.backend
|
||||||
|
.reposition_child(&self.surface.wl_surface, geometry);
|
||||||
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(&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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
use crate::nodes::items::panel::Geometry;
|
use crate::{nodes::items::panel::Geometry, wayland::WaylandResult};
|
||||||
use mint::Vector2;
|
use mint::Vector2;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use waynest::{
|
use waynest::ObjectId;
|
||||||
server::{
|
use waynest_protocols::server::stable::xdg_shell::xdg_positioner::*;
|
||||||
Client, Dispatcher, Result,
|
use waynest_server::Client as _;
|
||||||
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 {
|
||||||
@@ -17,11 +11,37 @@ 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,
|
||||||
@@ -67,30 +87,29 @@ impl PositionerData {
|
|||||||
.into(),
|
.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut position = anchor_point;
|
let mut geometry = Geometry {
|
||||||
|
origin: [
|
||||||
// Apply gravity
|
anchor_point.x + self.offset.x,
|
||||||
if self
|
anchor_point.y + self.offset.y,
|
||||||
.constraint_adjustment
|
]
|
||||||
.contains(ConstraintAdjustment::FlipX)
|
.into(),
|
||||||
{
|
|
||||||
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 {
|
||||||
@@ -100,6 +119,7 @@ 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(),
|
||||||
@@ -107,14 +127,17 @@ impl Default for PositionerData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher)]
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
|
||||||
pub struct Positioner {
|
pub struct Positioner {
|
||||||
data: Mutex<PositionerData>,
|
data: Mutex<PositionerData>,
|
||||||
|
id: ObjectId,
|
||||||
}
|
}
|
||||||
impl Default for Positioner {
|
impl Positioner {
|
||||||
fn default() -> Self {
|
pub fn new(id: ObjectId) -> Self {
|
||||||
Self {
|
Self {
|
||||||
data: Mutex::new(PositionerData::default()),
|
data: Mutex::new(PositionerData::default()),
|
||||||
|
id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,13 +147,15 @@ 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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_width: i32,
|
_width: i32,
|
||||||
_height: i32,
|
_height: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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;
|
||||||
@@ -139,13 +164,13 @@ impl XdgPositioner for Positioner {
|
|||||||
|
|
||||||
async fn set_anchor_rect(
|
async fn set_anchor_rect(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_x: i32,
|
_x: i32,
|
||||||
_y: i32,
|
_y: i32,
|
||||||
_width: i32,
|
_width: i32,
|
||||||
_height: i32,
|
_height: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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();
|
||||||
@@ -155,10 +180,10 @@ impl XdgPositioner for Positioner {
|
|||||||
|
|
||||||
async fn set_anchor(
|
async fn set_anchor(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_anchor: Anchor,
|
_anchor: Anchor,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
let mut data = self.data.lock();
|
let mut data = self.data.lock();
|
||||||
data.anchor = _anchor;
|
data.anchor = _anchor;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -166,19 +191,21 @@ impl XdgPositioner for Positioner {
|
|||||||
|
|
||||||
async fn set_gravity(
|
async fn set_gravity(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_gravity: Gravity,
|
gravity: Gravity,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
|
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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_constraint_adjustment: ConstraintAdjustment,
|
_constraint_adjustment: ConstraintAdjustment,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
let mut data = self.data.lock();
|
let mut data = self.data.lock();
|
||||||
data.constraint_adjustment = _constraint_adjustment;
|
data.constraint_adjustment = _constraint_adjustment;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -186,28 +213,32 @@ impl XdgPositioner for Positioner {
|
|||||||
|
|
||||||
async fn set_offset(
|
async fn set_offset(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_x: i32,
|
_x: i32,
|
||||||
_y: i32,
|
_y: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
async fn set_reactive(
|
||||||
|
&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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_parent_width: i32,
|
_parent_width: i32,
|
||||||
_parent_height: i32,
|
_parent_height: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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;
|
||||||
@@ -216,14 +247,19 @@ impl XdgPositioner for Positioner {
|
|||||||
|
|
||||||
async fn set_parent_configure(
|
async fn set_parent_configure(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_serial: u32,
|
_serial: u32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
use super::{popup::Popup, positioner::Positioner, toplevel::Mapped};
|
use super::{popup::Popup, positioner::Positioner, toplevel::MappedInner};
|
||||||
use crate::wayland::{core::surface::SurfaceRole, display::Display, xdg::toplevel::Toplevel};
|
use crate::nodes::items::panel::{ChildInfo, SurfaceId};
|
||||||
use std::sync::Arc;
|
use crate::wayland::{Client, WaylandError};
|
||||||
use std::sync::Weak;
|
use crate::wayland::{
|
||||||
pub use waynest::server::protocol::stable::xdg_shell::xdg_surface::*;
|
Message, WaylandResult, core::surface::SurfaceRole, display::Display, util::ClientExt,
|
||||||
use waynest::{
|
xdg::toplevel::Toplevel,
|
||||||
server::{Client, Dispatcher, Result},
|
|
||||||
wire::ObjectId,
|
|
||||||
};
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use waynest::ObjectId;
|
||||||
|
use waynest_protocols::server::stable::xdg_shell::xdg_popup::XdgPopup;
|
||||||
|
pub use waynest_protocols::server::stable::xdg_shell::xdg_surface::*;
|
||||||
|
use waynest_server::Client as _;
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher)]
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
|
||||||
pub struct Surface {
|
pub struct Surface {
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
version: u32,
|
version: u32,
|
||||||
wl_surface: Weak<crate::wayland::core::surface::Surface>,
|
pub wl_surface: Arc<crate::wayland::core::surface::Surface>,
|
||||||
configured: Arc<std::sync::atomic::AtomicBool>,
|
configured: Arc<std::sync::atomic::AtomicBool>,
|
||||||
}
|
}
|
||||||
impl Surface {
|
impl Surface {
|
||||||
@@ -24,84 +28,75 @@ impl Surface {
|
|||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
version,
|
version,
|
||||||
wl_surface: Arc::downgrade(&wl_surface),
|
wl_surface,
|
||||||
configured: Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
configured: Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wl_surface(&self) -> Arc<crate::wayland::core::surface::Surface> {
|
pub async fn reconfigure(&self, client: &mut Client) -> WaylandResult<()> {
|
||||||
// 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(&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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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 Client,
|
client: &mut Self::Connection,
|
||||||
sender_id: ObjectId,
|
sender_id: ObjectId,
|
||||||
toplevel_id: ObjectId,
|
toplevel_id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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,
|
||||||
surface.clone(),
|
self.wl_surface.clone(),
|
||||||
client.get::<Self>(sender_id).unwrap(),
|
client.get::<Self>(sender_id).unwrap(),
|
||||||
),
|
),
|
||||||
);
|
)?;
|
||||||
|
|
||||||
{
|
self.wl_surface
|
||||||
let mut surface_role = surface.role.lock();
|
.try_set_role(SurfaceRole::XdgToplevel, Error::AlreadyConstructed)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// A surface must not have any existing role when assigning a new one
|
let toplevel_weak = Arc::downgrade(&toplevel);
|
||||||
// "A surface must not have more than one role, and a role must not be assigned to more than one
|
let display = client.get::<Display>(ObjectId::DISPLAY).unwrap();
|
||||||
// surface at a time. However, wl_surface role-specific interfaces may reassign the role, allow
|
let seat = Arc::downgrade(display.seat.get().unwrap());
|
||||||
// a role to be destroyed, or allow multiple role-specific interfaces to share the same role."
|
let pid = display.pid;
|
||||||
// - 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();
|
||||||
surface.add_commit_handler(move |surface, state| {
|
let mut first_commit = true;
|
||||||
let Some(SurfaceRole::XdgToplevel(toplevel)) = &mut *surface.role.lock() else {
|
let message_tx = client.message_sink().clone();
|
||||||
|
self.wl_surface.add_commit_handler(move |surface, state| {
|
||||||
|
let Some(toplevel) = toplevel_weak.upgrade() else {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only proceed if configured and has valid buffer
|
if first_commit {
|
||||||
let has_valid_buffer = state
|
let _ = message_tx.send(Message::ReconfigureToplevel(toplevel.clone()));
|
||||||
.buffer
|
first_commit = false;
|
||||||
.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)
|
||||||
&& has_valid_buffer
|
&& state.has_valid_buffer()
|
||||||
{
|
{
|
||||||
mapped_lock.replace(Mapped::create(toplevel.clone(), pid));
|
let mapped_inner = MappedInner::create(&seat.upgrade().unwrap(), &toplevel, pid);
|
||||||
|
*surface.panel_item.lock() = Arc::downgrade(&mapped_inner.panel_item);
|
||||||
|
mapped_lock.replace(mapped_inner);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@@ -113,74 +108,88 @@ 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 Client,
|
client: &mut Self::Connection,
|
||||||
sender_id: ObjectId,
|
sender_id: ObjectId,
|
||||||
popup_id: ObjectId,
|
popup_id: ObjectId,
|
||||||
parent: Option<ObjectId>,
|
parent: Option<ObjectId>,
|
||||||
positioner: ObjectId,
|
positioner: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
let parent = client.get::<Surface>(parent.unwrap()).unwrap();
|
self.wl_surface
|
||||||
let panel_item = match parent.wl_surface().role.lock().as_ref().unwrap() {
|
.try_set_role(SurfaceRole::XdgPopup, Error::AlreadyConstructed)
|
||||||
SurfaceRole::XdgToplevel(toplevel) => {
|
.await?;
|
||||||
let toplevel_lock = toplevel.mapped.lock();
|
|
||||||
toplevel_lock.as_ref().unwrap()._panel_item.clone()
|
let Some(parent) = parent else {
|
||||||
}
|
return Err(WaylandError::Fatal {
|
||||||
SurfaceRole::XDGPopup(popup) => popup.panel_item.upgrade().unwrap(),
|
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 positioner = client.get::<Positioner>(positioner).unwrap();
|
||||||
|
|
||||||
let surface = client.get::<Surface>(self.id).unwrap();
|
let surface = client.get::<Surface>(self.id).unwrap();
|
||||||
|
|
||||||
let popup = client.insert(
|
let popup = client.insert(
|
||||||
popup_id,
|
popup_id,
|
||||||
Popup::new(
|
Popup::new(self.version, surface, &positioner, popup_id),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let positioner_geometry = positioner.data().infinite_geometry();
|
||||||
|
|
||||||
|
popup
|
||||||
|
.configure(
|
||||||
|
client,
|
||||||
popup_id,
|
popup_id,
|
||||||
self.version,
|
positioner_geometry.origin.x,
|
||||||
parent,
|
positioner_geometry.origin.y,
|
||||||
&panel_item,
|
positioner_geometry.size.x as i32,
|
||||||
&surface,
|
positioner_geometry.size.y as i32,
|
||||||
&positioner,
|
)
|
||||||
),
|
.await?;
|
||||||
);
|
|
||||||
|
|
||||||
{
|
|
||||||
let wl_surface = self.wl_surface();
|
|
||||||
let mut surface_role = wl_surface.role.lock();
|
|
||||||
|
|
||||||
if surface_role.is_some() {
|
|
||||||
// We should send "role" error here as per xdg_wm_base.error enum
|
|
||||||
// But we'll ignore for now
|
|
||||||
} else {
|
|
||||||
surface_role.replace(SurfaceRole::XDGPopup(popup.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let serial = client.next_event_serial();
|
let serial = client.next_event_serial();
|
||||||
self.configure(client, sender_id, serial).await?;
|
self.configure(client, sender_id, serial).await?;
|
||||||
|
|
||||||
// let pid = client.get::<Display>(ObjectId::DISPLAY).unwrap().pid;
|
let Some(SurfaceId::Child(id)) = self.wl_surface.surface_id.get() else {
|
||||||
// let configured = self.configured.clone();
|
return Ok(());
|
||||||
// surface.add_commit_handler(move |surface, state| {
|
};
|
||||||
// let Some(SurfaceRole::XDGPopup(popup)) = &mut *surface.role.lock() else {
|
let Some(parent_id) = parent.wl_surface.surface_id.get() else {
|
||||||
// return true;
|
return Ok(());
|
||||||
// };
|
};
|
||||||
|
|
||||||
// // Only proceed if configured and has valid buffer
|
let child_info = ChildInfo {
|
||||||
// let has_valid_buffer = state
|
id: *id,
|
||||||
// .buffer
|
parent: parent_id.clone(),
|
||||||
// .as_ref()
|
geometry: positioner.data().infinite_geometry(),
|
||||||
// .is_some_and(|b| b.buffer.size().x > 0 && b.buffer.size().y > 0);
|
z_order: 1,
|
||||||
|
receives_input: true,
|
||||||
|
};
|
||||||
|
|
||||||
// let mut mapped_lock = popup.mapped.lock();
|
let popup_weak = Arc::downgrade(&popup);
|
||||||
// if mapped_lock.is_none()
|
let configured = self.configured.clone();
|
||||||
// && configured.load(std::sync::atomic::Ordering::SeqCst)
|
self.wl_surface.add_commit_handler(move |surface, state| {
|
||||||
// && has_valid_buffer
|
let Some(popup) = popup_weak.upgrade() else {
|
||||||
// {
|
return true;
|
||||||
// mapped_lock.replace(Mapped::create(popup.clone(), pid));
|
};
|
||||||
// return false;
|
let Some(panel_item) = surface.panel_item.lock().upgrade() else {
|
||||||
// }
|
return true;
|
||||||
// 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
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -188,13 +197,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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_x: i32,
|
_x: i32,
|
||||||
_y: i32,
|
_y: i32,
|
||||||
_width: i32,
|
_width: i32,
|
||||||
_height: i32,
|
_height: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
// 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(())
|
||||||
@@ -203,10 +212,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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_serial: u32,
|
_serial: u32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
self.configured
|
self.configured
|
||||||
.store(true, std::sync::atomic::Ordering::SeqCst);
|
.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
use super::backend::XdgBackend;
|
use super::backend::XdgBackend;
|
||||||
use crate::{
|
use crate::{
|
||||||
nodes::{Node, items::panel::PanelItem},
|
nodes::{
|
||||||
wayland::core::surface::Surface,
|
Node,
|
||||||
|
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 std::sync::Weak;
|
use waynest::ObjectId;
|
||||||
pub use waynest::server::protocol::stable::xdg_shell::xdg_toplevel::*;
|
pub use waynest_protocols::server::stable::xdg_shell::xdg_toplevel::*;
|
||||||
use waynest::{
|
use waynest_server::Client as _;
|
||||||
server::{Client, Dispatcher, Result},
|
|
||||||
wire::ObjectId,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Mapped {
|
pub struct MappedInner {
|
||||||
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 Mapped {
|
impl MappedInner {
|
||||||
pub fn create(toplevel: Arc<Toplevel>, pid: Option<i32>) -> Self {
|
pub fn create(seat: &Arc<Seat>, 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(toplevel)), pid);
|
PanelItem::create(Box::new(XdgBackend::new(seat, toplevel)), pid);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
panel_item_node,
|
panel_item_node,
|
||||||
_panel_item,
|
panel_item,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,12 +55,12 @@ impl Default for ToplevelData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher)]
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
|
||||||
pub struct Toplevel {
|
pub struct Toplevel {
|
||||||
pub id: ObjectId,
|
pub id: ObjectId,
|
||||||
wl_surface: Weak<Surface>,
|
xdg_surface: Arc<super::surface::Surface>,
|
||||||
xdg_surface: Weak<super::surface::Surface>,
|
pub mapped: Mutex<Option<MappedInner>>,
|
||||||
pub mapped: Mutex<Option<Mapped>>,
|
|
||||||
data: Mutex<ToplevelData>,
|
data: Mutex<ToplevelData>,
|
||||||
}
|
}
|
||||||
impl Toplevel {
|
impl Toplevel {
|
||||||
@@ -66,20 +69,18 @@ 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,
|
||||||
wl_surface: Arc::downgrade(&wl_surface),
|
xdg_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 surface(&self) -> Arc<Surface> {
|
pub fn wl_surface(&self) -> &Arc<Surface> {
|
||||||
// We can safely unwrap as the surface must exist for the lifetime of the toplevel
|
&self.xdg_surface.wl_surface
|
||||||
self.wl_surface
|
|
||||||
.upgrade()
|
|
||||||
.expect("Surface was dropped before toplevel")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn title(&self) -> Option<String> {
|
pub fn title(&self) -> Option<String> {
|
||||||
@@ -102,7 +103,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.surface().current_state();
|
let state = self.wl_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 {
|
||||||
@@ -116,7 +117,7 @@ impl Toplevel {
|
|||||||
clamped
|
clamped
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn reconfigure(&self, client: &mut Client) -> Result<()> {
|
pub async fn reconfigure(&self, client: &mut Client) -> WaylandResult<()> {
|
||||||
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
|
||||||
@@ -148,20 +149,19 @@ impl Toplevel {
|
|||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
self.xdg_surface
|
self.xdg_surface.reconfigure(client).await?;
|
||||||
.upgrade()
|
Ok(())
|
||||||
.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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
parent: Option<ObjectId>,
|
parent: Option<ObjectId>,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
// 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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
title: String,
|
title: String,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
app_id: String,
|
app_id: String,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_seat: ObjectId,
|
_seat: ObjectId,
|
||||||
_serial: u32,
|
_serial: u32,
|
||||||
_x: i32,
|
_x: i32,
|
||||||
_y: i32,
|
_y: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn r#move(
|
async fn r#move(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_seat: ObjectId,
|
_seat: ObjectId,
|
||||||
_serial: u32,
|
_serial: u32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn resize(
|
async fn resize(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_seat: ObjectId,
|
_seat: ObjectId,
|
||||||
_serial: u32,
|
_serial: u32,
|
||||||
_edges: ResizeEdge,
|
_edges: ResizeEdge,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_max_size(
|
async fn set_max_size(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
width: i32,
|
width: i32,
|
||||||
height: i32,
|
height: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
self.surface().pending_state().pending.max_size = if width == 0 && height == 0 {
|
self.wl_surface().state_lock().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 Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
width: i32,
|
width: i32,
|
||||||
height: i32,
|
height: i32,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
self.surface().pending_state().pending.min_size = if width == 0 && height == 0 {
|
self.xdg_surface.wl_surface.state_lock().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,32 +273,53 @@ impl XdgToplevel for Toplevel {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_maximized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
async fn set_maximized(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unset_maximized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
async fn unset_maximized(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_fullscreen(
|
async fn set_fullscreen(
|
||||||
&self,
|
&self,
|
||||||
_client: &mut Client,
|
_client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
_output: Option<ObjectId>,
|
_output: Option<ObjectId>,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unset_fullscreen(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
async fn unset_fullscreen(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_minimized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
async fn set_minimized(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
self.mapped.lock().take();
|
self.mapped.lock().take();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,73 @@
|
|||||||
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};
|
||||||
|
|
||||||
#[derive(Debug, Dispatcher, Default)]
|
use waynest::ObjectId;
|
||||||
|
pub use waynest_protocols::server::stable::xdg_shell::xdg_wm_base::*;
|
||||||
|
use waynest_server::Client as _;
|
||||||
|
|
||||||
|
#[derive(Debug, waynest_server::RequestDispatcher)]
|
||||||
|
#[waynest(error = crate::wayland::WaylandError, connection = crate::wayland::Client)]
|
||||||
pub struct WmBase {
|
pub struct WmBase {
|
||||||
pub version: u32,
|
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 {
|
||||||
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
|
type Connection = crate::wayland::Client;
|
||||||
|
|
||||||
|
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 Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
client.insert(id, Positioner::default());
|
client.insert(id, Positioner::new(id))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_xdg_surface(
|
async fn get_xdg_surface(
|
||||||
&self,
|
&self,
|
||||||
client: &mut Client,
|
client: &mut Self::Connection,
|
||||||
_sender_id: ObjectId,
|
_sender_id: ObjectId,
|
||||||
xdg_surface_id: ObjectId,
|
xdg_surface_id: ObjectId,
|
||||||
wl_surface_id: ObjectId,
|
wl_surface_id: ObjectId,
|
||||||
) -> Result<()> {
|
) -> WaylandResult<()> {
|
||||||
let wl_surface = client
|
let wl_surface = client.try_get::<crate::wayland::core::surface::Surface>(wl_surface_id)?;
|
||||||
.get::<crate::wayland::core::surface::Surface>(wl_surface_id)
|
match wl_surface.role.get() {
|
||||||
.ok_or(waynest::server::Error::Custom(
|
None => (),
|
||||||
"can't get wayland surface id".to_string(),
|
Some(_) => {
|
||||||
))?;
|
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(&self, _client: &mut Client, _sender_id: ObjectId, _serial: u32) -> Result<()> {
|
async fn pong(
|
||||||
|
&self,
|
||||||
|
_client: &mut Self::Connection,
|
||||||
|
_sender_id: ObjectId,
|
||||||
|
_serial: u32,
|
||||||
|
) -> WaylandResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user