rewrite built-in wayland compositor egl → vulkan

This commit is contained in:
galister
2025-12-25 21:26:38 +09:00
parent 3b6acb3673
commit 40dc33410d
34 changed files with 923 additions and 3051 deletions

View File

@@ -26,7 +26,7 @@ use crate::{
manifest::{install_manifest, uninstall_manifest},
overlay::OpenVrOverlayData,
},
task::{InputTask, OpenVrTask, OverlayTask, TaskType},
task::{OpenVrTask, OverlayTask, TaskType},
},
config::save_state,
graphics::{GpuFutures, init_openvr_graphics},
@@ -42,9 +42,6 @@ use crate::{
},
};
#[cfg(feature = "wayvr")]
use crate::{backend::wayvr::WayVRAction, overlays::wayvr::wayvr_action};
pub mod helpers;
pub mod input;
pub mod lines;
@@ -157,51 +154,50 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
}
FRAME_COUNTER.fetch_add(1, Ordering::Relaxed);
// extremely cursed
const VREVENT_QUIT: u32 = EVREventType::VREvent_Quit as u32;
const VREVENT_TRACKED_ACTIVATED: u32 = EVREventType::VREvent_TrackedDeviceActivated as u32;
const VREVENT_TRACKED_DEACTIVATED: u32 =
EVREventType::VREvent_TrackedDeviceDeactivated as u32;
const VREVENT_TRACKED_UPDATED: u32 = EVREventType::VREvent_TrackedDeviceUpdated as u32;
const VREVENT_SEATED_ZERO: u32 = EVREventType::VREvent_SeatedZeroPoseReset as u32;
const VREVENT_STANDING_ZERO: u32 = EVREventType::VREvent_StandingZeroPoseReset as u32;
const VREVENT_CHAPERONE_CHANGED: u32 =
EVREventType::VREvent_ChaperoneUniverseHasChanged as u32;
const VREVENT_SCENE_APP_CHANGED: u32 = EVREventType::VREvent_SceneApplicationChanged as u32;
const VREVENT_IPD_CHANGED: u32 = EVREventType::VREvent_IpdChanged as u32;
{
// extremely cursed
const EV_QUIT: u32 = EVREventType::VREvent_Quit as u32;
const EV_DEV_ACTIVATED: u32 = EVREventType::VREvent_TrackedDeviceActivated as u32;
const EV_DEV_DEACTIVATED: u32 = EVREventType::VREvent_TrackedDeviceDeactivated as u32;
const EV_DEV_UPDATED: u32 = EVREventType::VREvent_TrackedDeviceUpdated as u32;
const EV_SEAT_ZERO: u32 = EVREventType::VREvent_SeatedZeroPoseReset as u32;
const EV_STAND_ZERO: u32 = EVREventType::VREvent_StandingZeroPoseReset as u32;
const EV_CHAP_CHANGED: u32 = EVREventType::VREvent_ChaperoneUniverseHasChanged as u32;
const EV_SCENE_CHANGED: u32 = EVREventType::VREvent_SceneApplicationChanged as u32;
const EV_IPD_CHANGED: u32 = EVREventType::VREvent_IpdChanged as u32;
while let Some(event) = system_mgr.poll_next_event() {
match event.event_type {
VREVENT_QUIT => {
log::warn!("Received quit event, shutting down.");
break 'main_loop;
}
VREVENT_TRACKED_ACTIVATED
| VREVENT_TRACKED_DEACTIVATED
| VREVENT_TRACKED_UPDATED => {
next_device_update = Instant::now();
}
VREVENT_SEATED_ZERO
| VREVENT_STANDING_ZERO
| VREVENT_CHAPERONE_CHANGED
| VREVENT_SCENE_APP_CHANGED => {
playspace.playspace_changed(&mut compositor_mgr, &mut chaperone_mgr);
}
VREVENT_IPD_CHANGED => {
if let Ok(ipd) = system_mgr.get_tracked_device_property::<f32>(
TrackedDeviceIndex::HMD,
ETrackedDeviceProperty::Prop_UserIpdMeters_Float,
) {
let ipd = (ipd * 1000.0).round();
if (ipd - app.input_state.ipd).abs() > 0.05 {
log::info!("IPD: {:.1} mm -> {:.1} mm", app.input_state.ipd, ipd);
Toast::new(ToastTopic::IpdChange, "IPD".into(), format!("{ipd:.1} mm"))
.submit(&mut app);
}
app.input_state.ipd = ipd;
while let Some(event) = system_mgr.poll_next_event() {
match event.event_type {
EV_QUIT => {
log::warn!("Received quit event, shutting down.");
break 'main_loop;
}
EV_DEV_ACTIVATED | EV_DEV_DEACTIVATED | EV_DEV_UPDATED => {
next_device_update = Instant::now();
}
EV_SEAT_ZERO | EV_STAND_ZERO | EV_CHAP_CHANGED | EV_SCENE_CHANGED => {
playspace.playspace_changed(&mut compositor_mgr, &mut chaperone_mgr);
}
EV_IPD_CHANGED => {
if let Ok(ipd) = system_mgr.get_tracked_device_property::<f32>(
TrackedDeviceIndex::HMD,
ETrackedDeviceProperty::Prop_UserIpdMeters_Float,
) {
let ipd = (ipd * 1000.0).round();
if (ipd - app.input_state.ipd).abs() > 0.05 {
log::info!("IPD: {:.1} mm -> {:.1} mm", app.input_state.ipd, ipd);
Toast::new(
ToastTopic::IpdChange,
"IPD".into(),
format!("{ipd:.1} mm"),
)
.submit(&mut app);
}
app.input_state.ipd = ipd;
}
}
_ => {}
}
_ => {}
}
}
@@ -235,9 +231,7 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
}
},
#[cfg(feature = "wayvr")]
TaskType::WayVR(action) => {
wayvr_action(&mut app, &mut overlays, &action);
}
TaskType::WayVR(_action) => { /* TODO */ }
}
}
@@ -267,9 +261,7 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
.pointers
.iter()
.any(|p| p.now.toggle_dashboard && !p.before.toggle_dashboard)
{
wayvr_action(&mut app, &mut overlays, &WayVRAction::ToggleDashboard);
}
{ /* TODO */ }
overlays
.values_mut()
@@ -337,11 +329,6 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
.values_mut()
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &app.gfx));
#[cfg(feature = "wayvr")]
if let Some(wayland_server) = app.wayland_server.as_ref() {
wayland_server.borrow_mut().data.tick_finish()?;
}
// chaperone
} // main_loop

View File

@@ -35,9 +35,6 @@ use crate::{
},
};
#[cfg(feature = "wayvr")]
use crate::{backend::wayvr::WayVRAction, overlays::wayvr::wayvr_action};
mod blocker;
mod helpers;
mod input;
@@ -149,8 +146,8 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
};
let pointer_lines = [
lines.allocate(&xr_state, &mut app)?,
lines.allocate(&xr_state, &mut app)?,
lines.allocate(&xr_state, &app)?,
lines.allocate(&xr_state, &app)?,
];
let watch_id = overlays.lookup(WATCH_NAME).unwrap(); // want panic
@@ -303,9 +300,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
.pointers
.iter()
.any(|p| p.now.toggle_dashboard && !p.before.toggle_dashboard)
{
wayvr_action(&mut app, &mut overlays, &WayVRAction::ToggleDashboard);
}
{ /* TODO */ }
watch_fade(&mut app, overlays.mut_by_id(watch_id).unwrap()); // want panic
if let Some(ref mut space_mover) = playspace {
@@ -458,11 +453,6 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
}
// End layer composition
#[cfg(feature = "wayvr")]
if let Some(wayland_server) = app.wayland_server.as_ref() {
wayland_server.borrow_mut().data.tick_finish()?;
}
// Begin layer submit
layers.sort_by(|a, b| b.0.total_cmp(&a.0));
@@ -502,10 +492,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
}
#[cfg(feature = "openvr")]
TaskType::OpenVR(_) => {}
#[cfg(feature = "wayvr")]
TaskType::WayVR(action) => {
wayvr_action(&mut app, &mut overlays, &action);
}
TaskType::WayVR(_action) => { /* TODO */ }
}
}

View File

