WayVR: External process support, various tweaks and bugfixes
- Support for foreign wayland clients, WayVR process is now separated into managed and external one - Add `run_compositor_at_start` global param - Add `primary` display param - Export WAYLAND_DISPLAY number into XDG_RUNTIME_DIR directory - Bugfix: Redraw event is not triggered after despawning a process - Sanitization in WayVRConfig::post_load()
This commit is contained in:
@@ -328,9 +328,7 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "wayvr")]
|
#[cfg(feature = "wayvr")]
|
||||||
if let Some(wayvr) = &state.wayvr {
|
crate::overlays::wayvr::tick_events::<OpenVrOverlayData>(&mut state, &mut overlays)?;
|
||||||
wayvr.borrow_mut().tick_events()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
log::trace!("Rendering frame");
|
log::trace!("Rendering frame");
|
||||||
|
|
||||||
|
|||||||
@@ -364,9 +364,7 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayvr")]
|
#[cfg(feature = "wayvr")]
|
||||||
if let Some(wayvr) = &app_state.wayvr {
|
crate::overlays::wayvr::tick_events::<OpenXrOverlayData>(&mut app_state, &mut overlays)?;
|
||||||
wayvr.borrow_mut().tick_events()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for o in overlays.iter_mut() {
|
for o in overlays.iter_mut() {
|
||||||
if !o.state.want_visible {
|
if !o.state.want_visible {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{io::Read, os::unix::net::UnixStream, sync::Arc};
|
use std::{io::Read, os::unix::net::UnixStream, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::input::Keycode,
|
backend::input::Keycode,
|
||||||
@@ -7,9 +7,11 @@ use smithay::{
|
|||||||
utils::SerialCounter,
|
utils::SerialCounter,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
comp::{self},
|
comp::{self},
|
||||||
display, process,
|
display, process, ProcessWayVREnv,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct WayVRClient {
|
pub struct WayVRClient {
|
||||||
@@ -33,22 +35,29 @@ pub struct WayVRManager {
|
|||||||
pub clients: Vec<WayVRClient>,
|
pub clients: Vec<WayVRClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_display_auth_from_pid(pid: i32) -> anyhow::Result<String> {
|
fn get_wayvr_env_from_pid(pid: i32) -> anyhow::Result<ProcessWayVREnv> {
|
||||||
let path = format!("/proc/{}/environ", pid);
|
let path = format!("/proc/{}/environ", pid);
|
||||||
let mut env_data = String::new();
|
let mut env_data = String::new();
|
||||||
std::fs::File::open(path)?.read_to_string(&mut env_data)?;
|
std::fs::File::open(path)?.read_to_string(&mut env_data)?;
|
||||||
|
|
||||||
let lines: Vec<&str> = env_data.split('\0').filter(|s| !s.is_empty()).collect();
|
let lines: Vec<&str> = env_data.split('\0').filter(|s| !s.is_empty()).collect();
|
||||||
|
|
||||||
|
let mut env = ProcessWayVREnv {
|
||||||
|
display_auth: None,
|
||||||
|
display_name: None,
|
||||||
|
};
|
||||||
|
|
||||||
for line in lines {
|
for line in lines {
|
||||||
if let Some((key, value)) = line.split_once('=') {
|
if let Some((key, value)) = line.split_once('=') {
|
||||||
if key == "WAYVR_DISPLAY_AUTH" {
|
if key == "WAYVR_DISPLAY_AUTH" {
|
||||||
return Ok(String::from(value));
|
env.display_auth = Some(String::from(value));
|
||||||
|
} else if key == "WAYVR_DISPLAY_NAME" {
|
||||||
|
env.display_name = Some(String::from(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anyhow::bail!("Failed to get display auth from PID {}", pid);
|
Ok(env)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WayVRManager {
|
impl WayVRManager {
|
||||||
@@ -73,6 +82,10 @@ impl WayVRManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_client(&mut self, client: WayVRClient) {
|
||||||
|
self.clients.push(client);
|
||||||
|
}
|
||||||
|
|
||||||
fn accept_connection(
|
fn accept_connection(
|
||||||
&mut self,
|
&mut self,
|
||||||
stream: UnixStream,
|
stream: UnixStream,
|
||||||
@@ -86,27 +99,46 @@ impl WayVRManager {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let creds = client.get_credentials(&self.display.handle())?;
|
let creds = client.get_credentials(&self.display.handle())?;
|
||||||
let auth_key = get_display_auth_from_pid(creds.pid)?;
|
|
||||||
|
let process_env = get_wayvr_env_from_pid(creds.pid)?;
|
||||||
|
|
||||||
// Find suitable auth key from the process list
|
// Find suitable auth key from the process list
|
||||||
for process in processes.vec.iter().flatten() {
|
for p in processes.vec.iter().flatten() {
|
||||||
let process = &process.obj;
|
if let process::Process::Managed(process) = &p.obj {
|
||||||
|
if let Some(auth_key) = &process_env.display_auth {
|
||||||
// Find process with matching auth key
|
// Find process with matching auth key
|
||||||
if process.auth_key.as_str() == auth_key {
|
if process.auth_key.as_str() == auth_key {
|
||||||
// Check if display handle is valid
|
// Check if display handle is valid
|
||||||
if displays.get(&process.display_handle).is_some() {
|
if displays.get(&process.display_handle).is_some() {
|
||||||
// Add client
|
// Add client
|
||||||
self.clients.push(WayVRClient {
|
self.add_client(WayVRClient {
|
||||||
client,
|
client,
|
||||||
display_handle: process.display_handle,
|
display_handle: process.display_handle,
|
||||||
pid: creds.pid as u32,
|
pid: creds.pid as u32,
|
||||||
});
|
});
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
anyhow::bail!("Process auth key is invalid or selected display is non-existent");
|
|
||||||
|
// This is a new process which we didn't met before.
|
||||||
|
// Treat external processes exclusively (spawned by the user or external program)
|
||||||
|
log::warn!(
|
||||||
|
"External process ID {} connected to this Wayland server",
|
||||||
|
creds.pid
|
||||||
|
);
|
||||||
|
|
||||||
|
self.state
|
||||||
|
.wayvr_tasks
|
||||||
|
.send(WayVRTask::NewExternalProcess(ExternalProcessRequest {
|
||||||
|
env: process_env,
|
||||||
|
client,
|
||||||
|
pid: creds.pid as u32,
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accept_connections(
|
fn accept_connections(
|
||||||
@@ -164,6 +196,15 @@ impl WayVRManager {
|
|||||||
|
|
||||||
const STARTING_WAYLAND_ADDR_IDX: u32 = 20;
|
const STARTING_WAYLAND_ADDR_IDX: u32 = 20;
|
||||||
|
|
||||||
|
fn export_display_number(display_num: u32) -> anyhow::Result<()> {
|
||||||
|
let mut path = std::env::var("XDG_RUNTIME_DIR")
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|_| PathBuf::from("/tmp"));
|
||||||
|
path.push("wayvr.disp");
|
||||||
|
std::fs::write(path, format!("{}\n", display_num)).unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn create_wayland_listener() -> anyhow::Result<(super::WaylandEnv, wayland_server::ListeningSocket)>
|
fn create_wayland_listener() -> anyhow::Result<(super::WaylandEnv, wayland_server::ListeningSocket)>
|
||||||
{
|
{
|
||||||
let mut env = super::WaylandEnv {
|
let mut env = super::WaylandEnv {
|
||||||
@@ -194,5 +235,7 @@ fn create_wayland_listener() -> anyhow::Result<(super::WaylandEnv, wayland_serve
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let _ = export_display_number(env.display_num);
|
||||||
|
|
||||||
Ok((env, listener))
|
Ok((env, listener))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ pub struct Display {
|
|||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
pub overlay_id: Option<OverlayID>,
|
pub overlay_id: Option<OverlayID>,
|
||||||
pub wants_redraw: bool,
|
pub wants_redraw: bool,
|
||||||
|
pub primary: bool,
|
||||||
wm: Rc<RefCell<window::WindowManager>>,
|
wm: Rc<RefCell<window::WindowManager>>,
|
||||||
pub displayed_windows: Vec<DisplayWindow>,
|
pub displayed_windows: Vec<DisplayWindow>,
|
||||||
wayland_env: super::WaylandEnv,
|
wayland_env: super::WaylandEnv,
|
||||||
@@ -81,6 +82,7 @@ impl Display {
|
|||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
primary: bool,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let tex_format = ffi::RGBA;
|
let tex_format = ffi::RGBA;
|
||||||
let internal_format = ffi::RGBA8;
|
let internal_format = ffi::RGBA8;
|
||||||
@@ -116,6 +118,7 @@ impl Display {
|
|||||||
gles_texture,
|
gles_texture,
|
||||||
wayland_env,
|
wayland_env,
|
||||||
visible: true,
|
visible: true,
|
||||||
|
primary,
|
||||||
overlay_id: None,
|
overlay_id: None,
|
||||||
tasks: SyncEventQueue::new(),
|
tasks: SyncEventQueue::new(),
|
||||||
})
|
})
|
||||||
@@ -239,7 +242,12 @@ impl Display {
|
|||||||
|
|
||||||
pub fn set_visible(&mut self, visible: bool) {
|
pub fn set_visible(&mut self, visible: bool) {
|
||||||
log::info!("Display \"{}\" visible: {}", self.name.as_str(), visible);
|
log::info!("Display \"{}\" visible: {}", self.name.as_str(), visible);
|
||||||
self.visible = visible;
|
if self.visible != visible {
|
||||||
|
self.visible = visible;
|
||||||
|
if visible {
|
||||||
|
self.wants_redraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_mouse_move(&self, manager: &mut WayVRManager, x: u32, y: u32) {
|
pub fn send_mouse_move(&self, manager: &mut WayVRManager, x: u32, y: u32) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
mod client;
|
pub mod client;
|
||||||
mod comp;
|
mod comp;
|
||||||
pub mod display;
|
pub mod display;
|
||||||
pub mod egl_data;
|
pub mod egl_data;
|
||||||
@@ -45,9 +45,23 @@ impl WaylandEnv {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ProcessWayVREnv {
|
||||||
|
pub display_auth: Option<String>,
|
||||||
|
pub display_name: Option<String>, // Externally spawned process by a user script
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ExternalProcessRequest {
|
||||||
|
pub env: ProcessWayVREnv,
|
||||||
|
pub client: wayland_server::Client,
|
||||||
|
pub pid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum WayVRTask {
|
pub enum WayVRTask {
|
||||||
NewToplevel(ClientId, ToplevelSurface),
|
NewToplevel(ClientId, ToplevelSurface),
|
||||||
|
NewExternalProcess(ExternalProcessRequest),
|
||||||
ProcessTerminationRequest(process::ProcessHandle),
|
ProcessTerminationRequest(process::ProcessHandle),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +70,7 @@ pub struct WayVR {
|
|||||||
time_start: u64,
|
time_start: u64,
|
||||||
gles_renderer: GlesRenderer,
|
gles_renderer: GlesRenderer,
|
||||||
pub displays: display::DisplayVec,
|
pub displays: display::DisplayVec,
|
||||||
manager: client::WayVRManager,
|
pub manager: client::WayVRManager,
|
||||||
wm: Rc<RefCell<window::WindowManager>>,
|
wm: Rc<RefCell<window::WindowManager>>,
|
||||||
egl_data: Rc<egl_data::EGLData>,
|
egl_data: Rc<egl_data::EGLData>,
|
||||||
pub processes: process::ProcessVec,
|
pub processes: process::ProcessVec,
|
||||||
@@ -70,8 +84,13 @@ pub enum MouseIndex {
|
|||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum TickResult {
|
||||||
|
NewExternalProcess(ExternalProcessRequest), // Call WayVRManager::add_client after receiving this message
|
||||||
|
}
|
||||||
|
|
||||||
impl WayVR {
|
impl WayVR {
|
||||||
pub fn new() -> anyhow::Result<Self> {
|
pub fn new() -> anyhow::Result<Self> {
|
||||||
|
log::info!("Initializing WayVR");
|
||||||
let display: wayland_server::Display<Application> = wayland_server::Display::new()?;
|
let display: wayland_server::Display<Application> = wayland_server::Display::new()?;
|
||||||
let dh = display.handle();
|
let dh = display.handle();
|
||||||
let compositor = compositor::CompositorState::new::<Application>(&dh);
|
let compositor = compositor::CompositorState::new::<Application>(&dh);
|
||||||
@@ -140,7 +159,9 @@ impl WayVR {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick_events(&mut self) -> anyhow::Result<()> {
|
pub fn tick_events(&mut self) -> anyhow::Result<Vec<TickResult>> {
|
||||||
|
let mut res: Vec<TickResult> = Vec::new();
|
||||||
|
|
||||||
// Check for redraw events
|
// Check for redraw events
|
||||||
self.displays.iter_mut(&mut |_, disp| {
|
self.displays.iter_mut(&mut |_, disp| {
|
||||||
for disp_window in &disp.displayed_windows {
|
for disp_window in &disp.displayed_windows {
|
||||||
@@ -159,17 +180,18 @@ impl WayVR {
|
|||||||
SmallVec::new();
|
SmallVec::new();
|
||||||
self.processes.iter_mut(&mut |handle, process| {
|
self.processes.iter_mut(&mut |handle, process| {
|
||||||
if !process.is_running() {
|
if !process.is_running() {
|
||||||
to_remove.push((handle, process.display_handle));
|
to_remove.push((handle, process.display_handle()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (p_handle, disp_handle) in to_remove {
|
for (p_handle, disp_handle) in to_remove {
|
||||||
self.processes.remove(&p_handle);
|
self.processes.remove(&p_handle);
|
||||||
|
|
||||||
if let Some(display) = self.displays.get(&disp_handle) {
|
if let Some(display) = self.displays.get_mut(&disp_handle) {
|
||||||
display
|
display
|
||||||
.tasks
|
.tasks
|
||||||
.send(display::DisplayTask::ProcessCleanup(p_handle));
|
.send(display::DisplayTask::ProcessCleanup(p_handle));
|
||||||
|
display.wants_redraw = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +201,9 @@ impl WayVR {
|
|||||||
|
|
||||||
while let Some(task) = self.tasks.read() {
|
while let Some(task) = self.tasks.read() {
|
||||||
match task {
|
match task {
|
||||||
|
WayVRTask::NewExternalProcess(req) => {
|
||||||
|
res.push(TickResult::NewExternalProcess(req));
|
||||||
|
}
|
||||||
WayVRTask::NewToplevel(client_id, toplevel) => {
|
WayVRTask::NewToplevel(client_id, toplevel) => {
|
||||||
// Attach newly created toplevel surfaces to displays
|
// Attach newly created toplevel surfaces to displays
|
||||||
for client in &self.manager.clients {
|
for client in &self.manager.clients {
|
||||||
@@ -197,7 +222,7 @@ impl WayVR {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::error!(
|
log::error!(
|
||||||
"Failed to find process by PID {}. It was probably spawned externally.",
|
"WayVR window creation failed: Unexpected process PID {}. It wasn't registered before.",
|
||||||
client.pid
|
client.pid
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -215,7 +240,9 @@ impl WayVR {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.manager
|
self.manager
|
||||||
.tick_wayland(&mut self.displays, &mut self.processes)
|
.tick_wayland(&mut self.displays, &mut self.processes)?;
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick_finish(&mut self) -> anyhow::Result<()> {
|
pub fn tick_finish(&mut self) -> anyhow::Result<()> {
|
||||||
@@ -266,8 +293,22 @@ impl WayVR {
|
|||||||
.map(|display| display.dmabuf_data.clone())
|
.map(|display| display.dmabuf_data.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_display_by_name(&self, name: &str) -> Option<display::DisplayHandle> {
|
pub fn get_primary_display(displays: &DisplayVec) -> Option<display::DisplayHandle> {
|
||||||
for (idx, cell) in self.displays.vec.iter().enumerate() {
|
for (idx, cell) in displays.vec.iter().enumerate() {
|
||||||
|
if let Some(cell) = cell {
|
||||||
|
if 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 {
|
if let Some(cell) = cell {
|
||||||
if cell.obj.name == name {
|
if cell.obj.name == name {
|
||||||
return Some(DisplayVec::get_handle(cell, idx));
|
return Some(DisplayVec::get_handle(cell, idx));
|
||||||
@@ -276,11 +317,13 @@ impl WayVR {
|
|||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_display(
|
pub fn create_display(
|
||||||
&mut self,
|
&mut self,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
primary: bool,
|
||||||
) -> anyhow::Result<display::DisplayHandle> {
|
) -> anyhow::Result<display::DisplayHandle> {
|
||||||
let display = display::Display::new(
|
let display = display::Display::new(
|
||||||
self.wm.clone(),
|
self.wm.clone(),
|
||||||
@@ -290,6 +333,7 @@ impl WayVR {
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
name,
|
name,
|
||||||
|
primary,
|
||||||
)?;
|
)?;
|
||||||
Ok(self.displays.add(display))
|
Ok(self.displays.add(display))
|
||||||
}
|
}
|
||||||
@@ -308,15 +352,15 @@ impl WayVR {
|
|||||||
) -> Option<process::ProcessHandle> {
|
) -> Option<process::ProcessHandle> {
|
||||||
for (idx, cell) in self.processes.vec.iter().enumerate() {
|
for (idx, cell) in self.processes.vec.iter().enumerate() {
|
||||||
if let Some(cell) = &cell {
|
if let Some(cell) = &cell {
|
||||||
let process = &cell.obj;
|
if let process::Process::Managed(process) = &cell.obj {
|
||||||
if process.display_handle != display_handle
|
if process.display_handle != display_handle
|
||||||
|| process.exec_path != exec_path
|
|| process.exec_path != exec_path
|
||||||
|| process.args != args
|
|| process.args != args
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
return Some(process::ProcessVec::get_handle(cell, idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Some(process::ProcessVec::get_handle(cell, idx));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,6 +372,18 @@ impl WayVR {
|
|||||||
.send(WayVRTask::ProcessTerminationRequest(process_handle));
|
.send(WayVRTask::ProcessTerminationRequest(process_handle));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_external_process(
|
||||||
|
&mut self,
|
||||||
|
display_handle: display::DisplayHandle,
|
||||||
|
pid: u32,
|
||||||
|
) -> process::ProcessHandle {
|
||||||
|
self.processes
|
||||||
|
.add(process::Process::External(process::ExternalProcess {
|
||||||
|
display_handle,
|
||||||
|
pid,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spawn_process(
|
pub fn spawn_process(
|
||||||
&mut self,
|
&mut self,
|
||||||
display_handle: display::DisplayHandle,
|
display_handle: display::DisplayHandle,
|
||||||
@@ -341,16 +397,18 @@ impl WayVR {
|
|||||||
.ok_or(anyhow::anyhow!(STR_INVALID_HANDLE_DISP))?;
|
.ok_or(anyhow::anyhow!(STR_INVALID_HANDLE_DISP))?;
|
||||||
|
|
||||||
let res = display.spawn_process(exec_path, args, env)?;
|
let res = display.spawn_process(exec_path, args, env)?;
|
||||||
Ok(self.processes.add(process::Process {
|
Ok(self
|
||||||
auth_key: res.auth_key,
|
.processes
|
||||||
child: res.child,
|
.add(process::Process::Managed(process::WayVRProcess {
|
||||||
display_handle,
|
auth_key: res.auth_key,
|
||||||
exec_path: String::from(exec_path),
|
child: res.child,
|
||||||
args: args.iter().map(|x| String::from(*x)).collect(),
|
display_handle,
|
||||||
env: env
|
exec_path: String::from(exec_path),
|
||||||
.iter()
|
args: args.iter().map(|x| String::from(*x)).collect(),
|
||||||
.map(|(a, b)| (String::from(*a), String::from(*b)))
|
env: env
|
||||||
.collect(),
|
.iter()
|
||||||
}))
|
.map(|(a, b)| (String::from(*a), String::from(*b)))
|
||||||
|
.collect(),
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::gen_id;
|
|||||||
|
|
||||||
use super::display;
|
use super::display;
|
||||||
|
|
||||||
pub struct Process {
|
pub struct WayVRProcess {
|
||||||
pub auth_key: String,
|
pub auth_key: String,
|
||||||
pub child: std::process::Child,
|
pub child: std::process::Child,
|
||||||
pub display_handle: display::DisplayHandle,
|
pub display_handle: display::DisplayHandle,
|
||||||
@@ -12,7 +12,40 @@ pub struct Process {
|
|||||||
pub env: Vec<(String, String)>,
|
pub env: Vec<(String, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Process {
|
pub struct ExternalProcess {
|
||||||
|
pub pid: u32,
|
||||||
|
pub display_handle: display::DisplayHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Process {
|
||||||
|
Managed(WayVRProcess), // Process spawned by WayVR
|
||||||
|
External(ExternalProcess), // External process not directly controlled by us
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Process {
|
||||||
|
pub fn display_handle(&self) -> display::DisplayHandle {
|
||||||
|
match self {
|
||||||
|
Process::Managed(p) => p.display_handle,
|
||||||
|
Process::External(p) => p.display_handle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_running(&mut self) -> bool {
|
||||||
|
match self {
|
||||||
|
Process::Managed(p) => p.is_running(),
|
||||||
|
Process::External(p) => p.is_running(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn terminate(&mut self) {
|
||||||
|
match self {
|
||||||
|
Process::Managed(p) => p.terminate(),
|
||||||
|
Process::External(p) => p.terminate(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for WayVRProcess {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
log::info!(
|
log::info!(
|
||||||
"Sending SIGTERM (graceful exit) to process {}",
|
"Sending SIGTERM (graceful exit) to process {}",
|
||||||
@@ -22,8 +55,8 @@ impl Drop for Process {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Process {
|
impl WayVRProcess {
|
||||||
pub fn is_running(&mut self) -> bool {
|
fn is_running(&mut self) -> bool {
|
||||||
match self.child.try_wait() {
|
match self.child.try_wait() {
|
||||||
Ok(Some(_exit_status)) => false,
|
Ok(Some(_exit_status)) => false,
|
||||||
Ok(None) => true,
|
Ok(None) => true,
|
||||||
@@ -35,7 +68,7 @@ impl Process {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn terminate(&mut self) {
|
fn terminate(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Gracefully stop process
|
// Gracefully stop process
|
||||||
libc::kill(self.child.id() as i32, libc::SIGTERM);
|
libc::kill(self.child.id() as i32, libc::SIGTERM);
|
||||||
@@ -43,13 +76,42 @@ impl Process {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ExternalProcess {
|
||||||
|
fn is_running(&self) -> bool {
|
||||||
|
if self.pid == 0 {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
std::fs::metadata(format!("/proc/{}", self.pid)).is_ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn terminate(&mut self) {
|
||||||
|
if self.pid != 0 {
|
||||||
|
unsafe {
|
||||||
|
// send SIGINT (^C)
|
||||||
|
libc::kill(self.pid as i32, libc::SIGINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.pid = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
gen_id!(ProcessVec, Process, ProcessCell, ProcessHandle);
|
gen_id!(ProcessVec, Process, ProcessCell, ProcessHandle);
|
||||||
|
|
||||||
pub fn find_by_pid(processes: &ProcessVec, pid: u32) -> Option<ProcessHandle> {
|
pub fn find_by_pid(processes: &ProcessVec, pid: u32) -> Option<ProcessHandle> {
|
||||||
for (idx, cell) in processes.vec.iter().enumerate() {
|
for (idx, cell) in processes.vec.iter().enumerate() {
|
||||||
if let Some(cell) = cell {
|
if let Some(cell) = cell {
|
||||||
if cell.obj.child.id() == pid {
|
match &cell.obj {
|
||||||
return Some(ProcessVec::get_handle(cell, idx));
|
Process::Managed(wayvr_process) => {
|
||||||
|
if wayvr_process.child.id() == pid {
|
||||||
|
return Some(ProcessVec::get_handle(cell, idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Process::External(external_process) => {
|
||||||
|
if external_process.pid == pid {
|
||||||
|
return Some(ProcessVec::get_handle(cell, idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
compile_error!("WayVR feature is not enabled");
|
compile_error!("WayVR feature is not enabled");
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
collections::{BTreeMap, HashMap},
|
collections::{BTreeMap, HashMap},
|
||||||
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -12,6 +14,7 @@ use crate::{
|
|||||||
backend::{
|
backend::{
|
||||||
overlay::RelativeTo,
|
overlay::RelativeTo,
|
||||||
task::{TaskContainer, TaskType},
|
task::{TaskContainer, TaskType},
|
||||||
|
wayvr,
|
||||||
},
|
},
|
||||||
config::{load_known_yaml, ConfigType},
|
config::{load_known_yaml, ConfigType},
|
||||||
overlays::wayvr::WayVRAction,
|
overlays::wayvr::WayVRAction,
|
||||||
@@ -63,6 +66,7 @@ pub struct WayVRDisplay {
|
|||||||
pub rotation: Option<Rotation>,
|
pub rotation: Option<Rotation>,
|
||||||
pub pos: Option<[f32; 3]>,
|
pub pos: Option<[f32; 3]>,
|
||||||
pub attach_to: Option<AttachTo>,
|
pub attach_to: Option<AttachTo>,
|
||||||
|
pub primary: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
@@ -79,6 +83,7 @@ impl WayVRCatalog {
|
|||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct WayVRConfig {
|
pub struct WayVRConfig {
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
|
pub run_compositor_at_start: bool,
|
||||||
pub catalogs: HashMap<String, WayVRCatalog>,
|
pub catalogs: HashMap<String, WayVRCatalog>,
|
||||||
pub displays: BTreeMap<String, WayVRDisplay>, // sorted alphabetically
|
pub displays: BTreeMap<String, WayVRDisplay>, // sorted alphabetically
|
||||||
}
|
}
|
||||||
@@ -92,7 +97,31 @@ impl WayVRConfig {
|
|||||||
self.displays.get(name)
|
self.displays.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn post_load(&self, tasks: &mut TaskContainer) {
|
pub fn get_default_display(&self) -> Option<(String, &WayVRDisplay)> {
|
||||||
|
for (disp_name, disp) in &self.displays {
|
||||||
|
if disp.primary.unwrap_or(false) {
|
||||||
|
return Some((disp_name.clone(), disp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn post_load(
|
||||||
|
&self,
|
||||||
|
tasks: &mut TaskContainer,
|
||||||
|
) -> anyhow::Result<Option<Rc<RefCell<wayvr::WayVR>>>> {
|
||||||
|
let primary_count = self
|
||||||
|
.displays
|
||||||
|
.iter()
|
||||||
|
.filter(|d| d.1.primary.unwrap_or(false))
|
||||||
|
.count();
|
||||||
|
|
||||||
|
if primary_count > 1 {
|
||||||
|
anyhow::bail!("Number of primary displays is more than 1")
|
||||||
|
} else if primary_count == 0 {
|
||||||
|
log::warn!("No primary display specified");
|
||||||
|
}
|
||||||
|
|
||||||
for (catalog_name, catalog) in &self.catalogs {
|
for (catalog_name, catalog) in &self.catalogs {
|
||||||
for app in &catalog.apps {
|
for app in &catalog.apps {
|
||||||
if let Some(b) = app.shown_at_start {
|
if let Some(b) = app.shown_at_start {
|
||||||
@@ -105,6 +134,14 @@ impl WayVRConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.run_compositor_at_start {
|
||||||
|
// Start Wayland server instantly
|
||||||
|
Ok(Some(Rc::new(RefCell::new(wayvr::WayVR::new()?))))
|
||||||
|
} else {
|
||||||
|
// Lazy-init WayVR later if the user requested
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
common::OverlayContainer,
|
common::OverlayContainer,
|
||||||
input::{self, InteractionHandler},
|
input::{self, InteractionHandler},
|
||||||
overlay::{ui_transform, OverlayData, OverlayRenderer, OverlayState, SplitOverlayBackend},
|
overlay::{ui_transform, OverlayData, OverlayRenderer, OverlayState, SplitOverlayBackend},
|
||||||
wayvr,
|
wayvr::{self, display, WayVR},
|
||||||
},
|
},
|
||||||
graphics::WlxGraphics,
|
graphics::WlxGraphics,
|
||||||
state::{self, AppState, KeyboardFocus},
|
state::{self, AppState, KeyboardFocus},
|
||||||
@@ -119,6 +119,121 @@ impl WayVRRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_or_create_display<O>(
|
||||||
|
app: &mut AppState,
|
||||||
|
wayvr: &mut WayVR,
|
||||||
|
disp_name: &str,
|
||||||
|
) -> anyhow::Result<(display::DisplayHandle, Option<OverlayData<O>>)>
|
||||||
|
where
|
||||||
|
O: Default,
|
||||||
|
{
|
||||||
|
let created_overlay: Option<OverlayData<O>>;
|
||||||
|
|
||||||
|
let disp_handle = if let Some(disp) = WayVR::get_display_by_name(&wayvr.displays, disp_name) {
|
||||||
|
created_overlay = None;
|
||||||
|
disp
|
||||||
|
} else {
|
||||||
|
let conf_display = app
|
||||||
|
.session
|
||||||
|
.wayvr_config
|
||||||
|
.get_display(disp_name)
|
||||||
|
.ok_or(anyhow::anyhow!(
|
||||||
|
"Cannot find display named \"{}\"",
|
||||||
|
disp_name
|
||||||
|
))?
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let disp_handle = wayvr.create_display(
|
||||||
|
conf_display.width,
|
||||||
|
conf_display.height,
|
||||||
|
disp_name,
|
||||||
|
conf_display.primary.unwrap_or(false),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut overlay = create_wayvr_display_overlay::<O>(
|
||||||
|
app,
|
||||||
|
conf_display.width,
|
||||||
|
conf_display.height,
|
||||||
|
disp_handle,
|
||||||
|
conf_display.scale.unwrap_or(1.0),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(attach_to) = &conf_display.attach_to {
|
||||||
|
overlay.state.relative_to = attach_to.get_relative_to();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rot) = &conf_display.rotation {
|
||||||
|
overlay.state.spawn_rotation = glam::Quat::from_axis_angle(
|
||||||
|
Vec3::from_slice(&rot.axis),
|
||||||
|
f32::to_radians(rot.angle),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pos) = &conf_display.pos {
|
||||||
|
overlay.state.spawn_point = Vec3A::from_slice(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
let display = wayvr.displays.get_mut(&disp_handle).unwrap(); // Never fails
|
||||||
|
display.overlay_id = Some(overlay.state.id);
|
||||||
|
|
||||||
|
created_overlay = Some(overlay);
|
||||||
|
|
||||||
|
disp_handle
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((disp_handle, created_overlay))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick_events<O>(app: &mut AppState, overlays: &mut OverlayContainer<O>) -> anyhow::Result<()>
|
||||||
|
where
|
||||||
|
O: Default,
|
||||||
|
{
|
||||||
|
if let Some(wayvr) = app.wayvr.clone() {
|
||||||
|
let res = wayvr.borrow_mut().tick_events()?;
|
||||||
|
|
||||||
|
for result in res {
|
||||||
|
match result {
|
||||||
|
wayvr::TickResult::NewExternalProcess(req) => {
|
||||||
|
let config = &app.session.wayvr_config;
|
||||||
|
|
||||||
|
let disp_name = if let Some(display_name) = req.env.display_name {
|
||||||
|
config
|
||||||
|
.get_display(display_name.as_str())
|
||||||
|
.map(|_| display_name)
|
||||||
|
} else {
|
||||||
|
config
|
||||||
|
.get_default_display()
|
||||||
|
.map(|(display_name, _)| display_name)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(disp_name) = disp_name {
|
||||||
|
let mut wayvr = wayvr.borrow_mut();
|
||||||
|
|
||||||
|
log::info!("Registering external process with PID {}", req.pid);
|
||||||
|
|
||||||
|
let (disp_handle, created_overlay) =
|
||||||
|
get_or_create_display::<O>(app, &mut wayvr, &disp_name)?;
|
||||||
|
|
||||||
|
wayvr.add_external_process(disp_handle, req.pid);
|
||||||
|
|
||||||
|
wayvr.manager.add_client(wayvr::client::WayVRClient {
|
||||||
|
client: req.client,
|
||||||
|
display_handle: disp_handle,
|
||||||
|
pid: req.pid,
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(created_overlay) = created_overlay {
|
||||||
|
overlays.add(created_overlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
impl WayVRRenderer {
|
impl WayVRRenderer {
|
||||||
fn ensure_dmabuf(&mut self, data: wayvr::egl_data::DMAbufData) -> anyhow::Result<()> {
|
fn ensure_dmabuf(&mut self, data: wayvr::egl_data::DMAbufData) -> anyhow::Result<()> {
|
||||||
if self.dmabuf_image.is_none() {
|
if self.dmabuf_image.is_none() {
|
||||||
@@ -285,10 +400,6 @@ fn action_app_click<O>(
|
|||||||
where
|
where
|
||||||
O: Default,
|
O: Default,
|
||||||
{
|
{
|
||||||
use crate::overlays::wayvr::create_wayvr_display_overlay;
|
|
||||||
|
|
||||||
let mut created_overlay: Option<OverlayData<O>> = None;
|
|
||||||
|
|
||||||
let wayvr = app.get_wayvr()?.clone();
|
let wayvr = app.get_wayvr()?.clone();
|
||||||
|
|
||||||
let catalog = app
|
let catalog = app
|
||||||
@@ -304,54 +415,8 @@ where
|
|||||||
if let Some(app_entry) = catalog.get_app(app_name) {
|
if let Some(app_entry) = catalog.get_app(app_name) {
|
||||||
let mut wayvr = wayvr.borrow_mut();
|
let mut wayvr = wayvr.borrow_mut();
|
||||||
|
|
||||||
let disp_handle = if let Some(disp) = wayvr.get_display_by_name(&app_entry.target_display) {
|
let (disp_handle, created_overlay) =
|
||||||
disp
|
get_or_create_display::<O>(app, &mut wayvr, &app_entry.target_display)?;
|
||||||
} else {
|
|
||||||
let conf_display = app
|
|
||||||
.session
|
|
||||||
.wayvr_config
|
|
||||||
.get_display(&app_entry.target_display)
|
|
||||||
.ok_or(anyhow::anyhow!(
|
|
||||||
"Cannot find display named \"{}\"",
|
|
||||||
app_entry.target_display
|
|
||||||
))?
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let display_handle = wayvr.create_display(
|
|
||||||
conf_display.width,
|
|
||||||
conf_display.height,
|
|
||||||
&app_entry.target_display,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut overlay = create_wayvr_display_overlay::<O>(
|
|
||||||
app,
|
|
||||||
conf_display.width,
|
|
||||||
conf_display.height,
|
|
||||||
display_handle,
|
|
||||||
conf_display.scale.unwrap_or(1.0),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if let Some(attach_to) = &conf_display.attach_to {
|
|
||||||
overlay.state.relative_to = attach_to.get_relative_to();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(rot) = &conf_display.rotation {
|
|
||||||
overlay.state.spawn_rotation = glam::Quat::from_axis_angle(
|
|
||||||
Vec3::from_slice(&rot.axis),
|
|
||||||
f32::to_radians(rot.angle),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(pos) = &conf_display.pos {
|
|
||||||
overlay.state.spawn_point = Vec3A::from_slice(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
let display = wayvr.displays.get_mut(&display_handle).unwrap(); // Never fails
|
|
||||||
display.overlay_id = Some(overlay.state.id);
|
|
||||||
|
|
||||||
created_overlay = Some(overlay);
|
|
||||||
display_handle
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse additional args
|
// Parse additional args
|
||||||
let args_vec: Vec<&str> = if let Some(args) = &app_entry.args {
|
let args_vec: Vec<&str> = if let Some(args) = &app_entry.args {
|
||||||
@@ -380,9 +445,10 @@ where
|
|||||||
// Spawn process
|
// Spawn process
|
||||||
wayvr.spawn_process(disp_handle, &app_entry.exec, &args_vec, &env_vec)?;
|
wayvr.spawn_process(disp_handle, &app_entry.exec, &args_vec, &env_vec)?;
|
||||||
}
|
}
|
||||||
|
Ok(created_overlay)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(created_overlay)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn action_display_click<O>(
|
pub fn action_display_click<O>(
|
||||||
@@ -397,7 +463,7 @@ where
|
|||||||
let wayvr = app.get_wayvr()?;
|
let wayvr = app.get_wayvr()?;
|
||||||
let mut wayvr = wayvr.borrow_mut();
|
let mut wayvr = wayvr.borrow_mut();
|
||||||
|
|
||||||
if let Some(handle) = wayvr.get_display_by_name(display_name) {
|
if let Some(handle) = WayVR::get_display_by_name(&wayvr.displays, display_name) {
|
||||||
if let Some(display) = wayvr.displays.get_mut(&handle) {
|
if let Some(display) = wayvr.displays.get_mut(&handle) {
|
||||||
if let Some(overlay_id) = display.overlay_id {
|
if let Some(overlay_id) = display.overlay_id {
|
||||||
if let Some(overlay) = overlays.mut_by_id(overlay_id) {
|
if let Some(overlay) = overlays.mut_by_id(overlay_id) {
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
|
|
||||||
version: 1
|
version: 1
|
||||||
|
|
||||||
|
# Set to true if you want to make Wyland server instantly available
|
||||||
|
# (used for remote starting external processes)
|
||||||
|
run_compositor_at_start: false
|
||||||
|
|
||||||
displays:
|
displays:
|
||||||
Watch:
|
Watch:
|
||||||
width: 400
|
width: 400
|
||||||
@@ -16,6 +20,7 @@ displays:
|
|||||||
Disp1:
|
Disp1:
|
||||||
width: 640
|
width: 640
|
||||||
height: 480
|
height: 480
|
||||||
|
primary: true # Required if you want to attach external processes (not spawned by WayVR itself) without WAYVR_DISPLAY_NAME set
|
||||||
Disp2:
|
Disp2:
|
||||||
width: 1280
|
width: 1280
|
||||||
height: 720
|
height: 720
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ impl AppState {
|
|||||||
let session = AppSession::load();
|
let session = AppSession::load();
|
||||||
|
|
||||||
#[cfg(feature = "wayvr")]
|
#[cfg(feature = "wayvr")]
|
||||||
session.wayvr_config.post_load(&mut tasks);
|
let wayvr = session.wayvr_config.post_load(&mut tasks)?;
|
||||||
|
|
||||||
Ok(AppState {
|
Ok(AppState {
|
||||||
fc: FontCache::new(session.config.primary_font.clone())?,
|
fc: FontCache::new(session.config.primary_font.clone())?,
|
||||||
@@ -116,7 +116,7 @@ impl AppState {
|
|||||||
keyboard_focus: KeyboardFocus::PhysicalScreen,
|
keyboard_focus: KeyboardFocus::PhysicalScreen,
|
||||||
|
|
||||||
#[cfg(feature = "wayvr")]
|
#[cfg(feature = "wayvr")]
|
||||||
wayvr: None,
|
wayvr,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +126,6 @@ impl AppState {
|
|||||||
if let Some(wvr) = &self.wayvr {
|
if let Some(wvr) = &self.wayvr {
|
||||||
Ok(wvr.clone())
|
Ok(wvr.clone())
|
||||||
} else {
|
} else {
|
||||||
log::info!("Initializing WayVR");
|
|
||||||
let wayvr = Rc::new(RefCell::new(WayVR::new()?));
|
let wayvr = Rc::new(RefCell::new(WayVR::new()?));
|
||||||
self.wayvr = Some(wayvr.clone());
|
self.wayvr = Some(wayvr.clone());
|
||||||
Ok(wayvr)
|
Ok(wayvr)
|
||||||
|
|||||||
Reference in New Issue
Block a user