Skip to content

Commit

Permalink
Integrate WayVR into wlx directly
Browse files Browse the repository at this point in the history
  • Loading branch information
olekolek1000 authored and galister committed Oct 18, 2024
1 parent f84d57d commit edfa77e
Show file tree
Hide file tree
Showing 27 changed files with 2,254 additions and 57 deletions.
227 changes: 208 additions & 19 deletions Cargo.lock

Large diffs are not rendered by default.

23 changes: 21 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"]
Expand All @@ -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 = []
15 changes: 13 additions & 2 deletions src/backend/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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() {
Expand Down
3 changes: 3 additions & 0 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
10 changes: 10 additions & 0 deletions src/backend/openvr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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?
Expand Down
10 changes: 10 additions & 0 deletions src/backend/openxr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
7 changes: 6 additions & 1 deletion src/backend/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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,
Expand All @@ -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,
Expand Down
37 changes: 37 additions & 0 deletions src/backend/wayvr/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 178 additions & 0 deletions src/backend/wayvr/client.rs
Original file line number Diff line number Diff line change
@@ -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))
}
Loading

0 comments on commit edfa77e

Please sign in to comment.