Integrate WayVR into wlx directly

This commit is contained in:
Aleksander
2024-10-18 20:21:23 +02:00
committed by galister
parent f84d57dc42
commit edfa77e07c
27 changed files with 2256 additions and 59 deletions

231
Cargo.lock generated
View File

@@ -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",

View File

@@ -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 = []

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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?

View File

@@ -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));

View File

@@ -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,

View 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
View 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
View 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,
);
}

View 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);

View 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,
],
)?)
}
}
}

View 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,
>;

View 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
View 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
View 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(())
}
}

View 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
}
}

View 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
}

View 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);

View File

@@ -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,

View File

@@ -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 {

View File

@@ -336,6 +336,7 @@ impl<D, S> Control<D, S> {
Ok(())
}
#[allow(dead_code)]
pub(super) fn render_sprite_hl(
&self,
canvas: &CanvasData<D>,

View File

@@ -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<()> {

View File

@@ -6,3 +6,6 @@ pub mod mirror;
pub mod screen;
pub mod toast;
pub mod watch;
#[cfg(feature = "wayvr")]
pub mod wayvr;

View File

@@ -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
View 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()
})
}

View File

@@ -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 {