@@ -14,12 +14,11 @@ use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask};
use super::{
ProcessWayVREnv,
comp::{self, ClientState},
display, process,
process,
};
pub struct WayVRClient {
pub client: wayland_server::Client,
pub display_handle: display::DisplayHandle,
pub pid: u32,
}
@@ -103,10 +102,13 @@ impl WayVRCompositor {
});
}
pub fn cleanup_handles(&mut self) {
self.state.cleanup();
}
fn accept_connection(
&mut self,
stream: UnixStream,
displays: &mut display::DisplayVec,
processes: &mut process::ProcessVec,
) -> anyhow::Result<()> {
let client = self
@@ -126,16 +128,12 @@ impl WayVRCompositor {
{
// Find process with matching auth key
if process.auth_key.as_str() == auth_key {
// Check if display handle is valid
if displays.get(&process.display_handle).is_some() {
// Add client
self.add_client(WayVRClient {
client,
display_handle: process.display_handle,
pid: creds.pid as u32,
});
return Ok(());
}
// Add client
self.add_client(WayVRClient {
client,
pid: creds.pid as u32,
});
return Ok(());
}
}
}
@@ -158,13 +156,9 @@ impl WayVRCompositor {
Ok(())
}
fn accept_connections(
&mut self,
displays: &mut display::DisplayVec,
processes: &mut process::ProcessVec,
) -> anyhow::Result<()> {
fn accept_connections(&mut self, processes: &mut process::ProcessVec) -> anyhow::Result<()> {
if let Some(stream) = self.listener.accept()?
&& let Err(e) = self.accept_connection(stream, displays, processes)
&& let Err(e) = self.accept_connection(stream, processes)
{
log::error!("Failed to accept connection: {e}");
}
@@ -172,12 +166,8 @@ impl WayVRCompositor {
Ok(())
}
pub fn tick_wayland(
&mut self,
displays: &mut display::DisplayVec,
processes: &mut process::ProcessVec,
) -> anyhow::Result<()> {
if let Err(e) = self.accept_connections(displays, processes) {
pub fn tick_wayland(&mut self, processes: &mut process::ProcessVec) -> anyhow::Result<()> {
if let Err(e) = self.accept_connections(processes) {
log::error!("accept_connections failed: {e}");
}

View File

@@ -1,18 +1,17 @@
use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::renderer::ImportDma;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::utils::on_commit_buffer_handler;
use smithay::backend::renderer::{BufferType, buffer_type};
use smithay::input::{Seat, SeatHandler, SeatState};
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
use smithay::reexports::wayland_server;
use smithay::reexports::wayland_server::Resource;
use smithay::reexports::wayland_server::protocol::{wl_buffer, wl_seat, wl_surface};
use smithay::reexports::wayland_server::protocol::{wl_buffer, wl_output, wl_seat, wl_surface};
use smithay::wayland::buffer::BufferHandler;
use smithay::wayland::dmabuf::{
DmabufFeedback, DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier,
DmabufFeedback, DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier, get_dmabuf,
};
use smithay::wayland::output::OutputHandler;
use smithay::wayland::shm::{ShmHandler, ShmState};
use smithay::wayland::shm::{ShmHandler, ShmState, with_buffer_contents};
use smithay::wayland::single_pixel_buffer::get_single_pixel_buffer;
use smithay::{
delegate_compositor, delegate_data_device, delegate_dmabuf, delegate_output, delegate_seat,
delegate_shm, delegate_xdg_shell,
@@ -23,7 +22,7 @@ use std::sync::{Arc, Mutex};
use smithay::utils::Serial;
use smithay::wayland::compositor::{
self, SurfaceAttributes, TraversalAction, with_surface_tree_downward,
self, BufferAssignment, SurfaceAttributes, TraversalAction, with_surface_tree_downward,
};
use smithay::wayland::selection::SelectionHandler;
@@ -37,12 +36,14 @@ use wayland_server::Client;
use wayland_server::backend::{ClientData, ClientId, DisconnectReason};
use wayland_server::protocol::wl_surface::WlSurface;
use crate::backend::wayvr::SurfaceBufWithImage;
use crate::backend::wayvr::image_importer::ImageImporter;
use crate::ipc::event_queue::SyncEventQueue;
use super::WayVRTask;
pub struct Application {
pub gles_renderer: GlesRenderer,
pub image_importer: ImageImporter,
pub dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
pub compositor: compositor::CompositorState,
pub xdg_shell: XdgShellState,
@@ -57,6 +58,10 @@ impl Application {
pub fn check_redraw(&mut self, surface: &WlSurface) -> bool {
self.redraw_requests.remove(&surface.id())
}
pub fn cleanup(&mut self) {
self.image_importer.cleanup();
}
}
impl compositor::CompositorHandler for Application {
@@ -72,7 +77,74 @@ impl compositor::CompositorHandler for Application {
}
fn commit(&mut self, surface: &WlSurface) {
on_commit_buffer_handler::<Self>(surface);
smithay::wayland::compositor::with_states(surface, |states| {
let mut guard = states.cached_state.get::<SurfaceAttributes>();
let attrs = guard.current();
match attrs.buffer.take() {
Some(BufferAssignment::NewBuffer(buffer)) => {
let current = SurfaceBufWithImage::get_from_surface(states);
if current.is_none_or(|c| c.buffer != buffer) {
match buffer_type(&buffer) {
Some(BufferType::Dma) => {
let dmabuf = get_dmabuf(&buffer).unwrap(); // always Ok due to buffer_type
if let Ok(image) =
self.image_importer.get_or_import_dmabuf(dmabuf.clone())
{
let sbwi = SurfaceBufWithImage {
image,
buffer,
transform: wl_transform_to_frame_transform(
attrs.buffer_transform,
),
scale: attrs.buffer_scale,
};
sbwi.apply_to_surface(states);
}
}
Some(BufferType::Shm) => {
with_buffer_contents(&buffer, |data, size, buf| {
if let Ok(image) =
self.image_importer.import_shm(data, size, buf)
{
let sbwi = SurfaceBufWithImage {
image,
buffer: buffer.clone(),
transform: wl_transform_to_frame_transform(
attrs.buffer_transform,
),
scale: attrs.buffer_scale,
};
sbwi.apply_to_surface(states);
}
});
}
Some(BufferType::SinglePixel) => {
let spb = get_single_pixel_buffer(&buffer).unwrap(); // always Ok
if let Ok(image) = self.image_importer.import_spb(spb) {
let sbwi = SurfaceBufWithImage {
image,
buffer,
transform: wl_transform_to_frame_transform(
// does this even matter
attrs.buffer_transform,
),
scale: attrs.buffer_scale,
};
sbwi.apply_to_surface(states);
}
}
Some(other) => log::warn!("Unsupported wl_buffer format: {other:?}"),
None => { /* don't draw anything */ }
}
}
}
Some(BufferAssignment::Removed) => {}
None => {}
}
});
self.redraw_requests.insert(surface.id());
}
}
@@ -198,7 +270,7 @@ impl DmabufHandler for Application {
dmabuf: Dmabuf,
notifier: ImportNotifier,
) {
if self.gles_renderer.import_dmabuf(&dmabuf, None).is_ok() {
if self.image_importer.get_or_import_dmabuf(dmabuf).is_ok() {
let _ = notifier.successful::<Self>();
} else {
notifier.failed();
@@ -235,3 +307,19 @@ pub fn send_frames_surface_tree(surface: &wl_surface::WlSurface, time: u32) {
|_, _, &()| true,
);
}
fn wl_transform_to_frame_transform(
transform: wl_output::Transform,
) -> wlx_capture::frame::Transform {
match transform {
wl_output::Transform::Normal => wlx_capture::frame::Transform::Normal,
wl_output::Transform::_90 => wlx_capture::frame::Transform::Rotated90,
wl_output::Transform::_180 => wlx_capture::frame::Transform::Rotated180,
wl_output::Transform::_270 => wlx_capture::frame::Transform::Rotated270,
wl_output::Transform::Flipped => wlx_capture::frame::Transform::Flipped,
wl_output::Transform::Flipped90 => wlx_capture::frame::Transform::Flipped90,
wl_output::Transform::Flipped180 => wlx_capture::frame::Transform::Flipped180,
wl_output::Transform::Flipped270 => wlx_capture::frame::Transform::Flipped270,
_ => wlx_capture::frame::Transform::Undefined,
}
}

View File

@@ -1,606 +0,0 @@
use std::{cell::RefCell, rc::Rc, sync::Arc};
use smithay::{
backend::renderer::{
Bind, Color32F, Frame, Renderer,
element::{
Kind,
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree},
},
gles::{GlesRenderer, GlesTexture, ffi},
utils::draw_render_elements,
},
input,
utils::{Logical, Point, Rectangle, Size, Transform},
wayland::shell::xdg::ToplevelSurface,
};
use wayvr_ipc::packet_server;
use crate::{
backend::wayvr::time::get_millis, gen_id, ipc::event_queue::SyncEventQueue,
subsystem::hid::WheelDelta, windowing::OverlayID,
};
use super::{
BlitMethod, WayVRSignal, client::WayVRCompositor, comp::send_frames_surface_tree, egl_data,
process, smithay_wrapper, time, window,
};
fn generate_auth_key() -> String {
let uuid = uuid::Uuid::new_v4();
uuid.to_string()
}
#[derive(Debug)]
pub struct DisplayWindow {
pub window_handle: window::WindowHandle,
pub toplevel: ToplevelSurface,
pub process_handle: process::ProcessHandle,
}
pub struct SpawnProcessResult {
pub auth_key: String,
pub child: std::process::Child,
}
#[derive(Debug)]
pub enum DisplayTask {
ProcessCleanup(process::ProcessHandle),
}
const MAX_DISPLAY_SIZE: u16 = 8192;
#[derive(Debug)]
pub struct Display {
// Display info stuff
pub width: u16,
pub height: u16,
pub name: String,
pub visible: bool,
pub layout: packet_server::WvrDisplayWindowLayout,
pub overlay_id: Option<OverlayID>,
pub wants_redraw: bool,
pub rendered_frame_count: u32,
pub primary: bool,
pub wm: Rc<RefCell<window::WindowManager>>,
pub displayed_windows: Vec<DisplayWindow>,
wayland_env: super::WaylandEnv,
last_pressed_time_ms: u64,
pub no_windows_since: Option<u64>,
// Render data stuff
gles_texture: GlesTexture, // TODO: drop texture
egl_image: khronos_egl::Image,
egl_data: Rc<egl_data::EGLData>,
pub render_data: egl_data::RenderData,
pub tasks: SyncEventQueue<DisplayTask>,
}
impl Drop for Display {
fn drop(&mut self) {
let _ = self
.egl_data
.egl
.destroy_image(self.egl_data.display, self.egl_image);
}
}
pub struct DisplayInitParams<'a> {
pub wm: Rc<RefCell<window::WindowManager>>,
pub config: &'a super::Config,
pub renderer: &'a mut GlesRenderer,
pub egl_data: Rc<egl_data::EGLData>,
pub wayland_env: super::WaylandEnv,
pub width: u16,
pub height: u16,
pub name: &'a str,
pub primary: bool,
}
impl Display {
pub fn new(params: DisplayInitParams) -> anyhow::Result<Self> {
if params.width > MAX_DISPLAY_SIZE {
anyhow::bail!(
"display width ({}) is larger than {}",
params.width,
MAX_DISPLAY_SIZE
);
}
if params.height > MAX_DISPLAY_SIZE {
anyhow::bail!(
"display height ({}) is larger than {}",
params.height,
MAX_DISPLAY_SIZE
);
}
let tex_format = ffi::RGBA;
let internal_format = ffi::RGBA8;
let tex_id = params.renderer.with_context(|gl| {
smithay_wrapper::create_framebuffer_texture(
gl,
u32::from(params.width),
u32::from(params.height),
tex_format,
internal_format,
)
})?;
let egl_image = params.egl_data.create_egl_image(tex_id)?;
let render_data = match params.config.blit_method {
BlitMethod::Dmabuf => match params.egl_data.create_dmabuf_data(&egl_image) {
Ok(dmabuf_data) => egl_data::RenderData::Dmabuf(dmabuf_data),
Err(e) => {
log::error!(
"create_dmabuf_data failed: {e:?}. Using software blitting (This will be slow!)"
);
egl_data::RenderData::Software(None)
}
},
BlitMethod::Software => egl_data::RenderData::Software(None),
};
let opaque = false;
let size = (i32::from(params.width), i32::from(params.height)).into();
let gles_texture = unsafe {
GlesTexture::from_raw(params.renderer, Some(tex_format), opaque, tex_id, size)
};
Ok(Self {
egl_data: params.egl_data,
width: params.width,
height: params.height,
name: String::from(params.name),
primary: params.primary,
wayland_env: params.wayland_env,
wm: params.wm,
displayed_windows: Vec::new(),
render_data,
egl_image,
gles_texture,
last_pressed_time_ms: 0,
no_windows_since: None,
overlay_id: None,
tasks: SyncEventQueue::new(),
visible: true,
wants_redraw: true,
rendered_frame_count: 0,
layout: packet_server::WvrDisplayWindowLayout::Tiling,
})
}
pub fn as_packet(&self, handle: DisplayHandle) -> packet_server::WvrDisplay {
packet_server::WvrDisplay {
width: self.width,
height: self.height,
name: self.name.clone(),
visible: self.visible,
handle: handle.as_packet(),
}
}
pub fn add_window(
&mut self,
window_handle: window::WindowHandle,
process_handle: process::ProcessHandle,
toplevel: &ToplevelSurface,
) {
log::debug!("Attaching toplevel surface into display");
self.displayed_windows.push(DisplayWindow {
window_handle,
process_handle,
toplevel: toplevel.clone(),
});
self.reposition_windows();
}
pub fn remove_window(&mut self, window_handle: window::WindowHandle) {
self.displayed_windows
.retain(|disp| disp.window_handle != window_handle);
}
pub fn reposition_windows(&mut self) {
let window_count = self.displayed_windows.len();
match &self.layout {
packet_server::WvrDisplayWindowLayout::Tiling => {
let mut i = 0;
for win in &mut self.displayed_windows {
if let Some(window) = self.wm.borrow_mut().windows.get_mut(&win.window_handle) {
if !window.visible {
continue;
}
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 * f32::from(self.width)) as i32;
let right = (d_next * f32::from(self.width)) as i32;
window.set_pos(left, 0);
window.set_size((right - left) as u32, u32::from(self.height));
i += 1;
}
}
}
packet_server::WvrDisplayWindowLayout::Stacking(opts) => {
let do_margins = |margins: &packet_server::Margins, window: &mut window::Window| {
let top = i32::from(margins.top);
let bottom = i32::from(self.height) - i32::from(margins.bottom);
let left = i32::from(margins.left);
let right = i32::from(self.width) - i32::from(margins.right);
let width = right - left;
let height = bottom - top;
if width < 0 || height < 0 {
return; // wrong parameters, do nothing!
}
window.set_pos(left, top);
window.set_size(width as u32, height as u32);
};
let mut i = 0;
for win in &mut self.displayed_windows {
if let Some(window) = self.wm.borrow_mut().windows.get_mut(&win.window_handle) {
if !window.visible {
continue;
}
do_margins(
if i == 0 {
&opts.margins_first
} else {
&opts.margins_rest
},
window,
);
i += 1;
}
}
}
}
}
pub fn tick(
&mut self,
config: &super::Config,
handle: &DisplayHandle,
signals: &mut SyncEventQueue<WayVRSignal>,
) {
if self.visible {
if !self.displayed_windows.is_empty() {
self.no_windows_since = None;
} else if let Some(auto_hide_delay) = config.auto_hide_delay
&& let Some(s) = self.no_windows_since
&& s + u64::from(auto_hide_delay) < get_millis()
{
// Auto-hide after specific time
signals.send(WayVRSignal::DisplayVisibility(*handle, false));
}
}
while let Some(task) = self.tasks.read() {
match task {
DisplayTask::ProcessCleanup(process_handle) => {
let count = self.displayed_windows.len();
self.displayed_windows
.retain(|win| win.process_handle != process_handle);
log::info!(
"Cleanup finished for display \"{}\". Current window count: {}",
self.name,
self.displayed_windows.len()
);
self.no_windows_since = Some(get_millis());
if count != self.displayed_windows.len() {
signals.send(WayVRSignal::BroadcastStateChanged(
packet_server::WvrStateChanged::WindowRemoved,
));
}
self.reposition_windows();
}
}
}
}
#[allow(clippy::significant_drop_tightening)]
pub fn tick_render(&mut self, renderer: &mut GlesRenderer, time_ms: u64) -> anyhow::Result<()> {
let mut gles_texture = self.gles_texture.clone();
let mut target = renderer.bind(&mut gles_texture)?;
let size = Size::from((i32::from(self.width), i32::from(self.height)));
let damage: Rectangle<i32, smithay::utils::Physical> = Rectangle::from_size(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.window_handle) {
if !window.visible {
return vec![];
}
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(&mut target, size, Transform::Normal)?;
let clear_color = if self.displayed_windows.is_empty() {
Color32F::new(0.5, 0.5, 0.5, 0.5)
} else {
Color32F::new(0.0, 0.0, 0.0, 0.0)
};
frame.clear(clear_color, &[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);
}
if let egl_data::RenderData::Software(_) = &self.render_data {
// Read OpenGL texture into memory. Slow!
let pixel_data = renderer.with_context(|gl| unsafe {
gl.BindTexture(ffi::TEXTURE_2D, self.gles_texture.tex_id());
let len = self.width as usize * self.height as usize * 4;
let mut data: Box<[u8]> = Box::new_uninit_slice(len).assume_init();
gl.ReadPixels(
0,
0,
i32::from(self.width),
i32::from(self.height),
ffi::RGBA,
ffi::UNSIGNED_BYTE,
data.as_mut_ptr().cast(),
);
let data: Arc<[u8]> = Arc::from(data);
data
})?;
self.render_data =
egl_data::RenderData::Software(Some(egl_data::RenderSoftwarePixelsData {
data: pixel_data,
width: self.width,
height: self.height,
}));
}
self.rendered_frame_count += 1;
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().rev() {
if let Some(window) = wm.windows.get(&cell.window_handle) {
if !window.visible {
continue;
}
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.window_handle);
}
}
}
None
}
pub const fn trigger_rerender(&mut self) {
self.wants_redraw = true;
}
pub fn set_visible(&mut self, visible: bool) {
log::info!("Display \"{}\" visible: {}", self.name.as_str(), visible);
if self.visible == visible {
return;
}
self.visible = visible;
if visible {
self.no_windows_since = None;
self.trigger_rerender();
}
}
pub fn set_layout(&mut self, layout: packet_server::WvrDisplayWindowLayout) {
log::info!("Display \"{}\" layout: {:?}", self.name.as_str(), layout);
if self.layout == layout {
return;
}
self.layout = layout;
self.trigger_rerender();
self.reposition_windows();
}
pub fn send_mouse_move(
&self,
config: &super::Config,
manager: &mut WayVRCompositor,
x: u32,
y: u32,
) {
let current_ms = time::get_millis();
if self.last_pressed_time_ms + u64::from(config.click_freeze_time_ms) > current_ms {
return;
}
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((
f64::from(x as i32 - window.pos_x),
f64::from(y as i32 - window.pos_y),
));
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);
}
}
}
const 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(&mut self, manager: &mut WayVRCompositor, index: super::MouseIndex) {
// Change keyboard focus to pressed window
let loc = manager.seat_pointer.current_location();
self.last_pressed_time_ms = time::get_millis();
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();
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(manager: &mut WayVRCompositor, 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(manager: &mut WayVRCompositor, delta: WheelDelta) {
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: (f64::from(delta.x), f64::from(-delta.y)),
v120: Some((0, (delta.y * -64.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)],
working_dir: Option<&str>,
) -> anyhow::Result<SpawnProcessResult> {
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);
if let Some(working_dir) = working_dir {
cmd.current_dir(working_dir);
}
for e in env {
cmd.env(e.0, e.1);
}
match cmd.spawn() {
Ok(child) => Ok(SpawnProcessResult { auth_key, child }),
Err(e) => {
anyhow::bail!(
"Failed to launch process with path \"{exec_path}\": {e}. Make sure your exec path exists."
);
}
}
}
}
gen_id!(DisplayVec, Display, DisplayCell, DisplayHandle);
impl DisplayHandle {
pub const fn from_packet(handle: packet_server::WvrDisplayHandle) -> Self {
Self {
generation: handle.generation,
idx: handle.idx,
}
}
pub const fn as_packet(&self) -> packet_server::WvrDisplayHandle {
packet_server::WvrDisplayHandle {
idx: self.idx,
generation: self.generation,
}
}
}

View File

@@ -1,315 +0,0 @@
use std::sync::Arc;
use crate::backend::wayvr::egl_ex::{
PFNEGLGETPLATFORMDISPLAYEXTPROC, PFNEGLQUERYDMABUFFORMATSEXTPROC,
PFNEGLQUERYDMABUFMODIFIERSEXTPROC,
};
use super::egl_ex;
use anyhow::Context;
#[derive(Debug)]
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(Debug, Clone)]
pub struct DMAbufModifierInfo {
pub modifiers: Vec<u64>,
pub fourcc: u32,
}
#[derive(Debug, Clone)]
pub struct RenderDMAbufData {
pub fd: i32,
pub stride: i32,
pub offset: i32,
pub mod_info: DMAbufModifierInfo,
}
#[derive(Debug, Clone)]
pub struct RenderSoftwarePixelsData {
pub data: Arc<[u8]>,
pub width: u16,
pub height: u16,
}
#[derive(Debug, Clone)]
pub enum RenderData {
Dmabuf(RenderDMAbufData),
Software(Option<RenderSoftwarePixelsData>), // will be set if the next image data is available
}
fn load_egl_func(
egl: &khronos_egl::Instance<khronos_egl::Static>,
func_name: &str,
) -> anyhow::Result<extern "system" fn()> {
let raw_fn = egl
.get_proc_address(func_name)
.ok_or_else(|| anyhow::anyhow!("Required EGL function {func_name} not found"))?;
Ok(raw_fn)
}
fn get_disp(
egl: &khronos_egl::Instance<khronos_egl::Static>,
) -> anyhow::Result<khronos_egl::Display> {
unsafe {
if let Ok(func) = load_egl_func(egl, "eglGetPlatformDisplayEXT") {
let egl_get_platform_display_ext =
bind_egl_function!(PFNEGLGETPLATFORMDISPLAYEXTPROC, &func);
let display_ext = egl_get_platform_display_ext(
egl_ex::EGL_PLATFORM_WAYLAND_EXT, // platform
std::ptr::null_mut(), // void *native_display
std::ptr::null_mut(), // EGLint *attrib_list
);
if display_ext.is_null() {
log::warn!("eglGetPlatformDisplayEXT failed, using eglGetDisplay instead");
} else {
return Ok(khronos_egl::Display::from_ptr(display_ext));
}
}
egl
.get_display(khronos_egl::DEFAULT_DISPLAY)
.context(
"Both eglGetPlatformDisplayEXT and eglGetDisplay failed. This shouldn't happen unless you don't have any display manager running. Cannot continue, check your EGL installation."
)
}
}
impl EGLData {
pub fn new() -> anyhow::Result<Self> {
let egl = khronos_egl::Instance::new(khronos_egl::Static);
let display = get_disp(&egl)?;
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)?
.context("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)?;
log::debug!("eglMakeCurrent");
egl.make_current(display, None, None, Some(context))?;
Ok(Self {
egl,
display,
config,
context,
})
}
fn query_dmabuf_mod_info(&self) -> anyhow::Result<DMAbufModifierInfo> {
let target_fourcc = 0x3432_4258; //XB24
unsafe {
let egl_query_dmabuf_formats_ext = bind_egl_function!(
PFNEGLQUERYDMABUFFORMATSEXTPROC,
&load_egl_func(&self.egl, "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(),
&raw mut num_formats,
);
// Retrieve format 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(),
&raw 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,
&load_egl_func(&self.egl, "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(),
&raw 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(),
&raw mut num_mods,
);
if mods[0] == 0xFFFF_FFFF_FFFF_FFFF {
anyhow::bail!("modifier is -1")
}
log::trace!("Modifier list:");
for modifier in &mods {
log::trace!("{modifier:#x}");
}
// 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; 2] = [
0x200_0000_2086_bf04, /* AMD RX 7800 XT, Navi32 */
0x200_0000_1866_bf04, /* AMD RX 7600 XT, Navi33 */
];
for modifier in &mod_whitelist {
if mods.contains(modifier) {
log::warn!("Using whitelisted dmabuf tiling modifier: {modifier:#x}");
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<RenderDMAbufData> {
use egl_ex::PFNEGLEXPORTDMABUFIMAGEMESAPROC as FUNC;
unsafe {
let egl_export_dmabuf_image_mesa =
bind_egl_function!(FUNC, &load_egl_func(&self.egl, "eglExportDMABUFImageMESA")?);
let mut fds: [i32; 3] = [0; 3];
let mut strides: [i32; 3] = [0; 3];
let mut offsets: [i32; 3] = [0; 3];
let ret = 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(),
);
if ret != khronos_egl::TRUE {
anyhow::bail!("eglExportDMABUFImageMESA failed with return code {ret}");
}
if fds[0] <= 0 {
anyhow::bail!("fd is <=0 (got {})", fds[0]);
}
// many planes in RGB data?
if fds[1] != 0 || strides[1] != 0 || offsets[1] != 0 {
anyhow::bail!("multi-planar data received, packed RGB expected");
}
if strides[0] < 0 {
anyhow::bail!("strides is < 0");
}
if offsets[0] < 0 {
anyhow::bail!("offsets is < 0");
}
let mod_info = self.query_dmabuf_mod_info()?;
Ok(RenderDMAbufData {
fd: fds[0],
stride: strides[0],
offset: offsets[0],
mod_info,
})
}
}
pub fn create_egl_image(&self, gl_tex_id: 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::ATTRIB_NONE],
)?)
}
}
}

View File

@@ -1,49 +0,0 @@
#![allow(clippy::all)]
pub const EGL_PLATFORM_WAYLAND_EXT: khronos_egl::Enum = 0x31D8;
// eglGetPlatformDisplayEXT
// https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_platform_base.txt
pub type PFNEGLGETPLATFORMDISPLAYEXTPROC = Option<
unsafe extern "C" fn(
platform: khronos_egl::Enum,
native_display: *mut std::ffi::c_void,
attrib_list: *mut khronos_egl::Enum,
) -> khronos_egl::EGLDisplay,
>;
// eglExportDMABUFImageMESA
// https://registry.khronos.org/EGL/extensions/MESA/EGL_MESA_image_dma_buf_export.txt
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
// https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt
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
// https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt
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,111 @@
use std::{collections::HashMap, os::fd::AsRawFd, sync::Arc};
use anyhow::Context;
use smithay::{
backend::allocator::{
Buffer,
dmabuf::{Dmabuf, WeakDmabuf},
},
wayland::{
shm::{BufferData, shm_format_to_fourcc},
single_pixel_buffer::SinglePixelBufferUserData,
},
};
use vulkano::{format::Format, image::view::ImageView};
use wgui::gfx::WGfx;
use wlx_capture::frame::{DmabufFrame, FrameFormat, Transform};
use crate::graphics::dmabuf::{WGfxDmabuf, fourcc_to_vk};
pub struct ImageImporter {
gfx: Arc<WGfx>,
dmabufs: HashMap<WeakDmabuf, Arc<ImageView>>,
}
impl ImageImporter {
pub fn new(gfx: Arc<WGfx>) -> Self {
Self {
gfx,
dmabufs: HashMap::new(),
}
}
pub fn import_spb(
&mut self,
spb: &SinglePixelBufferUserData,
) -> anyhow::Result<Arc<ImageView>> {
let mut cmd_buf = self.gfx.create_xfer_command_buffer(
vulkano::command_buffer::CommandBufferUsage::OneTimeSubmit,
)?;
let rgba = spb.rgba8888();
let image = cmd_buf.upload_image(1, 1, Format::R8G8B8A8_UNORM, &rgba)?;
cmd_buf.build_and_execute_now()?; //TODO: async
let image_view = ImageView::new_default(image)?;
Ok(image_view)
}
pub fn import_shm(
&mut self,
data: *const u8,
size: usize,
bd: BufferData,
) -> anyhow::Result<Arc<ImageView>> {
let mut cmd_buf = self.gfx.create_xfer_command_buffer(
vulkano::command_buffer::CommandBufferUsage::OneTimeSubmit,
)?;
let fourcc = shm_format_to_fourcc(bd.format)
.with_context(|| format!("Could not conver {:?} to fourcc", bd.format))?;
let format = fourcc_to_vk(fourcc)
.with_context(|| format!("Could not convert {fourcc} to vkFormat"))?;
let data = unsafe { std::slice::from_raw_parts(data, size) };
let image = cmd_buf.upload_image(bd.width as _, bd.height as _, format, data)?;
cmd_buf.build_and_execute_now()?; //TODO: async
let image_view = ImageView::new_default(image)?;
Ok(image_view)
}
pub fn get_or_import_dmabuf(&mut self, dmabuf: Dmabuf) -> anyhow::Result<Arc<ImageView>> {
let mut frame = DmabufFrame {
format: FrameFormat {
width: dmabuf.width(),
height: dmabuf.height(),
drm_format: dmabuf.format(),
transform: Transform::Undefined,
},
num_planes: dmabuf.num_planes(),
planes: Default::default(),
mouse: None,
};
for (i, handle) in dmabuf.handles().enumerate() {
// even if the original OwnedFd is dropped, the vkImage will hold reference on the DMA-buf
frame.planes[i].fd = Some(handle.as_raw_fd());
}
for (i, offset) in dmabuf.offsets().enumerate() {
frame.planes[i].offset = offset;
}
for (i, stride) in dmabuf.strides().enumerate() {
frame.planes[i].stride = stride as _;
}
let image = self.gfx.dmabuf_texture(frame)?;
let image_view = ImageView::new_default(image)?;
self.dmabufs.insert(dmabuf.weak(), image_view.clone());
Ok(image_view)
}
pub fn cleanup(&mut self) {
self.dmabufs.retain(|k, _| !k.is_gone());
}
}

View File

@@ -1,29 +1,22 @@
pub mod client;
mod comp;
pub mod display;
pub mod egl_data;
mod egl_ex;
mod handle;
mod image_importer;
pub mod process;
mod smithay_wrapper;
mod time;
pub mod window;
use anyhow::Context;
use comp::Application;
use display::{Display, DisplayInitParams, DisplayVec};
use process::ProcessVec;
use serde::Deserialize;
use slotmap::SecondaryMap;
use smallvec::SmallVec;
use smithay::{
backend::{
egl,
renderer::{ImportDma, gles::GlesRenderer},
},
input::{SeatState, keyboard::XkbConfig},
output::{Mode, Output},
reexports::wayland_server::{self, backend::ClientId},
reexports::wayland_server::{self, backend::ClientId, protocol::wl_buffer},
wayland::{
compositor,
compositor::{self, SurfaceData},
dmabuf::{DmabufFeedbackBuilder, DmabufState},
selection::data_device::DataDeviceState,
shell::xdg::{ToplevelSurface, XdgShellState},
@@ -33,20 +26,28 @@ use smithay::{
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
mem::MaybeUninit,
rc::Rc,
sync::Arc,
time::{Duration, Instant},
};
use time::get_millis;
use wayvr_ipc::{
packet_client::{self},
packet_server,
};
use vulkano::image::view::ImageView;
use wayvr_ipc::packet_server;
use wgui::gfx::WGfx;
use wlx_capture::frame::Transform;
use xkbcommon::xkb;
use crate::{
backend::{
task::{OverlayTask, TaskType},
wayvr::{image_importer::ImageImporter, window::Window},
},
graphics::WGfxExtras,
ipc::{event_queue::SyncEventQueue, ipc_server, signal::WayVRSignal},
state::AppState,
subsystem::hid::{MODS_TO_KEYS, WheelDelta},
windowing::{OverlayID, OverlaySelector},
};
const STR_INVALID_HANDLE_DISP: &str = "Invalid display handle";
@@ -109,17 +110,17 @@ pub struct Config {
pub struct WayVRState {
time_start: u64,
pub displays: display::DisplayVec,
pub manager: client::WayVRCompositor,
pub wm: Rc<RefCell<window::WindowManager>>,
egl_data: Rc<egl_data::EGLData>,
pub processes: process::ProcessVec,
pub config: Config,
dashboard_display: Option<display::DisplayHandle>,
pub tasks: SyncEventQueue<WayVRTask>,
ticks: u64,
cur_modifiers: u8,
signals: SyncEventQueue<WayVRSignal>,
mouse_freeze: Instant,
window_to_overlay: HashMap<window::WindowHandle, OverlayID>,
overlay_to_window: SecondaryMap<OverlayID, window::WindowHandle>,
}
pub struct WayVR {
@@ -134,15 +135,16 @@ pub enum MouseIndex {
pub enum TickTask {
NewExternalProcess(ExternalProcessRequest), // Call WayVRCompositor::add_client after receiving this message
NewDisplay(
packet_client::WvrDisplayCreateParams,
Option<display::DisplayHandle>, /* existing handle? */
),
}
impl WayVR {
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn new(config: Config, signals: SyncEventQueue<WayVRSignal>) -> anyhow::Result<Self> {
pub fn new(
gfx: Arc<WGfx>,
gfx_extras: &WGfxExtras,
config: Config,
signals: SyncEventQueue<WayVRSignal>,
) -> anyhow::Result<Self> {
log::info!("Initializing WayVR");
let display: wayland_server::Display<Application> = wayland_server::Display::new()?;
let dh = display.handle();
@@ -153,8 +155,8 @@ impl WayVR {
let data_device = DataDeviceState::new::<Application>(&dh);
let mut seat = seat_state.new_wl_seat(&dh, "wayvr");
let dummy_width = 1280;
let dummy_height = 720;
let dummy_width = 1920;
let dummy_height = 1080;
let dummy_milli_hz = 60000; /* refresh rate in millihertz */
let output = Output::new(
@@ -176,53 +178,38 @@ impl WayVR {
output.change_current_state(Some(mode), None, None, None);
output.set_preferred(mode);
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 render_node = egl::EGLDevice::device_for_display(&smithay_display)
.and_then(|device| device.try_get_render_node());
let gles_renderer = unsafe { GlesRenderer::new(smithay_context)? };
let dmabuf_default_feedback = match render_node {
Ok(Some(node)) => {
let dmabuf_formats = gles_renderer.dmabuf_formats();
let dmabuf_default_feedback =
DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats)
.build()
.unwrap();
Some(dmabuf_default_feedback)
}
Ok(None) => {
log::warn!("dmabuf: Failed to query render node");
None
}
Err(err) => {
log::warn!("dmabuf: Failed to get egl device for display: {err}");
None
}
let main_device = {
let (major, minor) = gfx_extras.drm_device.as_ref().context("No DRM device!")?;
libc::makedev(*major as _, *minor as _)
};
let dmabuf_state = dmabuf_default_feedback.map_or_else(
|| {
let dmabuf_formats = gles_renderer.dmabuf_formats();
let mut dmabuf_state = DmabufState::new();
let dmabuf_global =
dmabuf_state.create_global::<Application>(&display.handle(), dmabuf_formats);
(dmabuf_state, dmabuf_global, None)
},
|default_feedback| {
let mut dmabuf_state = DmabufState::new();
let dmabuf_global = dmabuf_state
.create_global_with_default_feedback::<Application>(
&display.handle(),
&default_feedback,
);
(dmabuf_state, dmabuf_global, Some(default_feedback))
},
);
// this will throw a compile-time error if smithay's drm-fourcc is out of sync with wlx-capture's
let mut formats: Vec<smithay::backend::allocator::Format> = vec![];
for f in gfx_extras.drm_formats.iter() {
formats.push(f.clone());
}
let dmabuf_state = DmabufFeedbackBuilder::new(main_device, formats.clone())
.build()
.map_or_else(
|_| {
log::info!("Falling back to zwp_linux_dmabuf_v1 version 3.");
let mut dmabuf_state = DmabufState::new();
let dmabuf_global =
dmabuf_state.create_global::<Application>(&display.handle(), formats);
(dmabuf_state, dmabuf_global, None)
},
|default_feedback| {
let mut dmabuf_state = DmabufState::new();
let dmabuf_global = dmabuf_state
.create_global_with_default_feedback::<Application>(
&display.handle(),
&default_feedback,
);
(dmabuf_state, dmabuf_global, Some(default_feedback))
},
);
let seat_keyboard = seat.add_keyboard(
XkbConfig::default(),
@@ -233,7 +220,10 @@ impl WayVR {
let tasks = SyncEventQueue::new();
let dma_importer = ImageImporter::new(gfx);
let state = Application {
image_importer: dma_importer,
compositor,
xdg_shell,
seat_state,
@@ -242,7 +232,6 @@ impl WayVR {
wayvr_tasks: tasks.clone(),
redraw_requests: HashSet::new(),
dmabuf_state,
gles_renderer,
};
let time_start = get_millis();
@@ -250,48 +239,21 @@ impl WayVR {
let state = WayVRState {
time_start,
manager: client::WayVRCompositor::new(state, display, seat_keyboard, seat_pointer)?,
displays: DisplayVec::new(),
processes: ProcessVec::new(),
egl_data: Rc::new(egl_data),
wm: Rc::new(RefCell::new(window::WindowManager::new())),
config,
dashboard_display: None,
ticks: 0,
tasks,
cur_modifiers: 0,
signals,
mouse_freeze: Instant::now(),
window_to_overlay: HashMap::new(),
overlay_to_window: SecondaryMap::new(),
};
Ok(Self { state })
}
pub fn render_display(&mut self, display: display::DisplayHandle) -> anyhow::Result<bool> {
let display = self
.state
.displays
.get_mut(&display)
.context(STR_INVALID_HANDLE_DISP)?;
/* Buffer warm-up is required, always two first calls of this function are always rendered */
if !display.wants_redraw && display.rendered_frame_count >= 2 {
// Nothing changed, do not render
return Ok(false);
}
if !display.visible {
// Display is invisible, do not render
return Ok(false);
}
// millis since the start of wayvr
let time_ms = get_millis() - self.state.time_start;
display.tick_render(&mut self.state.manager.state.gles_renderer, time_ms)?;
display.wants_redraw = false;
Ok(true)
}
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn tick_events(&mut self, app: &mut AppState) -> anyhow::Result<Vec<TickTask>> {
let mut tasks: Vec<TickTask> = Vec::new();
@@ -303,43 +265,17 @@ impl WayVR {
signals: &app.wayvr_signals,
});
// Check for redraw events
for (_, disp) in self.state.displays.iter_mut() {
for disp_window in &disp.displayed_windows {
if self
.state
.manager
.state
.check_redraw(disp_window.toplevel.wl_surface())
{
disp.wants_redraw = true;
}
}
}
// Tick all child processes
let mut to_remove: SmallVec<[(process::ProcessHandle, display::DisplayHandle); 2]> =
SmallVec::new();
let mut to_remove: SmallVec<[process::ProcessHandle; 2]> = SmallVec::new();
for (handle, process) in self.state.processes.iter_mut() {
if !process.is_running() {
to_remove.push((handle, process.display_handle()));
to_remove.push(handle);
}
}
for (p_handle, disp_handle) in &to_remove {
for p_handle in &to_remove {
self.state.processes.remove(p_handle);
if let Some(display) = self.state.displays.get_mut(disp_handle) {
display
.tasks
.send(display::DisplayTask::ProcessCleanup(*p_handle));
display.wants_redraw = true;
}
}
for (handle, display) in self.state.displays.iter_mut() {
display.tick(&self.state.config, &handle, &mut app.wayvr_signals);
}
if !to_remove.is_empty() {
@@ -374,16 +310,10 @@ impl WayVR {
.state
.wm
.borrow_mut()
.create_window(client.display_handle, &toplevel);
.create_window(&toplevel, process_handle);
let Some(display) = self.state.displays.get_mut(&client.display_handle)
else {
// This shouldn't happen, scream if it does
log::error!("Could not attach window handle into display");
continue;
};
//TODO: create overlay
display.add_window(window_handle, process_handle, &toplevel);
app.wayvr_signals.send(WayVRSignal::BroadcastStateChanged(
packet_server::WvrStateChanged::WindowCreated,
));
@@ -401,18 +331,15 @@ impl WayVR {
continue;
};
let Some(display) = self.state.displays.get_mut(&client.display_handle)
else {
log::warn!("DropToplevel: Couldn't find matching display");
continue;
};
if let Some(oid) = self.state.window_to_overlay.get(&window_handle) {
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Drop(
OverlaySelector::Id(*oid),
)));
}
display.remove_window(window_handle);
wm.remove_window(window_handle);
drop(wm);
display.reposition_windows();
}
}
WayVRTask::ProcessTerminationRequest(process_handle) => {
@@ -423,12 +350,11 @@ impl WayVR {
}
}
self.state
.manager
.tick_wayland(&mut self.state.displays, &mut self.state.processes)?;
self.state.manager.tick_wayland(&mut self.state.processes)?;
if self.state.ticks.is_multiple_of(200) {
self.state.manager.cleanup_clients();
self.state.manager.cleanup_handles();
}
self.state.ticks += 1;
@@ -436,44 +362,6 @@ impl WayVR {
Ok(tasks)
}
pub fn tick_finish(&mut self) -> anyhow::Result<()> {
self.state
.manager
.state
.gles_renderer
.with_context(|gl| unsafe {
gl.Flush();
gl.Finish();
})?;
Ok(())
}
#[allow(dead_code)]
pub fn get_primary_display(displays: &DisplayVec) -> Option<display::DisplayHandle> {
for (idx, cell) in displays.vec.iter().enumerate() {
if let Some(cell) = cell
&& cell.obj.primary
{
return Some(DisplayVec::get_handle(cell, idx));
}
}
None
}
pub fn get_display_by_name(
displays: &DisplayVec,
name: &str,
) -> Option<display::DisplayHandle> {
for (idx, cell) in displays.vec.iter().enumerate() {
if let Some(cell) = cell
&& cell.obj.name == name
{
return Some(DisplayVec::get_handle(cell, idx));
}
}
None
}
pub fn terminate_process(&mut self, process_handle: process::ProcessHandle) {
self.state
.tasks
@@ -482,24 +370,30 @@ impl WayVR {
}
impl WayVRState {
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(&self.config, &mut self.manager, x, y);
pub fn send_mouse_move(&mut self, handle: window::WindowHandle, x: u32, y: u32) {
if self.mouse_freeze > Instant::now() {
return;
}
if let Some(window) = self.wm.borrow_mut().windows.get_mut(&handle) {
window.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_mut(&display) {
display.send_mouse_down(&mut self.manager, index);
pub fn send_mouse_down(&mut self, handle: window::WindowHandle, index: MouseIndex) {
self.mouse_freeze =
Instant::now() + Duration::from_millis(self.config.click_freeze_time_ms as _);
if let Some(window) = self.wm.borrow_mut().windows.get_mut(&handle) {
window.send_mouse_down(&mut self.manager, index);
}
}
pub fn send_mouse_up(&mut self, index: MouseIndex) {
Display::send_mouse_up(&mut self.manager, index);
Window::send_mouse_up(&mut self.manager, index);
}
pub fn send_mouse_scroll(&mut self, delta: WheelDelta) {
Display::send_mouse_scroll(&mut self.manager, delta);
Window::send_mouse_scroll(&mut self.manager, delta);
}
pub fn send_key(&mut self, virtual_key: u32, down: bool) {
@@ -523,125 +417,9 @@ impl WayVRState {
self.cur_modifiers = modifiers;
}
pub fn set_display_visible(&mut self, display: display::DisplayHandle, visible: bool) {
if let Some(display) = self.displays.get_mut(&display) {
display.set_visible(visible);
}
}
pub fn set_display_layout(
&mut self,
display: display::DisplayHandle,
layout: packet_server::WvrDisplayWindowLayout,
) {
if let Some(display) = self.displays.get_mut(&display) {
display.set_layout(layout);
}
}
pub fn get_render_data(
&self,
display: display::DisplayHandle,
) -> Option<&egl_data::RenderData> {
self.displays
.get(&display)
.map(|display| &display.render_data)
}
pub fn create_display(
&mut self,
width: u16,
height: u16,
name: &str,
primary: bool,
) -> anyhow::Result<display::DisplayHandle> {
let display = display::Display::new(DisplayInitParams {
wm: self.wm.clone(),
egl_data: self.egl_data.clone(),
renderer: &mut self.manager.state.gles_renderer,
wayland_env: self.manager.wayland_env.clone(),
config: &self.config,
width,
height,
name,
primary,
})?;
let handle = self.displays.add(display);
self.signals.send(WayVRSignal::BroadcastStateChanged(
packet_server::WvrStateChanged::DisplayCreated,
));
Ok(handle)
}
pub fn destroy_display(&mut self, handle: display::DisplayHandle) -> anyhow::Result<()> {
let Some(display) = self.displays.get(&handle) else {
anyhow::bail!("Display not found");
};
if let Some(overlay_id) = display.overlay_id {
self.signals.send(WayVRSignal::DropOverlay(overlay_id));
} else {
log::warn!("Destroying display without OverlayID set"); // This shouldn't happen, but log it anyways.
}
let mut process_names = Vec::<String>::new();
for (_, process) in self.processes.iter_mut() {
if process.display_handle() == handle {
process_names.push(process.get_name());
}
}
if !display.displayed_windows.is_empty() || !process_names.is_empty() {
anyhow::bail!(
"Display is not empty. Attached processes: {}",
process_names.join(", ")
);
}
self.manager.cleanup_clients();
for client in &self.manager.clients {
if client.display_handle == handle {
// This shouldn't happen, but make sure we are all set to destroy this display
anyhow::bail!("Wayland client still exists");
}
}
self.displays.remove(&handle);
self.signals.send(WayVRSignal::BroadcastStateChanged(
packet_server::WvrStateChanged::DisplayRemoved,
));
Ok(())
}
pub fn get_or_create_dashboard_display(
&mut self,
width: u16,
height: u16,
name: &str,
) -> anyhow::Result<(bool /* newly created? */, display::DisplayHandle)> {
if let Some(handle) = &self.dashboard_display {
// ensure it still exists
if self.displays.get(handle).is_some() {
return Ok((false, *handle));
}
}
let new_disp = self.create_display(width, height, name, false)?;
self.dashboard_display = Some(new_disp);
Ok((true, new_disp))
}
// Check if process with given arguments already exists
pub fn process_query(
&self,
display_handle: display::DisplayHandle,
exec_path: &str,
args: &[&str],
_env: &[(&str, &str)],
@@ -650,10 +428,7 @@ impl WayVRState {
if let Some(cell) = &cell
&& let process::Process::Managed(process) = &cell.obj
{
if process.display_handle != display_handle
|| process.exec_path != exec_path
|| process.args != args
{
if process.exec_path != exec_path || process.args != args {
continue;
}
return Some(process::ProcessVec::get_handle(cell, idx));
@@ -663,40 +438,39 @@ impl WayVRState {
None
}
pub fn add_external_process(
&mut self,
display_handle: display::DisplayHandle,
pid: u32,
) -> process::ProcessHandle {
pub fn add_external_process(&mut self, pid: u32) -> process::ProcessHandle {
self.processes
.add(process::Process::External(process::ExternalProcess {
pid,
display_handle,
}))
.add(process::Process::External(process::ExternalProcess { pid }))
}
pub fn spawn_process(
&mut self,
display_handle: display::DisplayHandle,
exec_path: &str,
args: &[&str],
env: &[(&str, &str)],
working_dir: Option<&str>,
userdata: HashMap<String, String>,
) -> anyhow::Result<process::ProcessHandle> {
let display = self
.displays
.get_mut(&display_handle)
.context(STR_INVALID_HANDLE_DISP)?;
let auth_key = generate_auth_key();
let res = display.spawn_process(exec_path, args, env, working_dir)?;
let mut cmd = std::process::Command::new(exec_path);
self.configure_env(&mut cmd, auth_key.as_str());
cmd.args(args);
if let Some(working_dir) = working_dir {
cmd.current_dir(working_dir);
}
for e in env {
cmd.env(e.0, e.1);
}
let child = cmd.spawn().context("Failed to spawn child process")?;
let handle = self
.processes
.add(process::Process::Managed(process::WayVRProcess {
auth_key: res.auth_key,
child: res.child,
display_handle,
auth_key,
child,
exec_path: String::from(exec_path),
userdata,
args: args.iter().map(|x| String::from(*x)).collect(),
@@ -713,6 +487,25 @@ impl WayVRState {
Ok(handle)
}
fn configure_env(&self, cmd: &mut std::process::Command, auth_key: &str) {
cmd.env_remove("DISPLAY"); // Goodbye X11
cmd.env(
"WAYLAND_DISPLAY",
self.manager.wayland_env.display_num_string(),
);
cmd.env("WAYVR_DISPLAY_AUTH", auth_key);
}
}
fn generate_auth_key() -> String {
let uuid = uuid::Uuid::new_v4();
uuid.to_string()
}
pub struct SpawnProcessResult {
pub auth_key: String,
pub child: std::process::Child,
}
#[derive(Deserialize, Clone)]
@@ -733,3 +526,34 @@ pub enum WayVRAction {
},
ToggleDashboard,
}
struct SurfaceBufWithImageContainer {
inner: RefCell<SurfaceBufWithImage>,
}
#[derive(Clone)]
pub struct SurfaceBufWithImage {
buffer: wl_buffer::WlBuffer,
pub image: Arc<ImageView>,
pub transform: Transform,
pub scale: i32,
}
impl SurfaceBufWithImage {
fn apply_to_surface(self, surface_data: &SurfaceData) {
let container = surface_data.data_map.get_or_insert(|| unsafe {
SurfaceBufWithImageContainer {
inner: RefCell::new(MaybeUninit::uninit().assume_init()),
}
});
container.inner.replace(self);
}
pub fn get_from_surface(surface_data: &SurfaceData) -> Option<Self> {
surface_data
.data_map
.get::<SurfaceBufWithImageContainer>()
.map(|x| x.inner.borrow().clone())
}
}

View File

@@ -4,14 +4,11 @@ use wayvr_ipc::packet_server;
use crate::gen_id;
use super::display;
#[derive(Debug)]
#[allow(dead_code)]
pub struct WayVRProcess {
pub auth_key: String,
pub child: std::process::Child,
pub display_handle: display::DisplayHandle,
pub exec_path: String,
pub args: Vec<String>,
@@ -24,7 +21,6 @@ pub struct WayVRProcess {
#[derive(Debug)]
pub struct ExternalProcess {
pub pid: u32,
pub display_handle: display::DisplayHandle,
}
#[derive(Debug)]
@@ -34,13 +30,6 @@ pub enum Process {
}
impl Process {
pub const fn display_handle(&self) -> display::DisplayHandle {
match self {
Self::Managed(p) => p.display_handle,
Self::External(p) => p.display_handle,
}
}
pub fn is_running(&mut self) -> bool {
match self {
Self::Managed(p) => p.is_running(),
@@ -67,13 +56,11 @@ impl Process {
Self::Managed(p) => packet_server::WvrProcess {
name: p.get_name().unwrap_or_else(|| String::from("unknown")),
userdata: p.userdata.clone(),
display_handle: p.display_handle.as_packet(),
handle: handle.as_packet(),
},
Self::External(p) => packet_server::WvrProcess {
name: p.get_name().unwrap_or_else(|| String::from("unknown")),
userdata: HashMap::default(),
display_handle: p.display_handle.as_packet(),
handle: handle.as_packet(),
},
}

View File

@@ -1,54 +0,0 @@
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!(std::ptr::eq(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, &raw 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

@@ -1,39 +1,36 @@
use smithay::wayland::shell::xdg::ToplevelSurface;
use smithay::{
input,
utils::{Logical, Point},
wayland::shell::xdg::ToplevelSurface,
};
use wayvr_ipc::packet_server;
use crate::gen_id;
use super::display;
use crate::{
backend::wayvr::{client::WayVRCompositor, process},
gen_id,
subsystem::hid::WheelDelta,
};
#[derive(Debug)]
pub struct Window {
pub pos_x: i32,
pub pos_y: i32,
pub size_x: u32,
pub size_y: u32,
pub visible: bool,
pub toplevel: ToplevelSurface,
pub display_handle: display::DisplayHandle,
pub process: process::ProcessHandle,
}
impl Window {
pub fn new(display_handle: display::DisplayHandle, toplevel: &ToplevelSurface) -> Self {
fn new(toplevel: &ToplevelSurface, process: process::ProcessHandle) -> Self {
Self {
pos_x: 0,
pos_y: 0,
size_x: 0,
size_y: 0,
visible: true,
toplevel: toplevel.clone(),
display_handle,
process,
}
}
pub const 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());
@@ -44,6 +41,86 @@ impl Window {
self.size_x = size_x;
self.size_y = size_y;
}
pub fn send_mouse_move(&self, manager: &mut WayVRCompositor, x: u32, y: u32) {
let surf = self.toplevel.wl_surface().clone();
let point = Point::<f64, Logical>::from((f64::from(x as i32), f64::from(y as i32)));
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);
}
const 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(&mut self, manager: &mut WayVRCompositor, index: super::MouseIndex) {
let surf = self.toplevel.wl_surface().clone();
// Change keyboard focus to pressed window
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(manager: &mut WayVRCompositor, 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(manager: &mut WayVRCompositor, delta: WheelDelta) {
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: (f64::from(delta.x), f64::from(-delta.y)),
v120: Some((0, (delta.y * -64.0) as i32)),
stop: (false, false),
},
);
manager.seat_pointer.frame(&mut manager.state);
}
}
#[derive(Debug)]
@@ -72,10 +149,10 @@ impl WindowManager {
pub fn create_window(
&mut self,
display_handle: display::DisplayHandle,
toplevel: &ToplevelSurface,
process: process::ProcessHandle,
) -> WindowHandle {
self.windows.add(Window::new(display_handle, toplevel))
self.windows.add(Window::new(toplevel, process))
}
pub fn remove_window(&mut self, window_handle: WindowHandle) {

View File

@@ -10,6 +10,7 @@ use std::{
use anyhow::Context;
use serde::{Deserialize, Serialize};
use wgui::gfx::WGfx;
use wlx_common::{common::LeftRight, config::GeneralConfig, windowing::Positioning};
use crate::{
@@ -19,8 +20,9 @@ use crate::{
},
config::load_config_with_conf_d,
config_io,
graphics::WGfxExtras,
ipc::{event_queue::SyncEventQueue, signal::WayVRSignal},
overlays::wayvr::{WayVRData, executable_exists_in_path},
overlays::wayvr::WayVRData,
};
// Flat version of RelativeTo
@@ -199,6 +201,8 @@ impl WayVRConfig {
pub fn post_load(
&self,
gfx: Arc<WGfx>,
gfx_extras: &WGfxExtras,
config: &GeneralConfig,
tasks: &mut TaskContainer,
signals: SyncEventQueue<WayVRSignal>,
@@ -227,6 +231,8 @@ impl WayVRConfig {
}
Ok(Rc::new(RefCell::new(WayVRData::new(
gfx,
gfx_extras,
Self::get_wayvr_config(config, self)?,
signals,
)?)))
@@ -248,6 +254,19 @@ fn get_default_dashboard_exec() -> (
(String::from("wayvr-dashboard"), None)
}
pub fn executable_exists_in_path(command: &str) -> bool {
let Ok(path) = std::env::var("PATH") else {
return false; // very unlikely to happen
};
for dir in path.split(':') {
let exec_path = std::path::PathBuf::from(dir).join(command);
if exec_path.exists() && exec_path.is_file() {
return true; // executable found
}
}
false
}
pub fn load_wayvr() -> WayVRConfig {
let config_root_path = config_io::ConfigRoot::WayVR.ensure_dir();
log::info!("WayVR Config root path: {}", config_root_path.display());

View File

@@ -5,7 +5,7 @@ use std::{
};
use anyhow::Context;
use smallvec::SmallVec;
use smallvec::{SmallVec, smallvec};
use vulkano::{
VulkanError, VulkanObject,
device::Device,
@@ -19,12 +19,7 @@ use vulkano::{
sync::Sharing,
};
use wgui::gfx::WGfx;
use wlx_capture::frame::{
DRM_FORMAT_ABGR8888, DRM_FORMAT_ABGR2101010, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888,
DRM_FORMAT_XBGR2101010, DRM_FORMAT_XRGB8888, DmabufFrame, DrmFormat, FourCC,
};
pub const DRM_FORMAT_MOD_INVALID: u64 = 0xff_ffff_ffff_ffff;
use wlx_capture::{DrmFormat, DrmFourcc, DrmModifier, frame::DmabufFrame};
pub trait WGfxDmabuf {
fn dmabuf_texture_ex(
@@ -47,7 +42,7 @@ impl WGfxDmabuf for WGfx {
modifiers: &[u64],
) -> anyhow::Result<Arc<Image>> {
let extent = [frame.format.width, frame.format.height, 1];
let format = fourcc_to_vk(frame.format.fourcc)?;
let format = fourcc_to_vk(frame.format.drm_format.code)?;
let image = unsafe {
create_dmabuf_image(
@@ -116,11 +111,11 @@ impl WGfxDmabuf for WGfx {
}
fn dmabuf_texture(&self, frame: DmabufFrame) -> anyhow::Result<Arc<Image>> {
let mut modifiers: Vec<u64> = vec![];
let mut modifiers: SmallVec<[u64; 4]> = smallvec![];
let mut tiling: ImageTiling = ImageTiling::Optimal;
let mut layouts: Vec<SubresourceLayout> = vec![];
if frame.format.modifier != DRM_FORMAT_MOD_INVALID {
if !matches!(frame.format.drm_format.modifier, DrmModifier::Invalid) {
(0..frame.num_planes).for_each(|i| {
let plane = &frame.planes[i];
layouts.push(SubresourceLayout {
@@ -130,7 +125,7 @@ impl WGfxDmabuf for WGfx {
array_pitch: None,
depth_pitch: None,
});
modifiers.push(frame.format.modifier);
modifiers.push(frame.format.drm_format.modifier.into());
});
tiling = ImageTiling::DrmFormatModifier;
}
@@ -304,50 +299,56 @@ pub(super) unsafe fn create_dmabuf_image(
}
}
pub fn get_drm_formats(device: Arc<Device>) -> Vec<DrmFormat> {
pub(super) fn get_drm_formats(device: Arc<Device>) -> Vec<DrmFormat> {
let possible_formats = [
DRM_FORMAT_ABGR8888.into(),
DRM_FORMAT_XBGR8888.into(),
DRM_FORMAT_ARGB8888.into(),
DRM_FORMAT_XRGB8888.into(),
DRM_FORMAT_ABGR2101010.into(),
DRM_FORMAT_XBGR2101010.into(),
DrmFourcc::Abgr8888,
DrmFourcc::Xbgr8888,
DrmFourcc::Argb8888,
DrmFourcc::Xrgb8888,
DrmFourcc::Abgr2101010,
DrmFourcc::Xbgr2101010,
];
let mut final_formats = vec![];
let mut out_formats = vec![];
for &f in &possible_formats {
let Ok(vk_fmt) = fourcc_to_vk(f) else {
for &code in &possible_formats {
let Ok(vk_fmt) = fourcc_to_vk(code) else {
continue;
};
let Ok(props) = device.physical_device().format_properties(vk_fmt) else {
continue;
};
let mut fmt = DrmFormat {
fourcc: f,
modifiers: props
.drm_format_modifier_properties
.iter()
// important bit: only allow single-plane
.filter(|m| m.drm_format_modifier_plane_count == 1)
.map(|m| m.drm_format_modifier)
.collect(),
};
fmt.modifiers.push(DRM_FORMAT_MOD_INVALID); // implicit modifiers support
final_formats.push(fmt);
for m in props
.drm_format_modifier_properties
.iter()
.filter(|m| m.drm_format_modifier_plane_count == 1)
.map(|m| m.drm_format_modifier)
{
out_formats.push(DrmFormat {
code,
modifier: DrmModifier::from(m),
});
}
// accept implicit modifiers
out_formats.push(DrmFormat {
code,
modifier: DrmModifier::Invalid,
});
}
log::debug!("Supported DRM formats:");
for f in &final_formats {
log::debug!(" {} {:?}", f.fourcc, f.modifiers);
for f in &out_formats {
log::debug!(" {} {:?}", f.code, f.modifier);
}
final_formats
out_formats
}
pub fn fourcc_to_vk(fourcc: FourCC) -> anyhow::Result<Format> {
match fourcc.value {
DRM_FORMAT_ABGR8888 | DRM_FORMAT_XBGR8888 => Ok(Format::R8G8B8A8_UNORM),
DRM_FORMAT_ARGB8888 | DRM_FORMAT_XRGB8888 => Ok(Format::B8G8R8A8_UNORM),
DRM_FORMAT_ABGR2101010 | DRM_FORMAT_XBGR2101010 => Ok(Format::A2B10G10R10_UNORM_PACK32),
pub fn fourcc_to_vk(fourcc: DrmFourcc) -> anyhow::Result<Format> {
match fourcc {
DrmFourcc::Abgr8888 | DrmFourcc::Xbgr8888 => Ok(Format::R8G8B8A8_UNORM),
DrmFourcc::Argb8888 | DrmFourcc::Xrgb8888 => Ok(Format::B8G8R8A8_UNORM),
DrmFourcc::Abgr2101010 | DrmFourcc::Xbgr2101010 => Ok(Format::A2B10G10R10_UNORM_PACK32),
_ => anyhow::bail!("Unsupported format {fourcc}"),
}
}

View File

@@ -19,7 +19,7 @@ use wgui::gfx::WGfx;
#[cfg(feature = "openvr")]
use vulkano::instance::InstanceCreateFlags;
use wlx_capture::frame::DrmFormat;
use wlx_capture::DrmFormat;
use crate::shaders::{frag_color, frag_grid, frag_screen, frag_srgb, vert_quad};
@@ -73,6 +73,7 @@ pub struct WGfxExtras {
pub queue_capture: Option<Arc<Queue>>,
pub quad_verts: Vert2Buf,
pub fallback_image: Arc<ImageView>,
pub drm_device: Option<(i64, i64)>,
}
impl WGfxExtras {
@@ -135,12 +136,23 @@ impl WGfxExtras {
let fallback_image = ImageView::new_default(fallback_image)?;
let p = gfx.device.physical_device().properties();
let drm_device = if let (Some(maj), Some(min)) = (p.render_major, p.render_minor) {
log::info!("DRM render device: {maj} {min}");
Some((maj, min))
} else {
log::warn!("No DRM device.");
None
};
Ok(Self {
shaders,
drm_formats,
queue_capture,
quad_verts,
fallback_image,
drm_device,
})
}
}
@@ -271,6 +283,13 @@ pub fn init_openxr_graphics(
.ext_image_drm_format_modifier;
}
if physical_device
.supported_extensions()
.ext_physical_device_drm
{
device_extensions.ext_physical_device_drm = true;
}
let device_extensions_raw = device_extensions
.into_iter()
.filter_map(|(name, enabled)| {
@@ -432,6 +451,11 @@ pub fn init_openvr_graphics(
my_extensions.ext_filter_cubic = true;
}
if p.supported_extensions().ext_physical_device_drm {
// needed for wayland_server
my_extensions.ext_physical_device_drm = true;
}
log::debug!(
"Device exts for {}: {:?}",
p.properties().device_name,

View File

@@ -3,9 +3,7 @@ use std::{cell::RefCell, rc::Rc};
use wayvr_ipc::packet_server;
#[cfg(feature = "wayvr")]
use crate::{
backend::wayvr, config_wayvr, overlays::wayvr::OverlayToCreate, overlays::wayvr::WayVRData,
};
use crate::{backend::wayvr, overlays::wayvr::WayVRData};
use crate::{
backend::{
@@ -13,89 +11,32 @@ use crate::{
task::{InputTask, OverlayTask, TaskType},
},
ipc::signal::WayVRSignal,
overlays::{self},
state::AppState,
windowing::{OverlaySelector, manager::OverlayWindowManager},
};
#[cfg(feature = "wayvr")]
fn process_tick_tasks(
app: &mut AppState,
tick_tasks: Vec<backend::wayvr::TickTask>,
r_wayvr: &Rc<RefCell<WayVRData>>,
) -> anyhow::Result<()> {
for tick_task in tick_tasks {
match tick_task {
backend::wayvr::TickTask::NewExternalProcess(request) => {
let config = &app.session.wayvr_config;
let disp_name = request.env.display_name.map_or_else(
|| {
config
.get_default_display()
.map(|(display_name, _)| display_name)
},
|display_name| {
config
.get_display(display_name.as_str())
.map(|_| display_name)
},
);
if let Some(disp_name) = disp_name {
let mut wayvr = r_wayvr.borrow_mut();
log::info!("Registering external process with PID {}", request.pid);
let disp_handle = overlays::wayvr::get_or_create_display_by_name(
app, &mut wayvr, &disp_name,
)?;
wayvr
.data
.state
.add_external_process(disp_handle, request.pid);
wayvr
.data
.state
.manager
.add_client(wayvr::client::WayVRClient {
client: request.client,
display_handle: disp_handle,
pid: request.pid,
});
}
}
wayvr::TickTask::NewDisplay(cpar, disp_handle) => {
log::info!("Creating new display with name \"{}\"", cpar.name);
let mut wayvr = r_wayvr.borrow_mut();
let unique_name = wayvr.get_unique_display_name(cpar.name);
log::info!("Registering external process with PID {}", request.pid);
let disp_handle = match disp_handle {
Some(d) => d,
None => wayvr.data.state.create_display(
cpar.width,
cpar.height,
&unique_name,
false,
)?,
};
wayvr.data.state.add_external_process(request.pid);
wayvr.overlays_to_create.push(OverlayToCreate {
disp_handle,
conf_display: config_wayvr::WayVRDisplay {
attach_to: Some(config_wayvr::AttachTo::from_packet(&cpar.attach_to)),
width: cpar.width,
height: cpar.height,
pos: None,
primary: None,
rotation: None,
scale: cpar.scale,
},
});
wayvr
.data
.state
.manager
.add_client(wayvr::client::WayVRClient {
client: request.client,
pid: request.pid,
});
}
}
}
@@ -103,10 +44,9 @@ fn process_tick_tasks(
Ok(())
}
#[allow(clippy::too_many_lines)]
pub fn tick_events<O>(
app: &mut AppState,
overlays: &mut OverlayWindowManager<O>,
_overlays: &mut OverlayWindowManager<O>,
) -> anyhow::Result<()>
where
O: Default,
@@ -116,51 +56,11 @@ where
while let Some(signal) = app.wayvr_signals.read() {
match signal {
#[cfg(feature = "wayvr")]
WayVRSignal::DisplayVisibility(display_handle, visible) => {
if let Some(mut wayland_server) = wayland_server.as_ref().map(|r| r.borrow_mut())
&& let Some(overlay_id) = wayland_server.display_handle_map.get(&display_handle)
{
let overlay_id = *overlay_id;
wayland_server
.data
.state
.set_display_visible(display_handle, visible);
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Id(overlay_id),
Box::new(move |app, o| {
if visible == o.is_active() {
return;
}
if visible {
o.activate(app);
} else {
o.deactivate();
}
}),
)));
}
}
#[cfg(feature = "wayvr")]
WayVRSignal::DisplayWindowLayout(display_handle, layout) => {
if let Some(mut wayland_server) = wayland_server.as_ref().map(|r| r.borrow_mut()) {
wayland_server
.data
.state
.set_display_layout(display_handle, layout);
}
}
#[cfg(feature = "wayvr")]
WayVRSignal::BroadcastStateChanged(packet) => {
app.ipc_server
.broadcast(packet_server::PacketServer::WvrStateChanged(packet));
}
#[cfg(feature = "wayvr")]
WayVRSignal::Haptics(haptics) => {
if let Some(mut wayland_server) = wayland_server.as_ref().map(|r| r.borrow_mut()) {
wayland_server.pending_haptics = Some(haptics);
}
}
WayVRSignal::DeviceHaptics(device, haptics) => {
app.tasks
.enqueue(TaskType::Input(InputTask::Haptics { device, haptics }));
@@ -182,13 +82,7 @@ where
{
if let Some(wayland_server) = wayland_server {
let tick_tasks = wayland_server.borrow_mut().data.tick_events(app)?;
process_tick_tasks(app, tick_tasks, &wayland_server)?;
overlays::wayvr::create_queued_displays(
app,
&mut wayland_server.borrow_mut(),
overlays,
)?;
process_tick_tasks(tick_tasks, &wayland_server)?;
}
}

View File

@@ -156,41 +156,6 @@ impl Connection {
Ok(())
}
#[cfg(feature = "wayvr")]
fn handle_wvr_display_list(
&mut self,
params: &TickParams,
serial: ipc::Serial,
) -> anyhow::Result<()> {
let list: Vec<packet_server::WvrDisplay> = params
.wayland_state
.displays
.vec
.iter()
.enumerate()
.filter_map(|(idx, opt_cell)| {
let Some(cell) = opt_cell else {
return None;
};
let display = &cell.obj;
Some(display.as_packet(wayvr::display::DisplayHandle::new(
idx as u32,
cell.generation,
)))
})
.collect();
send_packet(
&mut self.conn,
&ipc::data_encode(&PacketServer::WvrDisplayListResponse(
serial,
packet_server::WvrDisplayList { list },
)),
)?;
Ok(())
}
fn handle_wlx_input_state(
&mut self,
params: &TickParams,
@@ -218,125 +183,31 @@ impl Connection {
}
#[cfg(feature = "wayvr")]
fn handle_wvr_display_create(
fn handle_wvr_window_list(
&mut self,
params: &mut TickParams,
serial: ipc::Serial,
packet_params: packet_client::WvrDisplayCreateParams,
) -> anyhow::Result<()> {
let display_handle = params.wayland_state.create_display(
packet_params.width,
packet_params.height,
&packet_params.name,
false,
)?;
params.tasks.push(wayvr::TickTask::NewDisplay(
packet_params,
Some(display_handle),
));
send_packet(
&mut self.conn,
&ipc::data_encode(&PacketServer::WvrDisplayCreateResponse(
serial,
display_handle.as_packet(),
)),
)?;
Ok(())
}
#[cfg(feature = "wayvr")]
fn handle_wvr_display_remove(
&mut self,
params: &mut TickParams,
serial: ipc::Serial,
handle: packet_server::WvrDisplayHandle,
) -> anyhow::Result<()> {
let res = params
.wayland_state
.destroy_display(wayvr::display::DisplayHandle::from_packet(handle))
.map_err(|e| format!("{e:?}"));
send_packet(
&mut self.conn,
&ipc::data_encode(&PacketServer::WvrDisplayRemoveResponse(serial, res)),
)?;
Ok(())
}
#[cfg(feature = "wayvr")]
fn handle_wvr_display_set_visible(
params: &mut TickParams,
handle: packet_server::WvrDisplayHandle,
visible: bool,
) {
params.signals.send(WayVRSignal::DisplayVisibility(
wayvr::display::DisplayHandle::from_packet(handle),
visible,
));
}
#[cfg(feature = "wayvr")]
fn handle_wvr_display_set_window_layout(
params: &mut TickParams,
handle: packet_server::WvrDisplayHandle,
layout: packet_server::WvrDisplayWindowLayout,
) {
params.signals.send(WayVRSignal::DisplayWindowLayout(
wayvr::display::DisplayHandle::from_packet(handle),
layout,
));
}
#[cfg(feature = "wayvr")]
fn handle_wvr_display_window_list(
&mut self,
params: &mut TickParams,
serial: ipc::Serial,
display_handle: packet_server::WvrDisplayHandle,
) -> anyhow::Result<()> {
let mut send = |list: Option<packet_server::WvrWindowList>| -> anyhow::Result<()> {
send_packet(
&mut self.conn,
&ipc::data_encode(&PacketServer::WvrDisplayWindowListResponse(serial, list)),
&ipc::data_encode(&PacketServer::WvrWindowListResponse(serial, list)),
)
};
let Some(display) =
params
.wayland_state
.displays
.get(&wayvr::display::DisplayHandle::from_packet(
display_handle.clone(),
))
else {
return send(None);
};
send(Some(packet_server::WvrWindowList {
list: display
.displayed_windows
list: params
.wayland_state
.wm
.borrow_mut()
.windows
.iter()
.filter_map(|disp_win| {
params
.wayland_state
.wm
.borrow_mut()
.windows
.get(&disp_win.window_handle)
.map(|win| packet_server::WvrWindow {
handle: wayvr::window::WindowHandle::as_packet(&disp_win.window_handle),
process_handle: wayvr::process::ProcessHandle::as_packet(
&disp_win.process_handle,
),
pos_x: win.pos_x,
pos_y: win.pos_y,
size_x: win.size_x,
size_y: win.size_y,
visible: win.visible,
display_handle: display_handle.clone(),
})
.map(|(handle, win)| packet_server::WvrWindow {
handle: wayvr::window::WindowHandle::as_packet(&handle),
process_handle: wayvr::process::ProcessHandle::as_packet(&win.process),
size_x: win.size_x,
size_y: win.size_y,
visible: win.visible,
})
.collect::<Vec<_>>(),
}))
@@ -348,7 +219,7 @@ impl Connection {
handle: packet_server::WvrWindowHandle,
visible: bool,
) {
let to_resize = if let Some(window) = params
if let Some(window) = params
.wayland_state
.wm
.borrow_mut()
@@ -356,16 +227,6 @@ impl Connection {
.get_mut(&wayvr::window::WindowHandle::from_packet(handle))
{
window.visible = visible;
Some(window.display_handle)
} else {
None
};
if let Some(to_resize) = to_resize
&& let Some(display) = params.wayland_state.displays.get_mut(&to_resize)
{
display.reposition_windows();
display.trigger_rerender();
}
}
@@ -380,7 +241,6 @@ impl Connection {
let env_vec = gen_env_vec(&packet_params.env);
let res = params.wayland_state.spawn_process(
wayvr::display::DisplayHandle::from_packet(packet_params.target_display),
&packet_params.exec,
&args_vec,
&env_vec,
@@ -398,28 +258,6 @@ impl Connection {
Ok(())
}
#[cfg(feature = "wayvr")]
fn handle_wvr_display_get(
&mut self,
params: &TickParams,
serial: ipc::Serial,
display_handle: packet_server::WvrDisplayHandle,
) -> anyhow::Result<()> {
let native_handle = &wayvr::display::DisplayHandle::from_packet(display_handle);
let disp = params
.wayland_state
.displays
.get(native_handle)
.map(|disp| disp.as_packet(*native_handle));
send_packet(
&mut self.conn,
&ipc::data_encode(&PacketServer::WvrDisplayGetResponse(serial, disp)),
)?;
Ok(())
}
#[cfg(feature = "wayvr")]
fn handle_wvr_process_list(
&mut self,
@@ -493,20 +331,6 @@ impl Connection {
Ok(())
}
#[cfg(feature = "wayvr")]
fn handle_wlx_haptics(
params: &mut TickParams,
haptics_params: packet_client::WlxHapticsParams,
) {
params
.signals
.send(WayVRSignal::Haptics(crate::backend::input::Haptics {
duration: haptics_params.duration,
frequency: haptics_params.frequency,
intensity: haptics_params.intensity,
}));
}
fn handle_wlx_device_haptics(
params: &mut TickParams,
device: usize,
@@ -569,29 +393,9 @@ impl Connection {
PacketClient::WlxInputState(serial) => {
self.handle_wlx_input_state(params, serial)?;
}
PacketClient::WvrDisplayList(serial) => {
PacketClient::WvrWindowList(serial) => {
#[cfg(feature = "wayvr")]
self.handle_wvr_display_list(params, serial)?;
}
PacketClient::WvrDisplayGet(serial, display_handle) => {
#[cfg(feature = "wayvr")]
self.handle_wvr_display_get(params, serial, display_handle)?;
}
PacketClient::WvrDisplayRemove(serial, display_handle) => {
#[cfg(feature = "wayvr")]
self.handle_wvr_display_remove(params, serial, display_handle)?;
}
PacketClient::WvrDisplaySetVisible(display_handle, visible) => {
#[cfg(feature = "wayvr")]
Self::handle_wvr_display_set_visible(params, display_handle, visible);
}
PacketClient::WvrDisplaySetWindowLayout(display_handle, layout) => {
#[cfg(feature = "wayvr")]
Self::handle_wvr_display_set_window_layout(params, display_handle, layout);
}
PacketClient::WvrDisplayWindowList(serial, display_handle) => {
#[cfg(feature = "wayvr")]
self.handle_wvr_display_window_list(params, serial, display_handle)?;
self.handle_wvr_window_list(params, serial)?;
}
PacketClient::WvrWindowSetVisible(window_handle, visible) => {
#[cfg(feature = "wayvr")]
@@ -609,18 +413,10 @@ impl Connection {
#[cfg(feature = "wayvr")]
self.handle_wvr_process_launch(params, serial, packet_params)?;
}
PacketClient::WvrDisplayCreate(serial, packet_params) => {
#[cfg(feature = "wayvr")]
self.handle_wvr_display_create(params, serial, packet_params)?;
}
PacketClient::WvrProcessTerminate(process_handle) => {
#[cfg(feature = "wayvr")]
Self::handle_wvr_process_terminate(params, process_handle);
}
PacketClient::WlxHaptics(haptics_params) => {
#[cfg(feature = "wayvr")]
Self::handle_wlx_haptics(params, haptics_params);
}
PacketClient::WlxDeviceHaptics(device, haptics_params) => {
Self::handle_wlx_device_haptics(params, device, haptics_params);
}

View File

@@ -1,19 +1,7 @@
#[cfg(feature = "wayvr")]
use crate::backend::wayvr;
#[derive(Clone)]
pub enum WayVRSignal {
#[cfg(feature = "wayvr")]
DisplayVisibility(wayvr::display::DisplayHandle, bool),
#[cfg(feature = "wayvr")]
DisplayWindowLayout(
wayvr::display::DisplayHandle,
wayvr_ipc::packet_server::WvrDisplayWindowLayout,
),
#[cfg(feature = "wayvr")]
BroadcastStateChanged(wayvr_ipc::packet_server::WvrStateChanged),
#[cfg(feature = "wayvr")]
Haptics(crate::backend::input::Haptics),
DeviceHaptics(usize, crate::backend::input::Haptics),
DropOverlay(crate::windowing::OverlayID),
CustomTask(crate::backend::task::ModifyPanelTask),

View File

@@ -222,9 +222,13 @@ impl OverlayBackend for ScreenBackend {
fn render(&mut self, app: &mut AppState, rdr: &mut RenderResources) -> anyhow::Result<()> {
// want panic; must be some if should_render was not Unable
let capture = self.cur_frame.as_ref().unwrap();
let image = capture.image.clone();
// want panic; must be Some if cur_frame is also Some
self.pipeline.as_mut().unwrap().render(&capture, app, rdr)?;
self.pipeline
.as_mut()
.unwrap()
.render(image, capture.mouse.as_ref(), app, rdr)?;
self.capture.request_new_frame();
Ok(())
}

View File

@@ -17,8 +17,8 @@ use wgui::gfx::{
pipeline::{WGfxPipeline, WPipelineCreateInfo},
};
use wlx_capture::{
WlxCapture,
frame::{self as wlx_frame, DrmFormat, FrameFormat, MouseMeta, Transform, WlxFrame},
DrmFormat, WlxCapture,
frame::{self as wlx_frame, FrameFormat, MouseMeta, WlxFrame},
};
use wlx_common::{config::GeneralConfig, overlays::StereoMode};
@@ -39,7 +39,8 @@ struct BufPass {
buf_vert: Subbuffer<[Vert2Uv]>,
}
pub(super) struct ScreenPipeline {
/// A render pipeline that supports mouse + stereo
pub struct ScreenPipeline {
mouse: BufPass,
pass: SmallVec<[BufPass; 2]>,
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
@@ -49,11 +50,7 @@ pub(super) struct ScreenPipeline {
}
impl ScreenPipeline {
pub(super) fn new(
meta: &FrameMeta,
app: &mut AppState,
stereo: StereoMode,
) -> anyhow::Result<Self> {
pub fn new(meta: &FrameMeta, app: &mut AppState, stereo: StereoMode) -> anyhow::Result<Self> {
let extentf = [meta.extent[0] as f32, meta.extent[1] as f32];
let pipeline = app.gfx.create_pipeline(
@@ -198,13 +195,13 @@ impl ScreenPipeline {
Ok(BufPass { pass, buf_vert })
}
pub(super) fn render(
pub fn render(
&mut self,
capture: &WlxCaptureOut,
image: Arc<ImageView>,
mouse: Option<&MouseMeta>,
app: &mut AppState,
rdr: &mut RenderResources,
) -> anyhow::Result<()> {
let view = ImageView::new_default(capture.image.clone())?;
self.buf_alpha.write()?[0] = rdr.alpha;
for (eye, cmd_buf) in rdr.cmd_bufs.iter_mut().enumerate() {
@@ -212,11 +209,11 @@ impl ScreenPipeline {
current
.pass
.update_sampler(0, view.clone(), app.gfx.texture_filter)?;
.update_sampler(0, image.clone(), app.gfx.texture_filter)?;
cmd_buf.run_ref(&current.pass)?;
if let Some(mouse) = capture.mouse.as_ref() {
if let Some(mouse) = mouse.as_ref() {
let size = CURSOR_SIZE * self.extentf[1];
let half_size = size * 0.5;
@@ -325,10 +322,10 @@ impl WlxCaptureIn {
}
#[derive(Clone)]
pub struct WlxCaptureOut {
image: Arc<Image>,
format: FrameFormat,
mouse: Option<MouseMeta>,
pub(super) struct WlxCaptureOut {
pub(super) image: Arc<ImageView>,
pub(super) format: FrameFormat,
pub(super) mouse: Option<MouseMeta>,
}
impl WlxCaptureOut {
@@ -340,10 +337,6 @@ impl WlxCaptureOut {
format: self.image.format(),
}
}
pub(super) const fn get_transform(&self) -> Transform {
self.format.transform
}
}
fn upload_image(
@@ -390,7 +383,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
let format = frame.format;
match me.gfx.dmabuf_texture(frame) {
Ok(image) => Some(WlxCaptureOut {
image,
image: ImageView::new_default(image).ok()?,
format,
mouse: None,
}),
@@ -406,7 +399,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
return None;
};
let format = match fourcc_to_vk(frame.format.fourcc) {
let format = match fourcc_to_vk(frame.format.drm_format.code) {
Ok(x) => x,
Err(e) => {
log::error!("{}: {}", me.name, e);
@@ -439,7 +432,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
}?;
Some(WlxCaptureOut {
image,
image: ImageView::new_default(image).ok()?,
format: frame.format,
mouse: None,
})
@@ -447,7 +440,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
WlxFrame::MemPtr(frame) => {
log::trace!("{}: New MemPtr frame", me.name);
let format = match fourcc_to_vk(frame.format.fourcc) {
let format = match fourcc_to_vk(frame.format.drm_format.code) {
Ok(x) => x,
Err(e) => {
log::error!("{}: {}", me.name, e);
@@ -459,7 +452,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
let image = upload_image(me, frame.format.width, frame.format.height, format, data)?;
Some(WlxCaptureOut {
image,
image: ImageView::new_default(image).ok()?,
format: frame.format,
mouse: frame.mouse,
})

View File

@@ -14,7 +14,7 @@ use crate::{
};
pub mod backend;
mod capture;
pub mod capture;
#[cfg(feature = "wayland")]
pub mod mirror;
#[cfg(feature = "pipewire")]

View File

@@ -1,575 +1,176 @@
use anyhow::Context;
use glam::{Affine2, Affine3A, Quat, Vec3, vec3};
use smallvec::smallvec;
use smithay::wayland::compositor::with_states;
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
use vulkano::{
buffer::{BufferUsage, Subbuffer},
command_buffer::CommandBufferUsage,
format::Format,
image::{Image, ImageTiling, SubresourceLayout, view::ImageView},
};
use wayvr_ipc::packet_server::{PacketServer, WvrStateChanged};
use wgui::gfx::{
WGfx,
pass::WGfxPass,
pipeline::{WGfxPipeline, WPipelineCreateInfo},
};
use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane};
use wlx_common::overlays::{BackendAttrib, BackendAttribValue};
use wlx_common::windowing::OverlayWindowState;
use vulkano::image::view::ImageView;
use wgui::gfx::WGfx;
use wlx_common::overlays::{BackendAttrib, BackendAttribValue, StereoMode};
use crate::{
backend::{
XrBackend,
input::{self, HoverResult},
task::{OverlayTask, TaskType},
wayvr::{self, WayVR, WayVRAction, WayVRDisplayClickAction, display},
wayvr::{self, SurfaceBufWithImage, WayVR, window::WindowManager},
},
config_wayvr,
graphics::{Vert2Uv, dmabuf::WGfxDmabuf},
ipc::{event_queue::SyncEventQueue, ipc_server, signal::WayVRSignal},
graphics::{ExtentExt, WGfxExtras},
ipc::{event_queue::SyncEventQueue, signal::WayVRSignal},
overlays::screen::capture::ScreenPipeline,
state::{self, AppState},
subsystem::{hid::WheelDelta, input::KeyboardFocus},
subsystem::hid::WheelDelta,
windowing::{
OverlayID, OverlaySelector, Z_ORDER_DASHBOARD,
OverlayID,
backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender,
ui_transform,
},
manager::OverlayWindowManager,
window::{OverlayCategory, OverlayWindowConfig, OverlayWindowData},
},
};
use super::toast::error_toast;
// Hard-coded for now
const DASHBOARD_WIDTH: u16 = 1920;
const DASHBOARD_HEIGHT: u16 = 1080;
const DASHBOARD_DISPLAY_NAME: &str = "_DASHBOARD";
pub struct WayVRContext {
wayvr: Rc<RefCell<WayVRData>>,
display: wayvr::display::DisplayHandle,
}
impl WayVRContext {
pub const fn new(wvr: Rc<RefCell<WayVRData>>, display: wayvr::display::DisplayHandle) -> Self {
Self {
wayvr: wvr,
display,
}
}
}
pub struct OverlayToCreate {
pub conf_display: config_wayvr::WayVRDisplay,
pub disp_handle: display::DisplayHandle,
}
pub struct WayVRData {
pub display_handle_map: HashMap<display::DisplayHandle, OverlayID>,
pub overlays_to_create: Vec<OverlayToCreate>,
pub dashboard_executed: bool,
pub window_handle_map: HashMap<wayvr::window::WindowHandle, OverlayID>,
pub data: WayVR,
pub pending_haptics: Option<input::Haptics>,
}
impl WayVRData {
pub fn new(
gfx: Arc<WGfx>,
gfx_extras: &WGfxExtras,
config: wayvr::Config,
signals: SyncEventQueue<WayVRSignal>,
) -> anyhow::Result<Self> {
Ok(Self {
display_handle_map: HashMap::default(),
data: WayVR::new(config, signals)?,
overlays_to_create: Vec::new(),
dashboard_executed: false,
pending_haptics: None,
window_handle_map: HashMap::default(),
data: WayVR::new(gfx, &gfx_extras, config, signals)?,
})
}
pub fn get_unique_display_name(&self, mut candidate: String) -> String {
let mut num = 0;
while !self
.data
.state
.displays
.vec
.iter()
.flatten()
.any(|d| d.obj.name == candidate)
{
if num > 0 {
candidate = format!("{candidate} ({num})");
}
num += 1;
}
candidate
}
fn set_overlay_display_handle(&mut self, id: OverlayID, disp_handle: display::DisplayHandle) {
self.display_handle_map.insert(disp_handle, id);
let display = self.data.state.displays.get_mut(&disp_handle).unwrap(); // Never fails
display.overlay_id = Some(id);
}
}
struct ImageData {
vk_image: Arc<Image>,
vk_image_view: Arc<ImageView>,
}
pub struct WayVRBackend {
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
pass: WGfxPass<Vert2Uv>,
buf_alpha: Subbuffer<[f32]>,
image: Option<ImageData>,
context: Rc<RefCell<WayVRContext>>,
graphics: Arc<WGfx>,
resolution: [u16; 2],
name: Arc<str>,
pipeline: Option<ScreenPipeline>,
mouse_transform: Affine2,
interaction_transform: Option<Affine2>,
window: wayvr::window::WindowHandle,
wayvr: Rc<RefCell<WayVRData>>,
wm: Rc<RefCell<WindowManager>>,
just_resumed: bool,
meta: Option<FrameMeta>,
stereo: Option<StereoMode>,
cur_image: Option<Arc<ImageView>>,
}
impl WayVRBackend {
pub fn new(
app: &state::AppState,
wvr: Rc<RefCell<WayVRData>>,
display: wayvr::display::DisplayHandle,
resolution: [u16; 2],
name: Arc<str>,
xr_backend: XrBackend,
wayvr: Rc<RefCell<WayVRData>>,
window: wayvr::window::WindowHandle,
) -> anyhow::Result<Self> {
let pipeline = app.gfx.create_pipeline(
app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic
app.gfx_extras.shaders.get("frag_srgb").unwrap(), // want panic
WPipelineCreateInfo::new(app.gfx.surface_format)
.use_updatable_descriptors(smallvec![0]),
)?;
let buf_alpha = app
.gfx
.empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::UNIFORM_BUFFER, 1)?;
let set0 = pipeline.uniform_sampler(
0,
app.gfx_extras.fallback_image.clone(),
app.gfx.texture_filter,
)?;
let set1 = pipeline.buffer(1, buf_alpha.clone())?;
let pass = pipeline.create_pass(
[resolution[0] as _, resolution[1] as _],
app.gfx_extras.quad_verts.clone(),
0..4,
0..1,
vec![set0, set1],
&Default::default(),
)?;
let wm = wayvr.borrow().data.state.wm.clone();
Ok(Self {
pipeline,
pass,
buf_alpha,
context: Rc::new(RefCell::new(WayVRContext::new(wvr, display))),
graphics: app.gfx.clone(),
image: None,
resolution,
name,
pipeline: None,
wayvr,
wm,
window,
mouse_transform: Affine2::IDENTITY,
interaction_transform: Some(ui_transform([resolution[0] as _, resolution[1] as _])), //TODO:dynamic
interaction_transform: None,
just_resumed: false,
meta: None,
stereo: if matches!(xr_backend, XrBackend::OpenXR) {
Some(StereoMode::None)
} else {
None
},
cur_image: None,
})
}
}
pub fn get_or_create_display_by_name(
app: &mut AppState,
wayvr: &mut WayVRData,
disp_name: &str,
) -> anyhow::Result<display::DisplayHandle> {
let disp_handle =
if let Some(disp) = WayVR::get_display_by_name(&wayvr.data.state.displays, disp_name) {
disp
} else {
let conf_display = app
.session
.wayvr_config
.get_display(disp_name)
.ok_or_else(|| anyhow::anyhow!("Cannot find display named \"{disp_name}\""))?
.clone();
let disp_handle = wayvr.data.state.create_display(
conf_display.width,
conf_display.height,
disp_name,
conf_display.primary.unwrap_or(false),
)?;
wayvr.overlays_to_create.push(OverlayToCreate {
conf_display,
disp_handle,
});
disp_handle
};
Ok(disp_handle)
}
pub fn executable_exists_in_path(command: &str) -> bool {
let Ok(path) = std::env::var("PATH") else {
return false; // very unlikely to happen
};
for dir in path.split(':') {
let exec_path = std::path::PathBuf::from(dir).join(command);
if exec_path.exists() && exec_path.is_file() {
return true; // executable found
}
}
false
}
fn toggle_dashboard<O>(
app: &mut AppState,
overlays: &mut OverlayWindowManager<O>,
wayvr: &mut WayVRData,
) -> anyhow::Result<()>
where
O: Default,
{
let Some(conf_dash) = app.session.wayvr_config.dashboard.clone() else {
anyhow::bail!("Dashboard is not configured");
};
if !wayvr.dashboard_executed && !executable_exists_in_path(&conf_dash.exec) {
anyhow::bail!("Executable \"{}\" not found", &conf_dash.exec);
}
let (newly_created, disp_handle) = wayvr.data.state.get_or_create_dashboard_display(
DASHBOARD_WIDTH,
DASHBOARD_HEIGHT,
DASHBOARD_DISPLAY_NAME,
)?;
if newly_created {
log::info!("Creating dashboard overlay");
let overlay = OverlayWindowData::from_config(OverlayWindowConfig {
default_state: OverlayWindowState {
interactable: true,
grabbable: true,
curvature: Some(0.15),
transform: Affine3A::from_scale_rotation_translation(
Vec3::ONE * 2.0,
Quat::IDENTITY,
vec3(0.0, -0.35, -1.75),
),
..OverlayWindowState::default()
},
z_order: Z_ORDER_DASHBOARD,
show_on_spawn: true,
global: true,
..create_overlay(
app,
DASHBOARD_DISPLAY_NAME,
OverlayToCreate {
disp_handle,
conf_display: config_wayvr::WayVRDisplay {
attach_to: None,
width: DASHBOARD_WIDTH,
height: DASHBOARD_HEIGHT,
scale: None,
rotation: None,
pos: None,
primary: None,
},
},
)?
});
let overlay_id = overlays.add(overlay, app);
wayvr.set_overlay_display_handle(overlay_id, disp_handle);
let args_vec = &conf_dash
.args
.as_ref()
.map_or_else(Vec::new, |args| ipc_server::gen_args_vec(args.as_str()));
let env_vec = &conf_dash
.env
.as_ref()
.map_or_else(Vec::new, |env| ipc_server::gen_env_vec(env));
let mut userdata = HashMap::new();
userdata.insert(String::from("type"), String::from("dashboard"));
// Start dashboard specified in the WayVR config
let _process_handle_unused = wayvr.data.state.spawn_process(
disp_handle,
&conf_dash.exec,
args_vec,
env_vec,
conf_dash.working_dir.as_deref(),
userdata,
)?;
wayvr.dashboard_executed = true;
return Ok(());
}
let display = wayvr.data.state.displays.get(&disp_handle).unwrap(); // safe
let Some(overlay_id) = display.overlay_id else {
anyhow::bail!("Overlay ID not set for dashboard display");
};
let cur_visibility = !display.visible;
app.ipc_server
.broadcast(PacketServer::WvrStateChanged(if cur_visibility {
WvrStateChanged::DashboardShown
} else {
WvrStateChanged::DashboardHidden
}));
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Id(overlay_id),
Box::new(move |app, o| {
o.toggle(app);
}),
)));
Ok(())
}
fn create_overlay(
app: &mut AppState,
name: &str,
cell: OverlayToCreate,
) -> anyhow::Result<OverlayWindowConfig> {
let conf_display = &cell.conf_display;
let disp_handle = cell.disp_handle;
let mut overlay = create_wayvr_display_overlay(
app,
conf_display.width,
conf_display.height,
disp_handle,
conf_display.scale.unwrap_or(1.0),
name,
)?;
if let Some(attach_to) = &conf_display.attach_to {
overlay.default_state.positioning = attach_to.get_positioning();
}
let rot = conf_display
.rotation
.as_ref()
.map_or(glam::Quat::IDENTITY, |rot| {
glam::Quat::from_axis_angle(Vec3::from_slice(&rot.axis), f32::to_radians(rot.angle))
});
let pos = conf_display
.pos
.as_ref()
.map_or(Vec3::NEG_Z, |pos| Vec3::from_slice(pos));
overlay.default_state.transform = Affine3A::from_rotation_translation(rot, pos);
Ok(overlay)
}
pub fn create_queued_displays<O>(
app: &mut AppState,
data: &mut WayVRData,
overlays: &mut OverlayWindowManager<O>,
) -> anyhow::Result<()>
where
O: Default,
{
let overlays_to_create = std::mem::take(&mut data.overlays_to_create);
for cell in overlays_to_create {
let Some(disp) = data.data.state.displays.get(&cell.disp_handle) else {
continue; // this shouldn't happen
};
let name = disp.name.clone();
let disp_handle = cell.disp_handle;
let overlay = OverlayWindowData::from_config(create_overlay(app, name.as_str(), cell)?);
let overlay_id = overlays.add(overlay, app); // Insert freshly created WayVR overlay into wlx stack
data.set_overlay_display_handle(overlay_id, disp_handle);
}
Ok(())
}
impl WayVRBackend {
fn ensure_software_data(
&mut self,
data: &wayvr::egl_data::RenderSoftwarePixelsData,
) -> anyhow::Result<()> {
let mut upload = self
.graphics
.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
let tex = upload.upload_image(
u32::from(data.width),
u32::from(data.height),
Format::R8G8B8A8_UNORM,
&data.data,
)?;
// FIXME: can we use _buffers_ here?
upload.build_and_execute_now()?;
//buffers.push(upload.build()?);
self.image = Some(ImageData {
vk_image: tex.clone(),
vk_image_view: ImageView::new_default(tex).unwrap(),
});
Ok(())
}
fn ensure_dmabuf_data(
&mut self,
data: &wayvr::egl_data::RenderDMAbufData,
) -> anyhow::Result<()> {
if self.image.is_some() {
return Ok(()); // already initialized and automatically updated due to direct zero-copy textue access
}
// 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 ctx = self.context.borrow_mut();
let wayvr = ctx.wayvr.borrow_mut();
let Some(disp) = wayvr.data.state.displays.get(&ctx.display) else {
anyhow::bail!("Failed to fetch WayVR display")
};
let frame = DmabufFrame {
format: FrameFormat {
width: u32::from(disp.width),
height: u32::from(disp.height),
fourcc: FourCC {
value: data.mod_info.fourcc,
},
modifier: data.mod_info.modifiers[0], /* possibly not proper? */
..Default::default()
},
num_planes: 1,
planes,
..Default::default()
};
drop(wayvr);
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,
ImageTiling::DrmFormatModifier,
layouts,
&data.mod_info.modifiers,
)?;
self.image = Some(ImageData {
vk_image: tex.clone(),
vk_image_view: ImageView::new_default(tex).unwrap(),
});
Ok(())
}
}
impl OverlayBackend for WayVRBackend {
fn init(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
Ok(())
}
fn pause(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
let ctx = self.context.borrow_mut();
let wayvr = &mut ctx.wayvr.borrow_mut().data;
wayvr.state.set_display_visible(ctx.display, false);
Ok(())
}
fn resume(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
let ctx = self.context.borrow_mut();
let wayvr = &mut ctx.wayvr.borrow_mut().data;
wayvr.state.set_display_visible(ctx.display, true);
self.just_resumed = true;
Ok(())
}
fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result<ShouldRender> {
let ctx = self.context.borrow();
let mut wayvr = ctx.wayvr.borrow_mut();
let redrawn = match wayvr.data.render_display(ctx.display) {
Ok(r) => r,
Err(e) => {
log::error!("render_display failed: {e}");
return Ok(ShouldRender::Unable);
}
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
let wm = self.wm.borrow();
let Some(window) = wm.windows.get(&self.window) else {
log::debug!(
"{:?}: WayVR overlay without matching window entry",
self.name
);
return Ok(ShouldRender::Unable);
};
if redrawn {
Ok(ShouldRender::Should)
} else {
Ok(ShouldRender::Can)
}
with_states(window.toplevel.wl_surface(), |states| {
if let Some(surf) = SurfaceBufWithImage::get_from_surface(states) {
let mut meta = FrameMeta {
extent: surf.image.image().extent(),
format: surf.image.format(),
..Default::default()
};
if let Some(pipeline) = self.pipeline.as_mut() {
meta.extent[2] = pipeline.get_depth();
if self
.meta
.is_some_and(|old| old.extent[..2] != meta.extent[..2])
{
pipeline.set_extent(app, [meta.extent[0] as _, meta.extent[1] as _])?;
self.interaction_transform =
Some(ui_transform(meta.extent.extent_u32arr()));
}
} else {
let pipeline =
ScreenPipeline::new(&meta, app, self.stereo.unwrap_or(StereoMode::None))?;
meta.extent[2] = pipeline.get_depth();
self.pipeline = Some(pipeline);
self.interaction_transform = Some(ui_transform(meta.extent.extent_u32arr()));
}
self.meta = Some(meta);
if self
.cur_image
.as_ref()
.is_none_or(|i| *i.image() != *surf.image.image())
{
self.cur_image = Some(surf.image);
Ok(ShouldRender::Should)
} else {
Ok(ShouldRender::Can)
}
} else {
Ok(ShouldRender::Unable)
}
})
}
fn render(
&mut self,
_app: &mut state::AppState,
app: &mut state::AppState,
rdr: &mut RenderResources,
) -> anyhow::Result<()> {
let ctx = self.context.borrow();
let wayvr = ctx.wayvr.borrow_mut();
let mouse = None; //TODO: mouse cursor
let image = self.cur_image.as_ref().unwrap().clone();
let data = wayvr
.data
.state
.get_render_data(ctx.display)
.context("Failed to fetch render data")?
.clone();
self.pipeline
.as_mut()
.unwrap()
.render(image, mouse, app, rdr)?;
drop(wayvr);
drop(ctx);
match data {
wayvr::egl_data::RenderData::Dmabuf(data) => {
self.ensure_dmabuf_data(&data)?;
}
wayvr::egl_data::RenderData::Software(data) => {
if let Some(new_frame) = &data {
self.ensure_software_data(new_frame)?;
}
}
}
let image = self.image.as_ref().unwrap();
self.pass
.update_sampler(0, image.vk_image_view.clone(), self.graphics.texture_filter)?;
self.buf_alpha.write()?[0] = rdr.alpha;
rdr.cmd_buf_single().run_ref(&self.pass)?;
Ok(())
}
fn frame_meta(&mut self) -> Option<FrameMeta> {
Some(FrameMeta {
extent: [self.resolution[0] as u32, self.resolution[1] as u32, 1],
..Default::default()
})
self.meta
}
fn notify(
@@ -581,24 +182,17 @@ impl OverlayBackend for WayVRBackend {
}
fn on_hover(&mut self, _app: &mut state::AppState, hit: &input::PointerHit) -> HoverResult {
let ctx = self.context.borrow();
let wayvr = &mut ctx.wayvr.borrow_mut();
if let Some(disp) = wayvr.data.state.displays.get(&ctx.display) {
if let Some(window) = self.wm.borrow().windows.get(&self.window) {
let pos = self.mouse_transform.transform_point2(hit.uv);
let x = ((pos.x * f32::from(disp.width)) as i32).max(0);
let y = ((pos.y * f32::from(disp.height)) as i32).max(0);
let x = ((pos.x * (window.size_x as f32)) as u32).max(0);
let y = ((pos.y * (window.size_y as f32)) as u32).max(0);
let ctx = self.context.borrow();
wayvr
.data
.state
.send_mouse_move(ctx.display, x as u32, y as u32);
let wayvr = &mut self.wayvr.borrow_mut().data;
wayvr.state.send_mouse_move(self.window, x, y);
}
HoverResult {
haptics: wayvr.pending_haptics.take(),
haptics: None, // haptics are handled via task
consume: true,
}
}
@@ -617,10 +211,9 @@ impl OverlayBackend for WayVRBackend {
None
}
} {
let ctx = self.context.borrow();
let wayvr = &mut ctx.wayvr.borrow_mut().data;
let wayvr = &mut self.wayvr.borrow_mut().data;
if pressed {
wayvr.state.send_mouse_down(ctx.display, index);
wayvr.state.send_mouse_down(self.window, index);
} else {
wayvr.state.send_mouse_up(index);
}
@@ -633,233 +226,34 @@ impl OverlayBackend for WayVRBackend {
_hit: &input::PointerHit,
delta: WheelDelta,
) {
let ctx = self.context.borrow();
ctx.wayvr.borrow_mut().data.state.send_mouse_scroll(delta);
self.wayvr.borrow_mut().data.state.send_mouse_scroll(delta);
}
fn get_interaction_transform(&mut self) -> Option<Affine2> {
self.interaction_transform
}
fn get_attrib(&self, _attrib: BackendAttrib) -> Option<BackendAttribValue> {
None
}
fn set_attrib(&mut self, _app: &mut AppState, _value: BackendAttribValue) -> bool {
false
}
}
#[allow(dead_code)]
pub fn create_wayvr_display_overlay(
app: &mut state::AppState,
display_width: u16,
display_height: u16,
display_handle: wayvr::display::DisplayHandle,
display_scale: f32,
name: &str,
) -> anyhow::Result<OverlayWindowConfig> {
let wayland_server = app
.wayland_server
.as_ref()
.map(|r| r.clone())
.context("wayland_server unavailable")?;
let backend = Box::new(WayVRBackend::new(
app,
wayland_server,
display_handle,
[display_width, display_height],
)?);
let category = if name == DASHBOARD_DISPLAY_NAME {
OverlayCategory::Dashboard
} else {
OverlayCategory::WayVR
};
Ok(OverlayWindowConfig {
name: format!("WVR-{name}").into(),
keyboard_focus: Some(KeyboardFocus::WayVR),
category,
default_state: OverlayWindowState {
interactable: true,
grabbable: true,
transform: Affine3A::from_scale_rotation_translation(
Vec3::ONE * display_scale,
Quat::IDENTITY,
vec3(0.0, -0.1, -1.0),
),
..OverlayWindowState::default()
},
..OverlayWindowConfig::from_backend(backend)
})
}
fn show_display<O>(
wayvr: &mut WayVRData,
overlays: &mut OverlayWindowManager<O>,
app: &mut AppState,
display_name: &str,
) where
O: Default,
{
if let Some(display) = WayVR::get_display_by_name(&wayvr.data.state.displays, display_name) {
if let Some(overlay_id) = wayvr.display_handle_map.get(&display)
&& let Some(overlay) = overlays.mut_by_id(*overlay_id)
{
overlay.config.activate(app);
}
wayvr.data.state.set_display_visible(display, true);
}
}
fn action_app_click<O>(
app: &mut AppState,
overlays: &mut OverlayWindowManager<O>,
catalog_name: &Arc<str>,
app_name: &Arc<str>,
) -> anyhow::Result<()>
where
O: Default,
{
let wayland_server = app
.wayland_server
.as_ref()
.map(|r| r.clone())
.context("wayland_server unavailable")?;
let catalog = app
.session
.wayvr_config
.get_catalog(catalog_name)
.ok_or_else(|| anyhow::anyhow!("Failed to get catalog \"{catalog_name}\""))?
.clone();
if let Some(app_entry) = catalog.get_app(app_name) {
let mut wayvr = wayland_server.borrow_mut();
let disp_handle = get_or_create_display_by_name(
app,
&mut wayvr,
&app_entry.target_display.to_lowercase(),
)?;
let args_vec = &app_entry
.args
.as_ref()
.map_or_else(Vec::new, |args| ipc_server::gen_args_vec(args.as_str()));
let env_vec = &app_entry
.env
.as_ref()
.map_or_else(Vec::new, |env| ipc_server::gen_env_vec(env));
// Terminate existing process if required
if let Some(process_handle) =
wayvr
.data
.state
.process_query(disp_handle, &app_entry.exec, args_vec, env_vec)
{
// Terminate process
wayvr.data.terminate_process(process_handle);
} else {
// Spawn process
wayvr.data.state.spawn_process(
disp_handle,
&app_entry.exec,
args_vec,
env_vec,
None,
HashMap::default(),
)?;
show_display::<O>(&mut wayvr, overlays, app, app_entry.target_display.as_str());
fn get_attrib(&self, attrib: BackendAttrib) -> Option<BackendAttribValue> {
match attrib {
BackendAttrib::Stereo => self.stereo.map(|s| BackendAttribValue::Stereo(s)),
_ => None,
}
}
Ok(())
}
pub fn action_display_click<O>(
app: &mut AppState,
overlays: &mut OverlayWindowManager<O>,
display_name: &Arc<str>,
action: &WayVRDisplayClickAction,
) -> anyhow::Result<()>
where
O: Default,
{
let wayland_server = app
.wayland_server
.clone()
.context("wayland_server unavailable")?;
let mut wayvr = wayland_server.borrow_mut();
let Some(handle) = WayVR::get_display_by_name(&wayvr.data.state.displays, display_name) else {
return Ok(());
};
let Some(display) = wayvr.data.state.displays.get_mut(&handle) else {
return Ok(());
};
let Some(overlay_id) = display.overlay_id else {
return Ok(());
};
let Some(overlay) = overlays.mut_by_id(overlay_id) else {
return Ok(());
};
match action {
WayVRDisplayClickAction::ToggleVisibility => {
// Toggle visibility
overlay.config.toggle(app);
}
WayVRDisplayClickAction::Reset => {
// Show it at the front
overlay.config.reset(app, true);
}
}
Ok(())
}
pub fn wayvr_action<O>(
app: &mut AppState,
overlays: &mut OverlayWindowManager<O>,
action: &WayVRAction,
) where
O: Default,
{
match action {
WayVRAction::AppClick {
catalog_name,
app_name,
} => {
if let Err(e) = action_app_click(app, overlays, catalog_name, app_name) {
// Happens if something went wrong with initialization
// or input exec path is invalid. Do nothing, just print an error
error_toast(app, "action_app_click failed", e);
}
}
WayVRAction::DisplayClick {
display_name,
action,
} => {
if let Err(e) = action_display_click::<O>(app, overlays, display_name, action) {
error_toast(app, "action_display_click failed", e);
}
}
WayVRAction::ToggleDashboard => {
if let Some(wayland_server) = app.wayland_server.as_ref().map(|r| r.clone())
&& let Err(e) =
toggle_dashboard::<O>(app, overlays, &mut wayland_server.borrow_mut())
{
error_toast(app, "toggle_dashboard failed", e);
fn set_attrib(&mut self, app: &mut AppState, value: BackendAttribValue) -> bool {
match value {
BackendAttribValue::Stereo(new) => {
if let Some(stereo) = self.stereo.as_mut() {
log::debug!("{}: stereo: {stereo:?} → {new:?}", self.name);
*stereo = new;
if let Some(pipeline) = self.pipeline.as_mut() {
pipeline.set_stereo(app, new).unwrap(); // only panics if gfx is dead
}
true
} else {
false
}
}
_ => false,
}
}
}

View File

@@ -80,7 +80,13 @@ impl AppState {
#[cfg(feature = "wayvr")]
let wayland_server = session
.wayvr_config
.post_load(&session.config, &mut tasks, wayvr_signals.clone())
.post_load(
gfx.clone(),
&gfx_extras,
&session.config,
&mut tasks,
wayvr_signals.clone(),
)
.inspect_err(|e| log::error!("Could not initialize wayland server: {e:?}"))
.ok();