rewrite built-in wayland compositor egl → vulkan
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 */ }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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],
|
||||
)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
>;
|
||||
111
wlx-overlay-s/src/backend/wayvr/image_importer.rs
Normal file
111
wlx-overlay-s/src/backend/wayvr/image_importer.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(¤t.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,
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::{
|
||||
};
|
||||
|
||||
pub mod backend;
|
||||
mod capture;
|
||||
pub mod capture;
|
||||
#[cfg(feature = "wayland")]
|
||||
pub mod mirror;
|
||||
#[cfg(feature = "pipewire")]
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user