Integrate WayVR into wlx directly
This commit is contained in:
231
Cargo.lock
generated
231
Cargo.lock
generated
@@ -179,9 +179,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.86"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
||||
|
||||
[[package]]
|
||||
name = "appendlist"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e149dc73cd30538307e7ffa2acd3d2221148eaeed4871f246657b1c3eaa1cbd2"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
@@ -680,13 +695,26 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ead1e1514bce44c0f40e027899fbc595907fc112635bed21b3b5d975c0a5e7"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"polling",
|
||||
"rustix",
|
||||
"slab",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop-wayland-source"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
|
||||
dependencies = [
|
||||
"calloop",
|
||||
"calloop 0.13.0",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
@@ -745,6 +773,16 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "cgmath"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317"
|
||||
dependencies = [
|
||||
"approx 0.4.0",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.38"
|
||||
@@ -1323,6 +1361,15 @@ version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.0"
|
||||
@@ -1679,13 +1726,24 @@ version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
|
||||
[[package]]
|
||||
name = "gl_generator"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
|
||||
dependencies = [
|
||||
"khronos_api",
|
||||
"log",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"approx 0.5.1",
|
||||
"mint",
|
||||
"serde",
|
||||
]
|
||||
@@ -1899,6 +1957,12 @@ dependencies = [
|
||||
"nix 0.29.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
@@ -2004,6 +2068,22 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "khronos-egl"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "khronos_api"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -3056,6 +3136,25 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "profiling"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58"
|
||||
dependencies = [
|
||||
"profiling-procmacros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "profiling-procmacros"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.72",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.30.0"
|
||||
@@ -3067,9 +3166,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.34.0"
|
||||
version = "0.36.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4"
|
||||
checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -3494,6 +3593,41 @@ version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "smithay"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/Smithay/smithay.git#df79eeba63a8e9c2d33b9be2418aee6a940135e7"
|
||||
dependencies = [
|
||||
"appendlist",
|
||||
"bitflags 2.6.0",
|
||||
"calloop 0.14.1",
|
||||
"cgmath",
|
||||
"cursor-icon",
|
||||
"downcast-rs",
|
||||
"drm-fourcc",
|
||||
"encoding_rs",
|
||||
"errno",
|
||||
"gl_generator",
|
||||
"indexmap 2.3.0",
|
||||
"libc",
|
||||
"libloading 0.8.5",
|
||||
"once_cell",
|
||||
"profiling",
|
||||
"rand",
|
||||
"rustix",
|
||||
"scopeguard",
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-misc",
|
||||
"wayland-protocols-wlr",
|
||||
"wayland-server",
|
||||
"x11rb",
|
||||
"xkbcommon 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smithay-client-toolkit"
|
||||
version = "0.19.2"
|
||||
@@ -3502,7 +3636,7 @@ checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bytemuck",
|
||||
"calloop",
|
||||
"calloop 0.13.0",
|
||||
"calloop-wayland-source",
|
||||
"cursor-icon",
|
||||
"libc",
|
||||
@@ -3777,6 +3911,7 @@ version = "0.1.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
@@ -3888,6 +4023,16 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.0"
|
||||
@@ -4047,9 +4192,9 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.6"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993"
|
||||
checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
@@ -4061,9 +4206,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.5"
|
||||
version = "0.31.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943"
|
||||
checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"rustix",
|
||||
@@ -4094,15 +4239,39 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.3"
|
||||
name = "wayland-egl"
|
||||
version = "0.32.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62989625a776e827cc0f15d41444a3cea5205b963c3a25be48ae1b52d6b4daaa"
|
||||
checksum = "4e3cb8b84ff95310fe59ce6c61f1fa344ec22f4c240c369a2b20f15caebfede4"
|
||||
dependencies = [
|
||||
"wayland-backend",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
"wayland-server",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-misc"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d40dd9d2f7f2713724d84b920d6f73ff878f6a353712942f75f78f4dadb72886"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"wayland-backend",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
"wayland-server",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4129,24 +4298,39 @@ dependencies = [
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
"wayland-server",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.4"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6"
|
||||
checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml 0.34.0",
|
||||
"quick-xml 0.36.2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.4"
|
||||
name = "wayland-server"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148"
|
||||
checksum = "0f18d47038c0b10479e695d99ed073e400ccd9bdbb60e6e503c96f62adcb12b6"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"downcast-rs",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"log",
|
||||
@@ -4507,7 +4691,7 @@ dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"block2",
|
||||
"bytemuck",
|
||||
"calloop",
|
||||
"calloop 0.13.0",
|
||||
"cfg_aliases 0.2.1",
|
||||
"concurrent-queue",
|
||||
"core-foundation",
|
||||
@@ -4599,6 +4783,7 @@ dependencies = [
|
||||
"input-linux",
|
||||
"json",
|
||||
"json5",
|
||||
"khronos-egl",
|
||||
"libc",
|
||||
"libmonado-rs",
|
||||
"log",
|
||||
@@ -4615,11 +4800,15 @@ dependencies = [
|
||||
"serde_json5",
|
||||
"serde_yaml",
|
||||
"smallvec",
|
||||
"smithay",
|
||||
"strum",
|
||||
"sysinfo",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
"vulkano",
|
||||
"vulkano-shaders",
|
||||
"wayland-client",
|
||||
"wayland-egl",
|
||||
"winit",
|
||||
"wlx-capture",
|
||||
"xcb",
|
||||
|
||||
23
Cargo.toml
23
Cargo.toml
@@ -10,7 +10,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.86"
|
||||
anyhow = "1.0.89"
|
||||
ash = "^0.37.2"
|
||||
chrono = "0.4.38"
|
||||
chrono-tz = "0.9.0"
|
||||
@@ -70,11 +70,23 @@ image_dds = { version = "0.6.0", default-features = false, features = [
|
||||
] }
|
||||
mint = "0.5.9"
|
||||
|
||||
# WayVR-only deps
|
||||
khronos-egl = { version = "6.0.0", features = ["static"], optional = true }
|
||||
smithay = { git = "https://github.com/Smithay/smithay.git", default-features = false, features = [
|
||||
"renderer_gl",
|
||||
"backend_egl",
|
||||
"xwayland",
|
||||
"wayland_frontend",
|
||||
], optional = true }
|
||||
uuid = { version = "1.10.0", features = ["v4", "fast-rng"], optional = true }
|
||||
wayland-client = { version = "0.31.6", optional = true }
|
||||
wayland-egl = { version = "0.32.4", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
regex = { version = "*" }
|
||||
|
||||
[features]
|
||||
default = ["openvr", "openxr", "osc", "x11", "wayland"]
|
||||
default = ["openxr", "openvr", "osc", "x11", "wayland"]
|
||||
openvr = ["dep:ovr_overlay", "dep:json"]
|
||||
openxr = ["dep:openxr", "dep:libmonado-rs"]
|
||||
osc = ["dep:rosc"]
|
||||
@@ -83,4 +95,11 @@ wayland = ["pipewire", "wlx-capture/wlr", "xkbcommon/wayland"]
|
||||
pipewire = ["wlx-capture/pipewire"]
|
||||
uidev = ["dep:winit"]
|
||||
xcb = ["dep:xcb"]
|
||||
wayvr = [
|
||||
"dep:khronos-egl",
|
||||
"dep:smithay",
|
||||
"dep:uuid",
|
||||
"dep:wayland-client",
|
||||
"dep:wayland-egl",
|
||||
]
|
||||
as-raw-xcb-connection = []
|
||||
|
||||
@@ -8,9 +8,9 @@ use smallvec::{smallvec, SmallVec};
|
||||
use crate::backend::common::{snap_upright, OverlaySelector};
|
||||
use crate::config::{AStrMapExt, GeneralConfig};
|
||||
use crate::overlays::anchor::ANCHOR_NAME;
|
||||
use crate::state::AppState;
|
||||
use crate::state::{AppState, KeyboardFocus};
|
||||
|
||||
use super::overlay::OverlayID;
|
||||
use super::overlay::{OverlayID, OverlayState};
|
||||
use super::task::{TaskContainer, TaskType};
|
||||
use super::{common::OverlayContainer, overlay::OverlayData};
|
||||
|
||||
@@ -262,6 +262,15 @@ pub enum PointerMode {
|
||||
Special,
|
||||
}
|
||||
|
||||
fn update_focus(focus: &mut KeyboardFocus, state: &OverlayState) {
|
||||
if let Some(f) = &state.keyboard_focus {
|
||||
if *focus != *f {
|
||||
log::info!("Setting keyboard focus to {:?}", *f);
|
||||
*focus = *f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interact<O>(
|
||||
overlays: &mut OverlayContainer<O>,
|
||||
app: &mut AppState,
|
||||
@@ -362,6 +371,7 @@ where
|
||||
log::trace!("Hit: {} {:?}", hovered.state.name, hit);
|
||||
|
||||
if pointer.now.grab && !pointer.before.grab && hovered.state.grabbable {
|
||||
update_focus(&mut app.keyboard_focus, &hovered.state);
|
||||
pointer.start_grab(hovered, &mut app.tasks);
|
||||
return (
|
||||
hit.dist,
|
||||
@@ -407,6 +417,7 @@ where
|
||||
|
||||
if pointer.now.click && !pointer.before.click {
|
||||
pointer.interaction.clicked_id = Some(hit.overlay);
|
||||
update_focus(&mut app.keyboard_focus, &hovered.state);
|
||||
hovered.backend.on_pointer(app, &hit, true);
|
||||
} else if !pointer.now.click && pointer.before.click {
|
||||
if let Some(clicked_id) = pointer.interaction.clicked_id.take() {
|
||||
|
||||
@@ -17,6 +17,9 @@ pub mod uidev;
|
||||
#[cfg(feature = "osc")]
|
||||
pub mod osc;
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
pub mod wayvr;
|
||||
|
||||
pub mod overlay;
|
||||
|
||||
pub mod task;
|
||||
|
||||
@@ -320,6 +320,11 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
|
||||
let _ = sender.send_params(&overlays);
|
||||
};
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
if let Some(wayvr) = &state.wayvr {
|
||||
wayvr.borrow_mut().tick_events()?;
|
||||
}
|
||||
|
||||
log::trace!("Rendering frame");
|
||||
|
||||
for o in overlays.iter_mut() {
|
||||
@@ -334,6 +339,11 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
|
||||
.iter_mut()
|
||||
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &state.graphics));
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
if let Some(wayvr) = &state.wayvr {
|
||||
wayvr.borrow_mut().tick_finish()?;
|
||||
}
|
||||
|
||||
// chaperone
|
||||
|
||||
// close font handles?
|
||||
|
||||
@@ -360,6 +360,11 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
if let Some(wayvr) = &app_state.wayvr {
|
||||
wayvr.borrow_mut().tick_events()?;
|
||||
}
|
||||
|
||||
for o in overlays.iter_mut() {
|
||||
if !o.state.want_visible {
|
||||
continue;
|
||||
@@ -394,6 +399,11 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
|
||||
layers.push((0.0, maybe_layer));
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
if let Some(wayvr) = &app_state.wayvr {
|
||||
wayvr.borrow_mut().tick_finish()?;
|
||||
}
|
||||
|
||||
command_buffer.build_and_execute_now()?;
|
||||
|
||||
layers.sort_by(|a, b| b.0.total_cmp(&a.0));
|
||||
|
||||
@@ -11,7 +11,10 @@ use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A};
|
||||
use serde::Deserialize;
|
||||
use vulkano::image::view::ImageView;
|
||||
|
||||
use crate::{config::AStrMapExt, state::AppState};
|
||||
use crate::{
|
||||
config::AStrMapExt,
|
||||
state::{AppState, KeyboardFocus},
|
||||
};
|
||||
|
||||
use super::input::{DummyInteractionHandler, Haptics, InteractionHandler, PointerHit};
|
||||
|
||||
@@ -34,6 +37,7 @@ pub struct OverlayState {
|
||||
pub interactable: bool,
|
||||
pub recenter: bool,
|
||||
pub anchored: bool,
|
||||
pub keyboard_focus: Option<KeyboardFocus>,
|
||||
pub dirty: bool,
|
||||
pub alpha: f32,
|
||||
pub z_order: u32,
|
||||
@@ -60,6 +64,7 @@ impl Default for OverlayState {
|
||||
recenter: false,
|
||||
interactable: false,
|
||||
anchored: false,
|
||||
keyboard_focus: None,
|
||||
dirty: true,
|
||||
alpha: 1.0,
|
||||
z_order: 0,
|
||||
|
||||
37
src/backend/wayvr/README.md
Normal file
37
src/backend/wayvr/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
**WayVR acts as a bridge between Wayland applications and wlx-overlay-s panels, allowing you to display your applications within a VR environment. Internally, WayVR utilizes Smithay to run a Wayland compositor.**
|
||||
|
||||
# Features
|
||||
|
||||
- Display Wayland applications without GPU overhead (zero-copy via dma-buf)
|
||||
- Mouse input
|
||||
- Precision scrolling support
|
||||
- XWayland "support" via `cage`
|
||||
|
||||
# Supported hardware
|
||||
|
||||
### Confirmed working GPUs
|
||||
|
||||
- Navi 32 family: AMD Radeon RX 7800 XT **\***
|
||||
- Navi 23 family: AMD Radeon RX 6600 XT
|
||||
- Navi 21 family: AMD Radeon Pro W6800, AMD Radeon RX 6800 XT
|
||||
- Nvidia GTX 16 Series
|
||||
- _Your GPU here? (Let us know!)_
|
||||
|
||||
**\*** - With dmabuf modifier mitigation (probably Mesa bug)
|
||||
|
||||
# Supported software
|
||||
|
||||
- Basically all Qt applications (they work out of the box)
|
||||
- Most XWayland applications via `cage`
|
||||
|
||||
# Known issues
|
||||
|
||||
- Context menus are not functional in most cases yet
|
||||
|
||||
- Due to unknown circumstances, dma-buf textures may display various graphical glitches due to invalid dma-buf tiling modifier. Please report your GPU model when filing an issue. Alternatively, you can run wlx-overlay-s with `LIBGL_ALWAYS_SOFTWARE=1` to mitigate that (only the Smithay compositor will run in software renderer mode, wlx will still be accelerated).
|
||||
|
||||
- Potential data race in the rendering pipeline - A texture could be displayed during the clear-and-blit process in the compositor, causing minor artifacts (no fence sync support yet).
|
||||
|
||||
- Even though some applications support Wayland, some still check for the `DISPLAY` environment variable and an available X11 server, throwing an error. This can be fixed by running `cage`.
|
||||
|
||||
- GNOME still insists on rendering client-side decorations instead of server-side ones. This results in all GTK applications looking odd due to additional window shadows. [Fix here, "Client-side decorations"](https://wiki.archlinux.org/title/GTK)
|
||||
178
src/backend/wayvr/client.rs
Normal file
178
src/backend/wayvr/client.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
use std::{io::Read, os::unix::net::UnixStream, sync::Arc};
|
||||
|
||||
use smithay::{
|
||||
backend::input::Keycode,
|
||||
input::{keyboard::KeyboardHandle, pointer::PointerHandle},
|
||||
reexports::wayland_server,
|
||||
utils::SerialCounter,
|
||||
};
|
||||
|
||||
use super::{
|
||||
comp::{self},
|
||||
display,
|
||||
};
|
||||
|
||||
pub struct WayVRClient {
|
||||
pub client: wayland_server::Client,
|
||||
pub display_handle: display::DisplayHandle,
|
||||
pub pid: i32,
|
||||
}
|
||||
|
||||
pub struct WayVRManager {
|
||||
pub state: comp::Application,
|
||||
pub seat_keyboard: KeyboardHandle<comp::Application>,
|
||||
pub seat_pointer: PointerHandle<comp::Application>,
|
||||
pub serial_counter: SerialCounter,
|
||||
pub wayland_env: super::WaylandEnv,
|
||||
|
||||
display: wayland_server::Display<comp::Application>,
|
||||
listener: wayland_server::ListeningSocket,
|
||||
|
||||
pub clients: Vec<WayVRClient>,
|
||||
}
|
||||
|
||||
fn get_display_auth_from_pid(pid: i32) -> anyhow::Result<String> {
|
||||
let path = format!("/proc/{}/environ", pid);
|
||||
let mut env_data = String::new();
|
||||
std::fs::File::open(path)?.read_to_string(&mut env_data)?;
|
||||
|
||||
let lines: Vec<&str> = env_data.split('\0').filter(|s| !s.is_empty()).collect();
|
||||
|
||||
for line in lines {
|
||||
if let Some((key, value)) = line.split_once('=') {
|
||||
if key == "WAYVR_DISPLAY_AUTH" {
|
||||
return Ok(String::from(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::bail!("Failed to get display auth from PID {}", pid);
|
||||
}
|
||||
|
||||
impl WayVRManager {
|
||||
pub fn new(
|
||||
state: comp::Application,
|
||||
display: wayland_server::Display<comp::Application>,
|
||||
seat_keyboard: KeyboardHandle<comp::Application>,
|
||||
seat_pointer: PointerHandle<comp::Application>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let (wayland_env, listener) = create_wayland_listener()?;
|
||||
|
||||
Ok(Self {
|
||||
state,
|
||||
display,
|
||||
seat_keyboard,
|
||||
seat_pointer,
|
||||
listener,
|
||||
wayland_env,
|
||||
serial_counter: SerialCounter::new(),
|
||||
clients: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn accept_connection(
|
||||
&mut self,
|
||||
stream: UnixStream,
|
||||
displays: &mut display::DisplayVec,
|
||||
) -> anyhow::Result<()> {
|
||||
let client = self
|
||||
.display
|
||||
.handle()
|
||||
.insert_client(stream, Arc::new(comp::ClientState::default()))
|
||||
.unwrap();
|
||||
|
||||
let creds = client.get_credentials(&self.display.handle())?;
|
||||
let auth_key = get_display_auth_from_pid(creds.pid)?;
|
||||
|
||||
for (idx, cell) in displays.vec.iter().enumerate() {
|
||||
if let Some(cell) = &cell {
|
||||
let display = &cell.obj;
|
||||
if display.auth_key_matches(auth_key.as_str()) {
|
||||
let display_handle = display::DisplayVec::get_handle(cell, idx);
|
||||
|
||||
self.clients.push(WayVRClient {
|
||||
client,
|
||||
display_handle,
|
||||
pid: creds.pid,
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::bail!("Process auth key is invalid or selected display is non-existent");
|
||||
}
|
||||
|
||||
fn accept_connections(&mut self, displays: &mut display::DisplayVec) -> anyhow::Result<()> {
|
||||
if let Some(stream) = self.listener.accept()? {
|
||||
if let Err(e) = self.accept_connection(stream, displays) {
|
||||
log::error!("Failed to accept connection: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tick_wayland(&mut self, displays: &mut display::DisplayVec) -> anyhow::Result<()> {
|
||||
if let Err(e) = self.accept_connections(displays) {
|
||||
log::error!("accept_connections failed: {}", e);
|
||||
}
|
||||
|
||||
self.display.dispatch_clients(&mut self.state)?;
|
||||
self.display.flush_clients()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_key(&mut self, virtual_key: u32, down: bool) {
|
||||
let state = if down {
|
||||
smithay::backend::input::KeyState::Pressed
|
||||
} else {
|
||||
smithay::backend::input::KeyState::Released
|
||||
};
|
||||
|
||||
self.seat_keyboard.input::<(), _>(
|
||||
&mut self.state,
|
||||
Keycode::new(virtual_key),
|
||||
state,
|
||||
self.serial_counter.next_serial(),
|
||||
0,
|
||||
|_, _, _| smithay::input::keyboard::FilterResult::Forward,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const STARTING_WAYLAND_ADDR_IDX: u32 = 20;
|
||||
|
||||
fn create_wayland_listener() -> anyhow::Result<(super::WaylandEnv, wayland_server::ListeningSocket)>
|
||||
{
|
||||
let mut env = super::WaylandEnv {
|
||||
display_num: STARTING_WAYLAND_ADDR_IDX,
|
||||
};
|
||||
|
||||
let listener = loop {
|
||||
let display_str = env.display_num_string();
|
||||
log::debug!("Trying to open socket \"{}\"", display_str);
|
||||
match wayland_server::ListeningSocket::bind(display_str.as_str()) {
|
||||
Ok(listener) => {
|
||||
log::debug!("Listening to {}", display_str);
|
||||
break listener;
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!(
|
||||
"Failed to open socket \"{}\" (reason: {}), trying next...",
|
||||
display_str,
|
||||
e
|
||||
);
|
||||
|
||||
env.display_num += 1;
|
||||
if env.display_num > STARTING_WAYLAND_ADDR_IDX + 20 {
|
||||
// Highly unlikely for the user to have 20 Wayland displays enabled at once. Return error instead.
|
||||
anyhow::bail!("Failed to create wayland-server socket")
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok((env, listener))
|
||||
}
|
||||
186
src/backend/wayvr/comp.rs
Normal file
186
src/backend/wayvr/comp.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use smithay::backend::renderer::utils::on_commit_buffer_handler;
|
||||
use smithay::input::{Seat, SeatHandler, SeatState};
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use smithay::reexports::wayland_server::protocol::{wl_buffer, wl_seat, wl_surface};
|
||||
use smithay::reexports::wayland_server::{self, Resource};
|
||||
use smithay::wayland::buffer::BufferHandler;
|
||||
use smithay::wayland::shm::{ShmHandler, ShmState};
|
||||
use smithay::{
|
||||
delegate_compositor, delegate_data_device, delegate_seat, delegate_shm, delegate_xdg_shell,
|
||||
};
|
||||
use std::os::fd::OwnedFd;
|
||||
|
||||
use smithay::utils::Serial;
|
||||
use smithay::wayland::compositor::{
|
||||
self, with_surface_tree_downward, SurfaceAttributes, TraversalAction,
|
||||
};
|
||||
|
||||
use smithay::wayland::selection::data_device::{
|
||||
ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler,
|
||||
};
|
||||
use smithay::wayland::selection::SelectionHandler;
|
||||
use smithay::wayland::shell::xdg::{
|
||||
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
|
||||
};
|
||||
use wayland_server::backend::{ClientData, ClientId, DisconnectReason};
|
||||
use wayland_server::protocol::wl_surface::WlSurface;
|
||||
use wayland_server::Client;
|
||||
|
||||
use super::event_queue::SyncEventQueue;
|
||||
|
||||
pub struct Application {
|
||||
pub compositor: compositor::CompositorState,
|
||||
pub xdg_shell: XdgShellState,
|
||||
pub seat_state: SeatState<Application>,
|
||||
pub shm: ShmState,
|
||||
pub data_device: DataDeviceState,
|
||||
|
||||
pub queue_new_toplevel: SyncEventQueue<(ClientId, ToplevelSurface)>,
|
||||
}
|
||||
|
||||
impl compositor::CompositorHandler for Application {
|
||||
fn compositor_state(&mut self) -> &mut compositor::CompositorState {
|
||||
&mut self.compositor
|
||||
}
|
||||
|
||||
fn client_compositor_state<'a>(
|
||||
&self,
|
||||
client: &'a Client,
|
||||
) -> &'a compositor::CompositorClientState {
|
||||
&client.get_data::<ClientState>().unwrap().compositor_state
|
||||
}
|
||||
|
||||
fn commit(&mut self, surface: &WlSurface) {
|
||||
on_commit_buffer_handler::<Self>(surface);
|
||||
}
|
||||
}
|
||||
|
||||
impl SeatHandler for Application {
|
||||
type KeyboardFocus = WlSurface;
|
||||
type PointerFocus = WlSurface;
|
||||
type TouchFocus = WlSurface;
|
||||
|
||||
fn seat_state(&mut self) -> &mut SeatState<Self> {
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
||||
fn focus_changed(&mut self, _seat: &Seat<Self>, _focused: Option<&WlSurface>) {}
|
||||
fn cursor_image(
|
||||
&mut self,
|
||||
_seat: &Seat<Self>,
|
||||
_image: smithay::input::pointer::CursorImageStatus,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferHandler for Application {
|
||||
fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {}
|
||||
}
|
||||
|
||||
impl ClientDndGrabHandler for Application {}
|
||||
|
||||
impl ServerDndGrabHandler for Application {
|
||||
fn send(&mut self, _mime_type: String, _fd: OwnedFd, _seat: Seat<Self>) {}
|
||||
}
|
||||
|
||||
impl DataDeviceHandler for Application {
|
||||
fn data_device_state(&self) -> &DataDeviceState {
|
||||
&self.data_device
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectionHandler for Application {
|
||||
type SelectionUserData = ();
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ClientState {
|
||||
compositor_state: compositor::CompositorClientState,
|
||||
}
|
||||
|
||||
impl ClientData for ClientState {
|
||||
fn initialized(&self, client_id: ClientId) {
|
||||
log::debug!("Client ID {:?} connected", client_id);
|
||||
}
|
||||
|
||||
fn disconnected(&self, client_id: ClientId, reason: DisconnectReason) {
|
||||
log::debug!(
|
||||
"Client ID {:?} disconnected. Reason: {:?}",
|
||||
client_id,
|
||||
reason
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<compositor::CompositorState> for Application {
|
||||
fn as_mut(&mut self) -> &mut compositor::CompositorState {
|
||||
&mut self.compositor
|
||||
}
|
||||
}
|
||||
|
||||
impl XdgShellHandler for Application {
|
||||
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
|
||||
&mut self.xdg_shell
|
||||
}
|
||||
|
||||
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
||||
if let Some(client) = surface.wl_surface().client() {
|
||||
self.queue_new_toplevel.send((client.id(), surface.clone()));
|
||||
}
|
||||
surface.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Activated);
|
||||
});
|
||||
surface.send_configure();
|
||||
}
|
||||
|
||||
fn new_popup(&mut self, _surface: PopupSurface, _positioner: PositionerState) {
|
||||
// Handle popup creation here
|
||||
}
|
||||
|
||||
fn grab(&mut self, _surface: PopupSurface, _seat: wl_seat::WlSeat, _serial: Serial) {
|
||||
// Handle popup grab here
|
||||
}
|
||||
|
||||
fn reposition_request(
|
||||
&mut self,
|
||||
_surface: PopupSurface,
|
||||
_positioner: PositionerState,
|
||||
_token: u32,
|
||||
) {
|
||||
// Handle popup reposition here
|
||||
}
|
||||
}
|
||||
|
||||
impl ShmHandler for Application {
|
||||
fn shm_state(&self) -> &ShmState {
|
||||
&self.shm
|
||||
}
|
||||
}
|
||||
|
||||
delegate_xdg_shell!(Application);
|
||||
delegate_compositor!(Application);
|
||||
delegate_shm!(Application);
|
||||
delegate_seat!(Application);
|
||||
delegate_data_device!(Application);
|
||||
|
||||
pub fn send_frames_surface_tree(surface: &wl_surface::WlSurface, time: u32) {
|
||||
with_surface_tree_downward(
|
||||
surface,
|
||||
(),
|
||||
|_, _, &()| TraversalAction::DoChildren(()),
|
||||
|_surf, states, &()| {
|
||||
// the surface may not have any user_data if it is a subsurface and has not
|
||||
// yet been commited
|
||||
for callback in states
|
||||
.cached_state
|
||||
.get::<SurfaceAttributes>()
|
||||
.current()
|
||||
.frame_callbacks
|
||||
.drain(..)
|
||||
{
|
||||
callback.done(time);
|
||||
}
|
||||
},
|
||||
|_, _, &()| true,
|
||||
);
|
||||
}
|
||||
356
src/backend/wayvr/display.rs
Normal file
356
src/backend/wayvr/display.rs
Normal file
@@ -0,0 +1,356 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
element::{
|
||||
surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement},
|
||||
Kind,
|
||||
},
|
||||
gles::{ffi, GlesRenderer, GlesTexture},
|
||||
utils::draw_render_elements,
|
||||
Bind, Color32F, Frame, Renderer,
|
||||
},
|
||||
input,
|
||||
utils::{Logical, Point, Rectangle, Size, Transform},
|
||||
wayland::shell::xdg::ToplevelSurface,
|
||||
};
|
||||
|
||||
use crate::gen_id;
|
||||
|
||||
use super::{
|
||||
client::WayVRManager, comp::send_frames_surface_tree, egl_data, smithay_wrapper, window,
|
||||
};
|
||||
|
||||
fn generate_auth_key() -> String {
|
||||
let uuid = uuid::Uuid::new_v4();
|
||||
uuid.to_string()
|
||||
}
|
||||
|
||||
struct Process {
|
||||
auth_key: String,
|
||||
child: std::process::Child,
|
||||
}
|
||||
|
||||
impl Drop for Process {
|
||||
fn drop(&mut self) {
|
||||
let _dont_care = self.child.kill();
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayWindow {
|
||||
handle: window::WindowHandle,
|
||||
toplevel: ToplevelSurface,
|
||||
}
|
||||
|
||||
pub struct Display {
|
||||
// Display info stuff
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
wm: Rc<RefCell<window::WindowManager>>,
|
||||
displayed_windows: Vec<DisplayWindow>,
|
||||
wayland_env: super::WaylandEnv,
|
||||
|
||||
// Render data stuff
|
||||
gles_texture: GlesTexture, // TODO: drop texture
|
||||
egl_image: khronos_egl::Image,
|
||||
egl_data: Rc<egl_data::EGLData>,
|
||||
pub dmabuf_data: egl_data::DMAbufData,
|
||||
|
||||
processes: Vec<Process>,
|
||||
}
|
||||
|
||||
impl Drop for Display {
|
||||
fn drop(&mut self) {
|
||||
let _ = self
|
||||
.egl_data
|
||||
.egl
|
||||
.destroy_image(self.egl_data.display, self.egl_image);
|
||||
}
|
||||
}
|
||||
|
||||
impl Display {
|
||||
pub fn new(
|
||||
wm: Rc<RefCell<window::WindowManager>>,
|
||||
renderer: &mut GlesRenderer,
|
||||
egl_data: Rc<egl_data::EGLData>,
|
||||
wayland_env: super::WaylandEnv,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> anyhow::Result<Self> {
|
||||
let tex_format = ffi::RGBA;
|
||||
let internal_format = ffi::RGBA8;
|
||||
|
||||
let tex_id = renderer.with_context(|gl| {
|
||||
smithay_wrapper::create_framebuffer_texture(
|
||||
gl,
|
||||
width,
|
||||
height,
|
||||
tex_format,
|
||||
internal_format,
|
||||
)
|
||||
})?;
|
||||
|
||||
let egl_image = egl_data.create_egl_image(tex_id, width, height)?;
|
||||
let dmabuf_data = egl_data.create_dmabuf_data(&egl_image)?;
|
||||
|
||||
let opaque = false;
|
||||
let size = (width as i32, height as i32).into();
|
||||
let gles_texture =
|
||||
unsafe { GlesTexture::from_raw(renderer, Some(tex_format), opaque, tex_id, size) };
|
||||
|
||||
Ok(Self {
|
||||
wm,
|
||||
width,
|
||||
height,
|
||||
displayed_windows: Vec::new(),
|
||||
egl_data,
|
||||
dmabuf_data,
|
||||
egl_image,
|
||||
gles_texture,
|
||||
wayland_env,
|
||||
processes: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn auth_key_matches(&self, auth_key: &str) -> bool {
|
||||
for process in &self.processes {
|
||||
if process.auth_key.as_str() == auth_key {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn add_window(&mut self, window_handle: window::WindowHandle, toplevel: &ToplevelSurface) {
|
||||
log::debug!("Attaching toplevel surface into display");
|
||||
self.displayed_windows.push(DisplayWindow {
|
||||
handle: window_handle,
|
||||
toplevel: toplevel.clone(),
|
||||
});
|
||||
self.reposition_windows();
|
||||
}
|
||||
|
||||
fn reposition_windows(&mut self) {
|
||||
let window_count = self.displayed_windows.len();
|
||||
|
||||
for (i, win) in self.displayed_windows.iter_mut().enumerate() {
|
||||
if let Some(window) = self.wm.borrow_mut().windows.get_mut(&win.handle) {
|
||||
let d_cur = i as f32 / window_count as f32;
|
||||
let d_next = (i + 1) as f32 / window_count as f32;
|
||||
|
||||
let left = (d_cur * self.width as f32) as i32;
|
||||
let right = (d_next * self.width as f32) as i32;
|
||||
|
||||
window.set_pos(left, 0);
|
||||
window.set_size((right - left) as u32, self.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick_render(&self, renderer: &mut GlesRenderer, time_ms: u64) -> anyhow::Result<()> {
|
||||
renderer.bind(self.gles_texture.clone())?;
|
||||
|
||||
let size = Size::from((self.width as i32, self.height as i32));
|
||||
let damage: Rectangle<i32, smithay::utils::Physical> =
|
||||
Rectangle::from_loc_and_size((0, 0), size);
|
||||
|
||||
let elements: Vec<WaylandSurfaceRenderElement<GlesRenderer>> = self
|
||||
.displayed_windows
|
||||
.iter()
|
||||
.flat_map(|display_window| {
|
||||
let wm = self.wm.borrow_mut();
|
||||
if let Some(window) = wm.windows.get(&display_window.handle) {
|
||||
render_elements_from_surface_tree(
|
||||
renderer,
|
||||
display_window.toplevel.wl_surface(),
|
||||
(window.pos_x, window.pos_y),
|
||||
1.0,
|
||||
1.0,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
} else {
|
||||
// Failed to fetch window
|
||||
vec![]
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut frame = renderer.render(size, Transform::Normal)?;
|
||||
|
||||
let clear_opacity = if self.displayed_windows.is_empty() {
|
||||
0.5
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
frame.clear(Color32F::new(1.0, 1.0, 1.0, clear_opacity), &[damage])?;
|
||||
|
||||
draw_render_elements(&mut frame, 1.0, &elements, &[damage])?;
|
||||
|
||||
let _sync_point = frame.finish()?;
|
||||
|
||||
for window in &self.displayed_windows {
|
||||
send_frames_surface_tree(window.toplevel.wl_surface(), time_ms as u32);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_hovered_window(&self, cursor_x: u32, cursor_y: u32) -> Option<window::WindowHandle> {
|
||||
let wm = self.wm.borrow();
|
||||
|
||||
for cell in self.displayed_windows.iter() {
|
||||
if let Some(window) = wm.windows.get(&cell.handle) {
|
||||
if (cursor_x as i32) >= window.pos_x
|
||||
&& (cursor_x as i32) < window.pos_x + window.size_x as i32
|
||||
&& (cursor_y as i32) >= window.pos_y
|
||||
&& (cursor_y as i32) < window.pos_y + window.size_y as i32
|
||||
{
|
||||
return Some(cell.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn send_mouse_move(&self, manager: &mut WayVRManager, x: u32, y: u32) {
|
||||
if let Some(window_handle) = self.get_hovered_window(x, y) {
|
||||
let wm = self.wm.borrow();
|
||||
if let Some(window) = wm.windows.get(&window_handle) {
|
||||
let surf = window.toplevel.wl_surface().clone();
|
||||
let point = Point::<f64, Logical>::from((
|
||||
(x as i32 - window.pos_x) as f64,
|
||||
(y as i32 - window.pos_y) as f64,
|
||||
));
|
||||
|
||||
manager.seat_pointer.motion(
|
||||
&mut manager.state,
|
||||
Some((surf, Point::from((0.0, 0.0)))),
|
||||
&input::pointer::MotionEvent {
|
||||
serial: manager.serial_counter.next_serial(),
|
||||
time: 0,
|
||||
location: point,
|
||||
},
|
||||
);
|
||||
|
||||
manager.seat_pointer.frame(&mut manager.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mouse_index_number(index: super::MouseIndex) -> u32 {
|
||||
match index {
|
||||
super::MouseIndex::Left => 0x110, /* BTN_LEFT */
|
||||
super::MouseIndex::Center => 0x112, /* BTN_MIDDLE */
|
||||
super::MouseIndex::Right => 0x111, /* BTN_RIGHT */
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_mouse_down(&self, manager: &mut WayVRManager, index: super::MouseIndex) {
|
||||
// Change keyboard focus to pressed window
|
||||
let loc = manager.seat_pointer.current_location();
|
||||
|
||||
if let Some(window_handle) =
|
||||
self.get_hovered_window(loc.x.max(0.0) as u32, loc.y.max(0.0) as u32)
|
||||
{
|
||||
let wm = self.wm.borrow();
|
||||
if let Some(window) = wm.windows.get(&window_handle) {
|
||||
let surf = window.toplevel.wl_surface().clone();
|
||||
|
||||
if manager.seat_keyboard.current_focus().is_none() {
|
||||
manager.seat_keyboard.set_focus(
|
||||
&mut manager.state,
|
||||
Some(surf),
|
||||
manager.serial_counter.next_serial(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
manager.seat_pointer.button(
|
||||
&mut manager.state,
|
||||
&input::pointer::ButtonEvent {
|
||||
button: Self::get_mouse_index_number(index),
|
||||
serial: manager.serial_counter.next_serial(),
|
||||
time: 0,
|
||||
state: smithay::backend::input::ButtonState::Pressed,
|
||||
},
|
||||
);
|
||||
|
||||
manager.seat_pointer.frame(&mut manager.state);
|
||||
}
|
||||
|
||||
pub fn send_mouse_up(&self, manager: &mut WayVRManager, index: super::MouseIndex) {
|
||||
manager.seat_pointer.button(
|
||||
&mut manager.state,
|
||||
&input::pointer::ButtonEvent {
|
||||
button: Self::get_mouse_index_number(index),
|
||||
serial: manager.serial_counter.next_serial(),
|
||||
time: 0,
|
||||
state: smithay::backend::input::ButtonState::Released,
|
||||
},
|
||||
);
|
||||
|
||||
manager.seat_pointer.frame(&mut manager.state);
|
||||
}
|
||||
|
||||
pub fn send_mouse_scroll(&self, manager: &mut WayVRManager, delta: f32) {
|
||||
manager.seat_pointer.axis(
|
||||
&mut manager.state,
|
||||
input::pointer::AxisFrame {
|
||||
source: None,
|
||||
relative_direction: (
|
||||
smithay::backend::input::AxisRelativeDirection::Identical,
|
||||
smithay::backend::input::AxisRelativeDirection::Identical,
|
||||
),
|
||||
time: 0,
|
||||
axis: (0.0, -delta as f64),
|
||||
v120: Some((0, (delta * -120.0) as i32)),
|
||||
stop: (false, false),
|
||||
},
|
||||
);
|
||||
manager.seat_pointer.frame(&mut manager.state);
|
||||
}
|
||||
|
||||
fn configure_env(&self, cmd: &mut std::process::Command, auth_key: &str) {
|
||||
cmd.env_remove("DISPLAY"); // Goodbye X11
|
||||
cmd.env("WAYLAND_DISPLAY", self.wayland_env.display_num_string());
|
||||
cmd.env("WAYVR_DISPLAY_AUTH", auth_key);
|
||||
}
|
||||
|
||||
pub fn spawn_process(
|
||||
&mut self,
|
||||
exec_path: &str,
|
||||
args: &[&str],
|
||||
env: &[(&str, &str)],
|
||||
) -> anyhow::Result<()> {
|
||||
log::info!("Spawning subprocess with exec path \"{}\"", exec_path);
|
||||
|
||||
let auth_key = generate_auth_key();
|
||||
|
||||
let mut cmd = std::process::Command::new(exec_path);
|
||||
self.configure_env(&mut cmd, auth_key.as_str());
|
||||
cmd.args(args);
|
||||
|
||||
for e in env {
|
||||
cmd.env(e.0, e.1);
|
||||
}
|
||||
|
||||
match cmd.spawn() {
|
||||
Ok(child) => {
|
||||
self.processes.push(Process { child, auth_key });
|
||||
}
|
||||
Err(e) => {
|
||||
anyhow::bail!(
|
||||
"Failed to launch process with path \"{}\": {}. Make sure your exec path exists.",
|
||||
exec_path,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
gen_id!(DisplayVec, Display, DisplayCell, DisplayHandle);
|
||||
273
src/backend/wayvr/egl_data.rs
Normal file
273
src/backend/wayvr/egl_data.rs
Normal file
@@ -0,0 +1,273 @@
|
||||
use super::egl_ex;
|
||||
use anyhow::anyhow;
|
||||
|
||||
pub struct EGLData {
|
||||
pub egl: khronos_egl::Instance<khronos_egl::Static>,
|
||||
pub display: khronos_egl::Display,
|
||||
pub config: khronos_egl::Config,
|
||||
pub context: khronos_egl::Context,
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bind_egl_function {
|
||||
($func_type:ident, $func:expr) => {
|
||||
std::mem::transmute_copy::<_, $func_type>($func).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DMAbufModifierInfo {
|
||||
pub modifiers: Vec<u64>,
|
||||
pub fourcc: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DMAbufData {
|
||||
pub fd: i32,
|
||||
pub stride: i32,
|
||||
pub offset: i32,
|
||||
pub mod_info: DMAbufModifierInfo,
|
||||
}
|
||||
|
||||
impl EGLData {
|
||||
pub fn load_func(&self, func_name: &str) -> anyhow::Result<extern "system" fn()> {
|
||||
let raw_fn = self.egl.get_proc_address(func_name).ok_or(anyhow::anyhow!(
|
||||
"Required EGL function {} not found",
|
||||
func_name
|
||||
))?;
|
||||
Ok(raw_fn)
|
||||
}
|
||||
|
||||
pub fn new() -> anyhow::Result<EGLData> {
|
||||
unsafe {
|
||||
let egl = khronos_egl::Instance::new(khronos_egl::Static);
|
||||
|
||||
let display = egl
|
||||
.get_display(khronos_egl::DEFAULT_DISPLAY)
|
||||
.ok_or(anyhow!("eglGetDisplay failed"))?;
|
||||
|
||||
let (major, minor) = egl.initialize(display)?;
|
||||
log::debug!("EGL version: {}.{}", major, minor);
|
||||
|
||||
let attrib_list = [
|
||||
khronos_egl::RED_SIZE,
|
||||
8,
|
||||
khronos_egl::GREEN_SIZE,
|
||||
8,
|
||||
khronos_egl::BLUE_SIZE,
|
||||
8,
|
||||
khronos_egl::SURFACE_TYPE,
|
||||
khronos_egl::WINDOW_BIT,
|
||||
khronos_egl::RENDERABLE_TYPE,
|
||||
khronos_egl::OPENGL_BIT,
|
||||
khronos_egl::NONE,
|
||||
];
|
||||
|
||||
let config = egl
|
||||
.choose_first_config(display, &attrib_list)?
|
||||
.ok_or(anyhow!("Failed to get EGL config"))?;
|
||||
|
||||
egl.bind_api(khronos_egl::OPENGL_ES_API)?;
|
||||
|
||||
log::debug!("eglCreateContext");
|
||||
|
||||
// Require OpenGL ES 3.0
|
||||
let context_attrib_list = [
|
||||
khronos_egl::CONTEXT_MAJOR_VERSION,
|
||||
3,
|
||||
khronos_egl::CONTEXT_MINOR_VERSION,
|
||||
0,
|
||||
khronos_egl::NONE,
|
||||
];
|
||||
|
||||
let context = egl.create_context(display, config, None, &context_attrib_list)?;
|
||||
|
||||
egl.make_current(display, None, None, Some(context))?;
|
||||
|
||||
Ok(EGLData {
|
||||
egl,
|
||||
display,
|
||||
config,
|
||||
context,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn make_current(&self, surface: &khronos_egl::Surface) -> anyhow::Result<()> {
|
||||
self.egl.make_current(
|
||||
self.display,
|
||||
Some(*surface),
|
||||
Some(*surface),
|
||||
Some(self.context),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn query_dmabuf_mod_info(&self) -> anyhow::Result<DMAbufModifierInfo> {
|
||||
let target_fourcc = 0x34324258; //XB24
|
||||
|
||||
unsafe {
|
||||
use egl_ex::PFNEGLQUERYDMABUFFORMATSEXTPROC;
|
||||
use egl_ex::PFNEGLQUERYDMABUFMODIFIERSEXTPROC;
|
||||
|
||||
let egl_query_dmabuf_formats_ext = bind_egl_function!(
|
||||
PFNEGLQUERYDMABUFFORMATSEXTPROC,
|
||||
&self.load_func("eglQueryDmaBufFormatsEXT")?
|
||||
);
|
||||
|
||||
// Query format count
|
||||
let mut num_formats: khronos_egl::Int = 0;
|
||||
egl_query_dmabuf_formats_ext(
|
||||
self.display.as_ptr(),
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
&mut num_formats,
|
||||
);
|
||||
|
||||
// Retrieve formt list
|
||||
let mut formats: Vec<i32> = vec![0; num_formats as usize];
|
||||
egl_query_dmabuf_formats_ext(
|
||||
self.display.as_ptr(),
|
||||
num_formats,
|
||||
formats.as_mut_ptr(),
|
||||
&mut num_formats,
|
||||
);
|
||||
|
||||
/*for (idx, format) in formats.iter().enumerate() {
|
||||
let bytes = format.to_le_bytes();
|
||||
log::trace!(
|
||||
"idx {}, format {}{}{}{} (hex {:#x})",
|
||||
idx,
|
||||
bytes[0] as char,
|
||||
bytes[1] as char,
|
||||
bytes[2] as char,
|
||||
bytes[3] as char,
|
||||
format
|
||||
);
|
||||
}*/
|
||||
|
||||
let egl_query_dmabuf_modifiers_ext = bind_egl_function!(
|
||||
PFNEGLQUERYDMABUFMODIFIERSEXTPROC,
|
||||
&self.load_func("eglQueryDmaBufModifiersEXT")?
|
||||
);
|
||||
|
||||
let mut num_mods: khronos_egl::Int = 0;
|
||||
|
||||
// Query modifier count
|
||||
egl_query_dmabuf_modifiers_ext(
|
||||
self.display.as_ptr(),
|
||||
target_fourcc,
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
&mut num_mods,
|
||||
);
|
||||
|
||||
if num_mods == 0 {
|
||||
anyhow::bail!("eglQueryDmaBufModifiersEXT modifier count is zero");
|
||||
}
|
||||
|
||||
let mut mods: Vec<u64> = vec![0; num_mods as usize];
|
||||
egl_query_dmabuf_modifiers_ext(
|
||||
self.display.as_ptr(),
|
||||
target_fourcc,
|
||||
num_mods,
|
||||
mods.as_mut_ptr(),
|
||||
std::ptr::null_mut(),
|
||||
&mut num_mods,
|
||||
);
|
||||
|
||||
if mods[0] == 0xFFFFFFFFFFFFFFFF {
|
||||
anyhow::bail!("modifier is -1")
|
||||
}
|
||||
|
||||
log::trace!("Modifier list:");
|
||||
for modifier in &mods {
|
||||
log::trace!("{:#x}", modifier);
|
||||
}
|
||||
|
||||
// We should not change these modifier values. Passing all of them to the Vulkan dmabuf
|
||||
// texture system causes significant graphical corruption due to invalid memory layout and
|
||||
// tiling on this specific GPU model (very probably others also have the same issue).
|
||||
// It is not guaranteed that this modifier will be present in other models.
|
||||
// If not, the full list of modifiers will be passed. Further testing is required.
|
||||
// For now, it looks like only NAVI32-based gpus have this problem.
|
||||
let mod_whitelist: [u64; 1] = [0x20000002086bf04 /* AMD RX 7800 XT */];
|
||||
|
||||
for modifier in &mod_whitelist {
|
||||
if mods.contains(modifier) {
|
||||
log::warn!("Using whitelisted dmabuf tiling modifier: {:#x}", modifier);
|
||||
mods = vec![*modifier, 0x0 /* also important (???) */];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(DMAbufModifierInfo {
|
||||
modifiers: mods,
|
||||
fourcc: target_fourcc as u32,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_dmabuf_data(&self, egl_image: &khronos_egl::Image) -> anyhow::Result<DMAbufData> {
|
||||
use egl_ex::PFNEGLEXPORTDMABUFIMAGEMESAPROC as FUNC;
|
||||
unsafe {
|
||||
let egl_export_dmabuf_image_mesa =
|
||||
bind_egl_function!(FUNC, &self.load_func("eglExportDMABUFImageMESA")?);
|
||||
|
||||
let mut fds: [i32; 3] = [0; 3];
|
||||
let mut strides: [i32; 3] = [0; 3];
|
||||
let mut offsets: [i32; 3] = [0; 3];
|
||||
|
||||
if egl_export_dmabuf_image_mesa(
|
||||
self.display.as_ptr(),
|
||||
egl_image.as_ptr(),
|
||||
fds.as_mut_ptr(),
|
||||
strides.as_mut_ptr(),
|
||||
offsets.as_mut_ptr(),
|
||||
) != khronos_egl::TRUE
|
||||
{
|
||||
anyhow::bail!("eglExportDMABUFImageMESA failed");
|
||||
}
|
||||
|
||||
// many planes in RGB data?
|
||||
debug_assert!(fds[1] == 0);
|
||||
debug_assert!(strides[1] == 0);
|
||||
debug_assert!(offsets[1] == 0);
|
||||
|
||||
let mod_info = self.query_dmabuf_mod_info()?;
|
||||
|
||||
Ok(DMAbufData {
|
||||
fd: fds[0],
|
||||
stride: strides[0],
|
||||
offset: offsets[0],
|
||||
mod_info,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_egl_image(
|
||||
&self,
|
||||
gl_tex_id: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> anyhow::Result<khronos_egl::Image> {
|
||||
unsafe {
|
||||
Ok(self.egl.create_image(
|
||||
self.display,
|
||||
self.context,
|
||||
khronos_egl::GL_TEXTURE_2D as std::ffi::c_uint,
|
||||
khronos_egl::ClientBuffer::from_ptr(gl_tex_id as *mut std::ffi::c_void),
|
||||
&[
|
||||
khronos_egl::WIDTH as usize,
|
||||
width as usize,
|
||||
khronos_egl::HEIGHT as usize,
|
||||
height as usize,
|
||||
khronos_egl::ATTRIB_NONE,
|
||||
],
|
||||
)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/backend/wayvr/egl_ex.rs
Normal file
32
src/backend/wayvr/egl_ex.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//eglExportDMABUFImageMESA
|
||||
pub type PFNEGLEXPORTDMABUFIMAGEMESAPROC = Option<
|
||||
unsafe extern "C" fn(
|
||||
dpy: khronos_egl::EGLDisplay,
|
||||
image: khronos_egl::EGLImage,
|
||||
fds: *mut i32,
|
||||
strides: *mut khronos_egl::Int,
|
||||
offsets: *mut khronos_egl::Int,
|
||||
) -> khronos_egl::Boolean,
|
||||
>;
|
||||
|
||||
//eglQueryDmaBufModifiersEXT
|
||||
pub type PFNEGLQUERYDMABUFMODIFIERSEXTPROC = Option<
|
||||
unsafe extern "C" fn(
|
||||
dpy: khronos_egl::EGLDisplay,
|
||||
format: khronos_egl::Int,
|
||||
max_modifiers: khronos_egl::Int,
|
||||
modifiers: *mut u64,
|
||||
external_only: *mut khronos_egl::Boolean,
|
||||
num_modifiers: *mut khronos_egl::Int,
|
||||
) -> khronos_egl::Boolean,
|
||||
>;
|
||||
|
||||
//eglQueryDmaBufFormatsEXT
|
||||
pub type PFNEGLQUERYDMABUFFORMATSEXTPROC = Option<
|
||||
unsafe extern "C" fn(
|
||||
dpy: khronos_egl::EGLDisplay,
|
||||
max_formats: khronos_egl::Int,
|
||||
formats: *mut khronos_egl::Int,
|
||||
num_formats: *mut khronos_egl::Int,
|
||||
) -> khronos_egl::Boolean,
|
||||
>;
|
||||
32
src/backend/wayvr/event_queue.rs
Normal file
32
src/backend/wayvr/event_queue.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
|
||||
|
||||
struct Data<DataType> {
|
||||
queue: VecDeque<DataType>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SyncEventQueue<DataType> {
|
||||
data: Rc<RefCell<Data<DataType>>>,
|
||||
}
|
||||
|
||||
impl<DataType> SyncEventQueue<DataType> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: Rc::new(RefCell::new(Data {
|
||||
queue: Default::default(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&self, message: DataType) {
|
||||
let mut data = self.data.borrow_mut();
|
||||
data.queue.push_back(message);
|
||||
}
|
||||
|
||||
pub fn read(&self) -> Option<DataType> {
|
||||
let mut data = self.data.borrow_mut();
|
||||
data.queue.pop_front()
|
||||
}
|
||||
}
|
||||
157
src/backend/wayvr/handle.rs
Normal file
157
src/backend/wayvr/handle.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
#[macro_export]
|
||||
macro_rules! gen_id {
|
||||
(
|
||||
$container_name:ident,
|
||||
$instance_name:ident,
|
||||
$cell_name:ident,
|
||||
$handle_name:ident) => {
|
||||
//ThingCell
|
||||
pub struct $cell_name {
|
||||
pub obj: $instance_name,
|
||||
generation: u64,
|
||||
}
|
||||
|
||||
//ThingVec
|
||||
pub struct $container_name {
|
||||
// Vec<Option<ThingCell>>
|
||||
pub vec: Vec<Option<$cell_name>>,
|
||||
|
||||
cur_generation: u64,
|
||||
}
|
||||
|
||||
//ThingHandle
|
||||
#[derive(Default, Clone, Copy, PartialEq)]
|
||||
pub struct $handle_name {
|
||||
idx: u32,
|
||||
generation: u64,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl $handle_name {
|
||||
pub fn reset(&mut self) {
|
||||
self.generation = 0;
|
||||
}
|
||||
|
||||
pub fn is_set(&self) -> bool {
|
||||
self.generation > 0
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u32 {
|
||||
self.idx
|
||||
}
|
||||
}
|
||||
|
||||
//ThingVec
|
||||
#[allow(dead_code)]
|
||||
impl $container_name {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
vec: Vec::new(),
|
||||
cur_generation: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self, callback: &mut dyn FnMut($handle_name, &$instance_name)) {
|
||||
for (idx, opt_cell) in self.vec.iter().enumerate() {
|
||||
if let Some(cell) = opt_cell {
|
||||
let handle = $container_name::get_handle(&cell, idx);
|
||||
callback(handle, &cell.obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_handle(cell: &$cell_name, idx: usize) -> $handle_name {
|
||||
$handle_name {
|
||||
idx: idx as u32,
|
||||
generation: cell.generation,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_unused_idx(&mut self) -> Option<u32> {
|
||||
for (num, obj) in self.vec.iter().enumerate() {
|
||||
if obj.is_none() {
|
||||
return Some(num as u32);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn add(&mut self, obj: $instance_name) -> $handle_name {
|
||||
self.cur_generation += 1;
|
||||
let generation = self.cur_generation;
|
||||
|
||||
let unused_idx = self.find_unused_idx();
|
||||
|
||||
let idx = if let Some(idx) = unused_idx {
|
||||
idx
|
||||
} else {
|
||||
self.vec.len() as u32
|
||||
};
|
||||
|
||||
let handle = $handle_name { idx, generation };
|
||||
|
||||
let cell = $cell_name { obj, generation };
|
||||
|
||||
if let Some(idx) = unused_idx {
|
||||
self.vec[idx as usize] = Some(cell);
|
||||
} else {
|
||||
self.vec.push(Some(cell))
|
||||
}
|
||||
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, handle: &$handle_name) {
|
||||
// Out of bounds, ignore
|
||||
if handle.idx as usize >= self.vec.len() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove only if the generation matches
|
||||
if let Some(cell) = &self.vec[handle.idx as usize] {
|
||||
if cell.generation == handle.generation {
|
||||
self.vec[handle.idx as usize] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, handle: &$handle_name) -> Option<&$instance_name> {
|
||||
// Out of bounds, ignore
|
||||
if handle.idx as usize >= self.vec.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(cell) = &self.vec[handle.idx as usize] {
|
||||
if cell.generation == handle.generation {
|
||||
return Some(&cell.obj);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, handle: &$handle_name) -> Option<&mut $instance_name> {
|
||||
// Out of bounds, ignore
|
||||
if handle.idx as usize >= self.vec.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(cell) = &mut self.vec[handle.idx as usize] {
|
||||
if cell.generation == handle.generation {
|
||||
return Some(&mut cell.obj);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* Example usage:
|
||||
gen_id!(ThingVec, ThingInstance, ThingCell, ThingHandle);
|
||||
|
||||
struct ThingInstance {}
|
||||
|
||||
impl ThingInstance {}
|
||||
*/
|
||||
213
src/backend/wayvr/mod.rs
Normal file
213
src/backend/wayvr/mod.rs
Normal file
@@ -0,0 +1,213 @@
|
||||
mod client;
|
||||
mod comp;
|
||||
pub mod display;
|
||||
pub mod egl_data;
|
||||
mod egl_ex;
|
||||
mod event_queue;
|
||||
mod handle;
|
||||
mod smithay_wrapper;
|
||||
mod time;
|
||||
mod window;
|
||||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use comp::Application;
|
||||
use display::DisplayVec;
|
||||
use event_queue::SyncEventQueue;
|
||||
use smithay::{
|
||||
backend::renderer::gles::GlesRenderer,
|
||||
input::SeatState,
|
||||
reexports::wayland_server::{self, backend::ClientId},
|
||||
wayland::{
|
||||
compositor,
|
||||
selection::data_device::DataDeviceState,
|
||||
shell::xdg::{ToplevelSurface, XdgShellState},
|
||||
shm::ShmState,
|
||||
},
|
||||
};
|
||||
use time::get_millis;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WaylandEnv {
|
||||
pub display_num: u32,
|
||||
}
|
||||
|
||||
impl WaylandEnv {
|
||||
pub fn display_num_string(&self) -> String {
|
||||
// e.g. "wayland-20"
|
||||
format!("wayland-{}", self.display_num)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct WayVR {
|
||||
time_start: u64,
|
||||
gles_renderer: GlesRenderer,
|
||||
displays: display::DisplayVec,
|
||||
manager: client::WayVRManager,
|
||||
wm: Rc<RefCell<window::WindowManager>>,
|
||||
egl_data: Rc<egl_data::EGLData>,
|
||||
|
||||
queue_new_toplevel: SyncEventQueue<(ClientId, ToplevelSurface)>,
|
||||
}
|
||||
|
||||
pub enum MouseIndex {
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl WayVR {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let display: wayland_server::Display<Application> = wayland_server::Display::new()?;
|
||||
let dh = display.handle();
|
||||
let compositor = compositor::CompositorState::new::<Application>(&dh);
|
||||
let xdg_shell = XdgShellState::new::<Application>(&dh);
|
||||
let mut seat_state = SeatState::new();
|
||||
let shm = ShmState::new::<Application>(&dh, Vec::new());
|
||||
let data_device = DataDeviceState::new::<Application>(&dh);
|
||||
let mut seat = seat_state.new_wl_seat(&dh, "wayvr");
|
||||
|
||||
// TODO: Keyboard repeat delay and rate?
|
||||
let seat_keyboard = seat.add_keyboard(Default::default(), 100, 100)?;
|
||||
let seat_pointer = seat.add_pointer();
|
||||
|
||||
let queue_new_toplevel = SyncEventQueue::new();
|
||||
|
||||
let state = Application {
|
||||
compositor,
|
||||
xdg_shell,
|
||||
seat_state,
|
||||
shm,
|
||||
data_device,
|
||||
queue_new_toplevel: queue_new_toplevel.clone(),
|
||||
};
|
||||
|
||||
let time_start = get_millis();
|
||||
let egl_data = egl_data::EGLData::new()?;
|
||||
let smithay_display = smithay_wrapper::get_egl_display(&egl_data)?;
|
||||
let smithay_context = smithay_wrapper::get_egl_context(&egl_data, &smithay_display)?;
|
||||
let gles_renderer = unsafe { GlesRenderer::new(smithay_context)? };
|
||||
|
||||
Ok(Self {
|
||||
gles_renderer,
|
||||
time_start,
|
||||
manager: client::WayVRManager::new(state, display, seat_keyboard, seat_pointer)?,
|
||||
displays: DisplayVec::new(),
|
||||
egl_data: Rc::new(egl_data),
|
||||
wm: Rc::new(RefCell::new(window::WindowManager::new())),
|
||||
queue_new_toplevel,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tick_display(&mut self, display: display::DisplayHandle) -> anyhow::Result<()> {
|
||||
// millis since the start of wayvr
|
||||
let time_ms = get_millis() - self.time_start;
|
||||
|
||||
let display = self
|
||||
.displays
|
||||
.get(&display)
|
||||
.ok_or(anyhow::anyhow!("Invalid display handle"))?;
|
||||
|
||||
display.tick_render(&mut self.gles_renderer, time_ms)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tick_events(&mut self) -> anyhow::Result<()> {
|
||||
// Attach newly created toplevel surfaces to displayes
|
||||
while let Some((client_id, toplevel)) = self.queue_new_toplevel.read() {
|
||||
for client in &self.manager.clients {
|
||||
if client.client.id() == client_id {
|
||||
let window_handle = self.wm.borrow_mut().create_window(&toplevel);
|
||||
|
||||
if let Some(display) = self.displays.get_mut(&client.display_handle) {
|
||||
display.add_window(window_handle, &toplevel);
|
||||
} else {
|
||||
// This shouldn't happen, scream if it does
|
||||
log::error!("Could not attach window handle into display");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.manager.tick_wayland(&mut self.displays)
|
||||
}
|
||||
|
||||
pub fn tick_finish(&mut self) -> anyhow::Result<()> {
|
||||
self.gles_renderer.with_context(|gl| unsafe {
|
||||
gl.Flush();
|
||||
gl.Finish();
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn send_mouse_move(&mut self, display: display::DisplayHandle, x: u32, y: u32) {
|
||||
if let Some(display) = self.displays.get(&display) {
|
||||
display.send_mouse_move(&mut self.manager, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_mouse_down(&mut self, display: display::DisplayHandle, index: MouseIndex) {
|
||||
if let Some(display) = self.displays.get(&display) {
|
||||
display.send_mouse_down(&mut self.manager, index);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_mouse_up(&mut self, display: display::DisplayHandle, index: MouseIndex) {
|
||||
if let Some(display) = self.displays.get(&display) {
|
||||
display.send_mouse_up(&mut self.manager, index);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_mouse_scroll(&mut self, display: display::DisplayHandle, delta: f32) {
|
||||
if let Some(display) = self.displays.get(&display) {
|
||||
display.send_mouse_scroll(&mut self.manager, delta);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_key(&mut self, virtual_key: u32, down: bool) {
|
||||
self.manager.send_key(virtual_key, down);
|
||||
}
|
||||
|
||||
pub fn get_dmabuf_data(&self, display: display::DisplayHandle) -> Option<egl_data::DMAbufData> {
|
||||
self.displays
|
||||
.get(&display)
|
||||
.map(|display| display.dmabuf_data.clone())
|
||||
}
|
||||
|
||||
pub fn create_display(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> anyhow::Result<display::DisplayHandle> {
|
||||
let display = display::Display::new(
|
||||
self.wm.clone(),
|
||||
&mut self.gles_renderer,
|
||||
self.egl_data.clone(),
|
||||
self.manager.wayland_env.clone(),
|
||||
width,
|
||||
height,
|
||||
)?;
|
||||
Ok(self.displays.add(display))
|
||||
}
|
||||
|
||||
pub fn destroy_display(&mut self, handle: display::DisplayHandle) {
|
||||
self.displays.remove(&handle);
|
||||
}
|
||||
|
||||
pub fn spawn_process(
|
||||
&mut self,
|
||||
display: display::DisplayHandle,
|
||||
exec_path: &str,
|
||||
args: &[&str],
|
||||
env: &[(&str, &str)],
|
||||
) -> anyhow::Result<()> {
|
||||
if let Some(display) = self.displays.get_mut(&display) {
|
||||
display.spawn_process(exec_path, args, env)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
54
src/backend/wayvr/smithay_wrapper.rs
Normal file
54
src/backend/wayvr/smithay_wrapper.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use super::egl_data;
|
||||
use smithay::backend::{egl as smithay_egl, renderer::gles::ffi};
|
||||
|
||||
pub fn get_egl_display(data: &egl_data::EGLData) -> anyhow::Result<smithay_egl::EGLDisplay> {
|
||||
Ok(unsafe { smithay_egl::EGLDisplay::from_raw(data.display.as_ptr(), data.config.as_ptr())? })
|
||||
}
|
||||
|
||||
pub fn get_egl_context(
|
||||
data: &egl_data::EGLData,
|
||||
display: &smithay_egl::EGLDisplay,
|
||||
) -> anyhow::Result<smithay_egl::EGLContext> {
|
||||
let display_ptr = display.get_display_handle().handle;
|
||||
debug_assert!(display_ptr == data.display.as_ptr());
|
||||
let config_ptr = data.config.as_ptr();
|
||||
let context_ptr = data.context.as_ptr();
|
||||
Ok(unsafe { smithay_egl::EGLContext::from_raw(display_ptr, config_ptr, context_ptr)? })
|
||||
}
|
||||
|
||||
pub fn create_framebuffer_texture(
|
||||
gl: &ffi::Gles2,
|
||||
width: u32,
|
||||
height: u32,
|
||||
tex_format: u32,
|
||||
internal_format: u32,
|
||||
) -> u32 {
|
||||
unsafe {
|
||||
let mut tex = 0;
|
||||
gl.GenTextures(1, &mut tex);
|
||||
gl.BindTexture(ffi::TEXTURE_2D, tex);
|
||||
gl.TexParameteri(
|
||||
ffi::TEXTURE_2D,
|
||||
ffi::TEXTURE_MIN_FILTER,
|
||||
ffi::NEAREST as i32,
|
||||
);
|
||||
gl.TexParameteri(
|
||||
ffi::TEXTURE_2D,
|
||||
ffi::TEXTURE_MAG_FILTER,
|
||||
ffi::NEAREST as i32,
|
||||
);
|
||||
gl.TexImage2D(
|
||||
ffi::TEXTURE_2D,
|
||||
0,
|
||||
internal_format as i32,
|
||||
width as i32,
|
||||
height as i32,
|
||||
0,
|
||||
tex_format,
|
||||
ffi::UNSIGNED_BYTE,
|
||||
std::ptr::null(),
|
||||
);
|
||||
gl.BindTexture(ffi::TEXTURE_2D, 0);
|
||||
tex
|
||||
}
|
||||
}
|
||||
9
src/backend/wayvr/time.rs
Normal file
9
src/backend/wayvr/time.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
// Returns milliseconds since unix epoch
|
||||
pub fn get_millis() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as u64
|
||||
}
|
||||
69
src/backend/wayvr/window.rs
Normal file
69
src/backend/wayvr/window.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use smithay::wayland::shell::xdg::ToplevelSurface;
|
||||
|
||||
use crate::gen_id;
|
||||
|
||||
pub struct Window {
|
||||
pub pos_x: i32,
|
||||
pub pos_y: i32,
|
||||
pub size_x: u32,
|
||||
pub size_y: u32,
|
||||
pub toplevel: ToplevelSurface,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(toplevel: &ToplevelSurface) -> Self {
|
||||
Self {
|
||||
pos_x: 0,
|
||||
pos_y: 0,
|
||||
size_x: 0,
|
||||
size_y: 0,
|
||||
toplevel: toplevel.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_pos(&mut self, pos_x: i32, pos_y: i32) {
|
||||
self.pos_x = pos_x;
|
||||
self.pos_y = pos_y;
|
||||
}
|
||||
|
||||
pub fn set_size(&mut self, size_x: u32, size_y: u32) {
|
||||
self.toplevel.with_pending_state(|state| {
|
||||
//state.bounds = Some((size_x as i32, size_y as i32).into());
|
||||
state.size = Some((size_x as i32, size_y as i32).into());
|
||||
});
|
||||
self.toplevel.send_configure();
|
||||
|
||||
self.size_x = size_x;
|
||||
self.size_y = size_y;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowManager {
|
||||
pub windows: WindowVec,
|
||||
}
|
||||
|
||||
impl WindowManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
windows: WindowVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_window_handle(&self, toplevel: &ToplevelSurface) -> Option<WindowHandle> {
|
||||
for (idx, cell) in self.windows.vec.iter().enumerate() {
|
||||
if let Some(cell) = cell {
|
||||
let window = &cell.obj;
|
||||
if window.toplevel == *toplevel {
|
||||
return Some(WindowVec::get_handle(cell, idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn create_window(&mut self, toplevel: &ToplevelSurface) -> WindowHandle {
|
||||
self.windows.add(Window::new(toplevel))
|
||||
}
|
||||
}
|
||||
|
||||
gen_id!(WindowVec, Window, WindowCell, WindowHandle);
|
||||
@@ -553,7 +553,9 @@ impl WlxGraphics {
|
||||
Arc<winit::window::Window>,
|
||||
Arc<vulkano::swapchain::Surface>,
|
||||
)> {
|
||||
use vulkano::swapchain::Surface;
|
||||
use vulkano::{
|
||||
device::physical::PhysicalDeviceType, instance::InstanceCreateFlags, swapchain::Surface,
|
||||
};
|
||||
use winit::{event_loop::EventLoop, window::Window};
|
||||
|
||||
let event_loop = EventLoop::new().unwrap(); // want panic
|
||||
@@ -795,30 +797,17 @@ impl WlxGraphics {
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn dmabuf_texture(&self, frame: DmabufFrame) -> anyhow::Result<Arc<Image>> {
|
||||
pub fn dmabuf_texture_ex(
|
||||
&self,
|
||||
frame: DmabufFrame,
|
||||
tiling: ImageTiling,
|
||||
layouts: Vec<SubresourceLayout>,
|
||||
modifiers: Vec<u64>,
|
||||
) -> anyhow::Result<Arc<Image>> {
|
||||
let extent = [frame.format.width, frame.format.height, 1];
|
||||
|
||||
let format = fourcc_to_vk(frame.format.fourcc)?;
|
||||
|
||||
let mut tiling: ImageTiling = ImageTiling::Optimal;
|
||||
let mut modifiers: Vec<u64> = vec![];
|
||||
let mut layouts: Vec<SubresourceLayout> = vec![];
|
||||
|
||||
if frame.format.modifier != DRM_FORMAT_MOD_INVALID {
|
||||
(0..frame.num_planes).for_each(|i| {
|
||||
let plane = &frame.planes[i];
|
||||
layouts.push(SubresourceLayout {
|
||||
offset: plane.offset as _,
|
||||
size: 0,
|
||||
row_pitch: plane.stride as _,
|
||||
array_pitch: None,
|
||||
depth_pitch: None,
|
||||
});
|
||||
modifiers.push(frame.format.modifier);
|
||||
});
|
||||
tiling = ImageTiling::DrmFormatModifier;
|
||||
};
|
||||
|
||||
let image = unsafe {
|
||||
RawImage::new_unchecked(
|
||||
self.device.clone(),
|
||||
@@ -885,6 +874,29 @@ impl WlxGraphics {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dmabuf_texture(&self, frame: DmabufFrame) -> anyhow::Result<Arc<Image>> {
|
||||
let mut modifiers: Vec<u64> = vec![];
|
||||
let mut tiling: ImageTiling = ImageTiling::Optimal;
|
||||
let mut layouts: Vec<SubresourceLayout> = vec![];
|
||||
|
||||
if frame.format.modifier != DRM_FORMAT_MOD_INVALID {
|
||||
(0..frame.num_planes).for_each(|i| {
|
||||
let plane = &frame.planes[i];
|
||||
layouts.push(SubresourceLayout {
|
||||
offset: plane.offset as _,
|
||||
size: 0,
|
||||
row_pitch: plane.stride as _,
|
||||
array_pitch: None,
|
||||
depth_pitch: None,
|
||||
});
|
||||
modifiers.push(frame.format.modifier);
|
||||
});
|
||||
tiling = ImageTiling::DrmFormatModifier;
|
||||
};
|
||||
|
||||
self.dmabuf_texture_ex(frame, tiling, layouts, modifiers)
|
||||
}
|
||||
|
||||
pub fn render_texture(
|
||||
&self,
|
||||
width: u32,
|
||||
|
||||
@@ -111,6 +111,7 @@ impl<D, S> CanvasBuilder<D, S> {
|
||||
}
|
||||
|
||||
// Creates a sprite that highlights on pointer hover. Will not draw anything until set_sprite is called.
|
||||
#[allow(dead_code)]
|
||||
pub fn sprite_interactive(&mut self, x: f32, y: f32, w: f32, h: f32) -> &mut Control<D, S> {
|
||||
let idx = self.canvas.controls.len();
|
||||
self.canvas.controls.push(Control {
|
||||
|
||||
@@ -336,6 +336,7 @@ impl<D, S> Control<D, S> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn render_sprite_hl(
|
||||
&self,
|
||||
canvas: &CanvasData<D>,
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
get_key_type, KeyModifier, KeyType, VirtualKey, XkbKeymap, ALT, CTRL, KEYS_TO_MODS, META,
|
||||
NUM_LOCK, SHIFT, SUPER,
|
||||
},
|
||||
state::AppState,
|
||||
state::{AppState, KeyboardFocus},
|
||||
};
|
||||
use glam::{vec2, vec3a, Affine2, Vec4};
|
||||
use once_cell::sync::Lazy;
|
||||
@@ -31,6 +31,36 @@ const AUTO_RELEASE_MODS: [KeyModifier; 5] = [SHIFT, CTRL, ALT, SUPER, META];
|
||||
|
||||
pub const KEYBOARD_NAME: &str = "kbd";
|
||||
|
||||
fn send_key(app: &mut AppState, key: VirtualKey, down: bool) {
|
||||
log::info!(
|
||||
"Sending key {:?} to {:?} (down: {})",
|
||||
key,
|
||||
app.keyboard_focus,
|
||||
down
|
||||
);
|
||||
match app.keyboard_focus {
|
||||
KeyboardFocus::PhysicalScreen => {
|
||||
app.hid_provider.send_key(key, down);
|
||||
}
|
||||
KeyboardFocus::WayVR =>
|
||||
{
|
||||
#[cfg(feature = "wayvr")]
|
||||
if let Some(wayvr) = &app.wayvr {
|
||||
wayvr.borrow_mut().send_key(key as u32, down);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_modifiers(app: &mut AppState, mods: u8) {
|
||||
match app.keyboard_focus {
|
||||
KeyboardFocus::PhysicalScreen => {
|
||||
app.hid_provider.set_modifiers(mods);
|
||||
}
|
||||
KeyboardFocus::WayVR => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_keyboard<O>(
|
||||
app: &AppState,
|
||||
keymap: Option<XkbKeymap>,
|
||||
@@ -197,22 +227,22 @@ fn key_press(
|
||||
|
||||
if let PointerMode::Right = mode {
|
||||
data.modifiers |= SHIFT;
|
||||
app.hid_provider.set_modifiers(data.modifiers);
|
||||
set_modifiers(app, data.modifiers);
|
||||
}
|
||||
|
||||
app.hid_provider.send_key(*vk, true);
|
||||
send_key(app, *vk, true);
|
||||
*pressed = true;
|
||||
}
|
||||
Some(KeyButtonData::Modifier { modifier, sticky }) => {
|
||||
*sticky = data.modifiers & *modifier == 0;
|
||||
data.modifiers |= *modifier;
|
||||
data.key_click(app);
|
||||
app.hid_provider.set_modifiers(data.modifiers);
|
||||
set_modifiers(app, data.modifiers);
|
||||
}
|
||||
Some(KeyButtonData::Macro { verbs }) => {
|
||||
data.key_click(app);
|
||||
for (vk, press) in verbs {
|
||||
app.hid_provider.send_key(*vk, *press);
|
||||
send_key(app, *vk, *press);
|
||||
}
|
||||
}
|
||||
Some(KeyButtonData::Exec { program, args, .. }) => {
|
||||
@@ -236,20 +266,20 @@ fn key_release(
|
||||
) {
|
||||
match control.state.as_mut() {
|
||||
Some(KeyButtonData::Key { vk, pressed }) => {
|
||||
app.hid_provider.send_key(*vk, false);
|
||||
send_key(app, *vk, false);
|
||||
*pressed = false;
|
||||
|
||||
for m in AUTO_RELEASE_MODS.iter() {
|
||||
if data.modifiers & *m != 0 {
|
||||
data.modifiers &= !*m;
|
||||
app.hid_provider.set_modifiers(data.modifiers);
|
||||
set_modifiers(app, data.modifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(KeyButtonData::Modifier { modifier, sticky }) => {
|
||||
if !*sticky {
|
||||
data.modifiers &= !*modifier;
|
||||
app.hid_provider.set_modifiers(data.modifiers);
|
||||
set_modifiers(app, data.modifiers);
|
||||
}
|
||||
}
|
||||
Some(KeyButtonData::Exec {
|
||||
@@ -493,7 +523,7 @@ impl OverlayRenderer for KeyboardBackend {
|
||||
}
|
||||
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||
self.canvas.data_mut().modifiers = 0;
|
||||
app.hid_provider.set_modifiers(0);
|
||||
set_modifiers(app, 0);
|
||||
self.canvas.pause(app)
|
||||
}
|
||||
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||
|
||||
@@ -6,3 +6,6 @@ pub mod mirror;
|
||||
pub mod screen;
|
||||
pub mod toast;
|
||||
pub mod watch;
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
pub mod wayvr;
|
||||
|
||||
@@ -58,7 +58,7 @@ use crate::{
|
||||
fourcc_to_vk, WlxCommandBuffer, WlxPipeline, WlxPipelineLegacy, DRM_FORMAT_MOD_INVALID,
|
||||
},
|
||||
hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
|
||||
state::{AppSession, AppState, ScreenMeta},
|
||||
state::{AppSession, AppState, KeyboardFocus, ScreenMeta},
|
||||
};
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
@@ -712,6 +712,7 @@ fn create_screen_state(
|
||||
|
||||
OverlayState {
|
||||
name: name.clone(),
|
||||
keyboard_focus: Some(KeyboardFocus::PhysicalScreen),
|
||||
grabbable: true,
|
||||
recenter: true,
|
||||
anchored: true,
|
||||
|
||||
272
src/overlays/wayvr.rs
Normal file
272
src/overlays/wayvr.rs
Normal file
@@ -0,0 +1,272 @@
|
||||
use glam::{vec3a, Affine2};
|
||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||
use vulkano::image::SubresourceLayout;
|
||||
use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane};
|
||||
|
||||
use crate::{
|
||||
backend::{
|
||||
input::{self, InteractionHandler},
|
||||
overlay::{ui_transform, OverlayData, OverlayRenderer, OverlayState, SplitOverlayBackend},
|
||||
wayvr,
|
||||
},
|
||||
graphics::WlxGraphics,
|
||||
state::{self, KeyboardFocus},
|
||||
};
|
||||
|
||||
pub struct WayVRContext {
|
||||
wayvr: Rc<RefCell<wayvr::WayVR>>,
|
||||
display: wayvr::display::DisplayHandle,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WayVRProcess<'a> {
|
||||
pub exec_path: &'a str,
|
||||
pub args: &'a [&'a str],
|
||||
pub env: &'a [(&'a str, &'a str)],
|
||||
}
|
||||
|
||||
impl WayVRContext {
|
||||
pub fn new(
|
||||
wvr: Rc<RefCell<wayvr::WayVR>>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
processes: &[WayVRProcess],
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut wayvr = wvr.borrow_mut();
|
||||
|
||||
let display = wayvr.create_display(width, height)?;
|
||||
|
||||
for process in processes {
|
||||
wayvr.spawn_process(display, process.exec_path, process.args, process.env)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
wayvr: wvr.clone(),
|
||||
display,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WayVRInteractionHandler {
|
||||
context: Rc<RefCell<WayVRContext>>,
|
||||
mouse_transform: Affine2,
|
||||
}
|
||||
|
||||
impl WayVRInteractionHandler {
|
||||
pub fn new(context: Rc<RefCell<WayVRContext>>, mouse_transform: Affine2) -> Self {
|
||||
Self {
|
||||
context,
|
||||
mouse_transform,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractionHandler for WayVRInteractionHandler {
|
||||
fn on_hover(
|
||||
&mut self,
|
||||
_app: &mut state::AppState,
|
||||
hit: &input::PointerHit,
|
||||
) -> Option<input::Haptics> {
|
||||
let ctx = self.context.borrow();
|
||||
|
||||
let pos = self.mouse_transform.transform_point2(hit.uv);
|
||||
let x = ((pos.x * ctx.width as f32) as i32).max(0);
|
||||
let y = ((pos.y * ctx.height as f32) as i32).max(0);
|
||||
|
||||
let ctx = self.context.borrow();
|
||||
ctx.wayvr
|
||||
.borrow_mut()
|
||||
.send_mouse_move(ctx.display, x as u32, y as u32);
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn on_left(&mut self, _app: &mut state::AppState, _pointer: usize) {
|
||||
// Ignore event
|
||||
}
|
||||
|
||||
fn on_pointer(&mut self, _app: &mut state::AppState, hit: &input::PointerHit, pressed: bool) {
|
||||
if let Some(index) = match hit.mode {
|
||||
input::PointerMode::Left => Some(wayvr::MouseIndex::Left),
|
||||
input::PointerMode::Middle => Some(wayvr::MouseIndex::Center),
|
||||
input::PointerMode::Right => Some(wayvr::MouseIndex::Right),
|
||||
_ => {
|
||||
// Unknown pointer event, ignore
|
||||
None
|
||||
}
|
||||
} {
|
||||
let ctx = self.context.borrow();
|
||||
let mut wayvr = ctx.wayvr.borrow_mut();
|
||||
if pressed {
|
||||
wayvr.send_mouse_down(ctx.display, index);
|
||||
} else {
|
||||
wayvr.send_mouse_up(ctx.display, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_scroll(&mut self, _app: &mut state::AppState, _hit: &input::PointerHit, delta: f32) {
|
||||
let ctx = self.context.borrow();
|
||||
ctx.wayvr.borrow_mut().send_mouse_scroll(ctx.display, delta);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WayVRRenderer {
|
||||
dmabuf_image: Option<Arc<vulkano::image::Image>>,
|
||||
view: Option<Arc<vulkano::image::view::ImageView>>,
|
||||
context: Rc<RefCell<WayVRContext>>,
|
||||
graphics: Arc<WlxGraphics>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl WayVRRenderer {
|
||||
pub fn new(
|
||||
app: &mut state::AppState,
|
||||
wvr: Rc<RefCell<wayvr::WayVR>>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
processes: &[WayVRProcess],
|
||||
) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
context: Rc::new(RefCell::new(WayVRContext::new(
|
||||
wvr, width, height, processes,
|
||||
)?)),
|
||||
width,
|
||||
height,
|
||||
dmabuf_image: None,
|
||||
view: None,
|
||||
graphics: app.graphics.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WayVRRenderer {
|
||||
fn ensure_dmabuf(&mut self, data: wayvr::egl_data::DMAbufData) -> anyhow::Result<()> {
|
||||
if self.dmabuf_image.is_none() {
|
||||
// First init
|
||||
let mut planes = [FramePlane::default(); 4];
|
||||
planes[0].fd = Some(data.fd);
|
||||
planes[0].offset = data.offset as u32;
|
||||
planes[0].stride = data.stride;
|
||||
|
||||
let frame = DmabufFrame {
|
||||
format: FrameFormat {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
fourcc: FourCC {
|
||||
value: data.mod_info.fourcc,
|
||||
},
|
||||
modifier: data.mod_info.modifiers[0], /* possibly not proper? */
|
||||
},
|
||||
num_planes: 1,
|
||||
planes,
|
||||
};
|
||||
|
||||
let layouts: Vec<SubresourceLayout> = vec![SubresourceLayout {
|
||||
offset: data.offset as _,
|
||||
size: 0,
|
||||
row_pitch: data.stride as _,
|
||||
array_pitch: None,
|
||||
depth_pitch: None,
|
||||
}];
|
||||
|
||||
let tex = self.graphics.dmabuf_texture_ex(
|
||||
frame,
|
||||
vulkano::image::ImageTiling::DrmFormatModifier,
|
||||
layouts,
|
||||
data.mod_info.modifiers,
|
||||
)?;
|
||||
self.dmabuf_image = Some(tex.clone());
|
||||
self.view = Some(vulkano::image::view::ImageView::new_default(tex).unwrap());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OverlayRenderer for WayVRRenderer {
|
||||
fn init(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pause(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resume(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
|
||||
let ctx = self.context.borrow();
|
||||
let mut wayvr = ctx.wayvr.borrow_mut();
|
||||
|
||||
wayvr.tick_display(ctx.display)?;
|
||||
|
||||
let dmabuf_data = wayvr
|
||||
.get_dmabuf_data(ctx.display)
|
||||
.ok_or(anyhow::anyhow!("Failed to fetch dmabuf data"))?
|
||||
.clone();
|
||||
|
||||
drop(wayvr);
|
||||
drop(ctx);
|
||||
self.ensure_dmabuf(dmabuf_data.clone())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn view(&mut self) -> Option<Arc<vulkano::image::view::ImageView>> {
|
||||
self.view.clone()
|
||||
}
|
||||
|
||||
fn extent(&mut self) -> Option<[u32; 3]> {
|
||||
self.view.as_ref().map(|view| view.image().extent())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn create_wayvr<O>(
|
||||
app: &mut state::AppState,
|
||||
width: u32,
|
||||
height: u32,
|
||||
processes: &[WayVRProcess],
|
||||
) -> anyhow::Result<OverlayData<O>>
|
||||
where
|
||||
O: Default,
|
||||
{
|
||||
let transform = ui_transform(&[width, height]);
|
||||
|
||||
let state = OverlayState {
|
||||
name: format!("WayVR Screen ({}x{})", width, height).into(),
|
||||
keyboard_focus: Some(KeyboardFocus::WayVR),
|
||||
want_visible: true,
|
||||
interactable: true,
|
||||
recenter: true,
|
||||
grabbable: true,
|
||||
spawn_scale: 1.0,
|
||||
spawn_point: vec3a(0.0, -0.5, 0.0),
|
||||
interaction_transform: transform,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let wayvr = app.get_wayvr()?;
|
||||
|
||||
let renderer = WayVRRenderer::new(app, wayvr, width, height, processes)?;
|
||||
let context = renderer.context.clone();
|
||||
|
||||
let backend = Box::new(SplitOverlayBackend {
|
||||
renderer: Box::new(renderer),
|
||||
interaction: Box::new(WayVRInteractionHandler::new(context, Affine2::IDENTITY)),
|
||||
});
|
||||
|
||||
Ok(OverlayData {
|
||||
state,
|
||||
backend,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
38
src/state.rs
38
src/state.rs
@@ -1,13 +1,18 @@
|
||||
use std::{io::Cursor, sync::Arc};
|
||||
|
||||
use anyhow::bail;
|
||||
use glam::Affine3A;
|
||||
use idmap::IdMap;
|
||||
use rodio::{Decoder, OutputStream, OutputStreamHandle, Source};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::{io::Cursor, sync::Arc};
|
||||
use vulkano::image::view::ImageView;
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
use crate::backend::wayvr::WayVR;
|
||||
|
||||
use crate::{
|
||||
backend::{input::InputState, overlay::OverlayID, task::TaskContainer},
|
||||
config::{AStrMap, GeneralConfig},
|
||||
@@ -22,6 +27,14 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum KeyboardFocus {
|
||||
PhysicalScreen,
|
||||
|
||||
#[allow(dead_code)] // Not available if "wayvr" feature is disabled
|
||||
WayVR, // (for now without wayland window id data, it's handled internally),
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
pub fc: FontCache,
|
||||
pub session: AppSession,
|
||||
@@ -33,6 +46,10 @@ pub struct AppState {
|
||||
pub screens: SmallVec<[ScreenMeta; 8]>,
|
||||
pub anchor: Affine3A,
|
||||
pub sprites: AStrMap<Arc<ImageView>>,
|
||||
pub keyboard_focus: KeyboardFocus,
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
pub wayvr: Option<Rc<RefCell<WayVR>>>, // Dynamically created if requested
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -84,8 +101,25 @@ impl AppState {
|
||||
screens: smallvec![],
|
||||
anchor: Affine3A::IDENTITY,
|
||||
sprites: AStrMap::new(),
|
||||
keyboard_focus: KeyboardFocus::PhysicalScreen,
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
wayvr: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
#[allow(dead_code)]
|
||||
pub fn get_wayvr(&mut self) -> anyhow::Result<Rc<RefCell<WayVR>>> {
|
||||
if let Some(wvr) = &self.wayvr {
|
||||
Ok(wvr.clone())
|
||||
} else {
|
||||
log::info!("Initializing WayVR");
|
||||
let wayvr = Rc::new(RefCell::new(WayVR::new()?));
|
||||
self.wayvr = Some(wayvr.clone());
|
||||
Ok(wayvr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppSession {
|
||||
|
||||
Reference in New Issue
Block a user