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")]
|
||||
if let Some(wayvr) = &state.wayvr {
|
||||
wayvr.borrow_mut().tick_events()?;
|
||||
}
|
||||
crate::overlays::wayvr::tick_events::<OpenVrOverlayData>(&mut state, &mut overlays)?;
|
||||
|
||||
log::trace!("Rendering frame");
|
||||
|
||||
|
||||
@@ -364,9 +364,7 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(),
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
if let Some(wayvr) = &app_state.wayvr {
|
||||
wayvr.borrow_mut().tick_events()?;
|
||||
}
|
||||
crate::overlays::wayvr::tick_events::<OpenXrOverlayData>(&mut app_state, &mut overlays)?;
|
||||
|
||||
for o in overlays.iter_mut() {
|
||||
if !o.state.want_visible {
|
||||
|
||||
+64
-21
@@ -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::{
|
||||
backend::input::Keycode,
|
||||
@@ -7,9 +7,11 @@ use smithay::{
|
||||
utils::SerialCounter,
|
||||
};
|
||||
|
||||
use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask};
|
||||
|
||||
use super::{
|
||||
comp::{self},
|
||||
display, process,
|
||||
display, process, ProcessWayVREnv,
|
||||
};
|
||||
|
||||
pub struct WayVRClient {
|
||||
@@ -33,22 +35,29 @@ pub struct WayVRManager {
|
||||
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 mut env_data = String::new();
|
||||
std::fs::File::open(path)?.read_to_string(&mut env_data)?;
|
||||
|
||||
let lines: Vec<&str> = env_data.split('\0').filter(|s| !s.is_empty()).collect();
|
||||
|
||||
let mut env = ProcessWayVREnv {
|
||||
display_auth: None,
|
||||
display_name: None,
|
||||
};
|
||||
|
||||
for line in lines {
|
||||
if let Some((key, value)) = line.split_once('=') {
|
||||
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 {
|
||||
@@ -73,6 +82,10 @@ impl WayVRManager {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_client(&mut self, client: WayVRClient) {
|
||||
self.clients.push(client);
|
||||
}
|
||||
|
||||
fn accept_connection(
|
||||
&mut self,
|
||||
stream: UnixStream,
|
||||
@@ -86,27 +99,46 @@ impl WayVRManager {
|
||||
.unwrap();
|
||||
|
||||
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
|
||||
for process in processes.vec.iter().flatten() {
|
||||
let process = &process.obj;
|
||||
|
||||
// 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.clients.push(WayVRClient {
|
||||
client,
|
||||
display_handle: process.display_handle,
|
||||
pid: creds.pid as u32,
|
||||
});
|
||||
return Ok(());
|
||||
for p in processes.vec.iter().flatten() {
|
||||
if let process::Process::Managed(process) = &p.obj {
|
||||
if let Some(auth_key) = &process_env.display_auth {
|
||||
// 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(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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(
|
||||
@@ -164,6 +196,15 @@ impl WayVRManager {
|
||||
|
||||
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)>
|
||||
{
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ pub struct Display {
|
||||
pub visible: bool,
|
||||
pub overlay_id: Option<OverlayID>,
|
||||
pub wants_redraw: bool,
|
||||
pub primary: bool,
|
||||
wm: Rc<RefCell<window::WindowManager>>,
|
||||
pub displayed_windows: Vec<DisplayWindow>,
|
||||
wayland_env: super::WaylandEnv,
|
||||
@@ -81,6 +82,7 @@ impl Display {
|
||||
width: u32,
|
||||
height: u32,
|
||||
name: &str,
|
||||
primary: bool,
|
||||
) -> anyhow::Result<Self> {
|
||||
let tex_format = ffi::RGBA;
|
||||
let internal_format = ffi::RGBA8;
|
||||
@@ -116,6 +118,7 @@ impl Display {
|
||||
gles_texture,
|
||||
wayland_env,
|
||||
visible: true,
|
||||
primary,
|
||||
overlay_id: None,
|
||||
tasks: SyncEventQueue::new(),
|
||||
})
|
||||
@@ -239,7 +242,12 @@ impl Display {
|
||||
|
||||
pub fn set_visible(&mut self, visible: bool) {
|
||||
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) {
|
||||
|
||||
+86
-28
@@ -1,4 +1,4 @@
|
||||
mod client;
|
||||
pub mod client;
|
||||
mod comp;
|
||||
pub mod display;
|
||||
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)]
|
||||
pub enum WayVRTask {
|
||||
NewToplevel(ClientId, ToplevelSurface),
|
||||
NewExternalProcess(ExternalProcessRequest),
|
||||
ProcessTerminationRequest(process::ProcessHandle),
|
||||
}
|
||||
|
||||
@@ -56,7 +70,7 @@ pub struct WayVR {
|
||||
time_start: u64,
|
||||
gles_renderer: GlesRenderer,
|
||||
pub displays: display::DisplayVec,
|
||||
manager: client::WayVRManager,
|
||||
pub manager: client::WayVRManager,
|
||||
wm: Rc<RefCell<window::WindowManager>>,
|
||||
egl_data: Rc<egl_data::EGLData>,
|
||||
pub processes: process::ProcessVec,
|
||||
@@ -70,8 +84,13 @@ pub enum MouseIndex {
|
||||
Right,
|
||||
}
|
||||
|
||||
pub enum TickResult {
|
||||
NewExternalProcess(ExternalProcessRequest), // Call WayVRManager::add_client after receiving this message
|
||||
}
|
||||
|
||||
impl WayVR {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
log::info!("Initializing WayVR");
|
||||
let display: wayland_server::Display<Application> = wayland_server::Display::new()?;
|
||||
let dh = display.handle();
|
||||
let compositor = compositor::CompositorState::new::<Application>(&dh);
|
||||
@@ -140,7 +159,9 @@ impl WayVR {
|
||||
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
|
||||
self.displays.iter_mut(&mut |_, disp| {
|
||||
for disp_window in &disp.displayed_windows {
|
||||
@@ -159,17 +180,18 @@ impl WayVR {
|
||||
SmallVec::new();
|
||||
self.processes.iter_mut(&mut |handle, process| {
|
||||
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 {
|
||||
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
|
||||
.tasks
|
||||
.send(display::DisplayTask::ProcessCleanup(p_handle));
|
||||
display.wants_redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +201,9 @@ impl WayVR {
|
||||
|
||||
while let Some(task) = self.tasks.read() {
|
||||
match task {
|
||||
WayVRTask::NewExternalProcess(req) => {
|
||||
res.push(TickResult::NewExternalProcess(req));
|
||||
}
|
||||
WayVRTask::NewToplevel(client_id, toplevel) => {
|
||||
// Attach newly created toplevel surfaces to displays
|
||||
for client in &self.manager.clients {
|
||||
@@ -197,7 +222,7 @@ impl WayVR {
|
||||
}
|
||||
} else {
|
||||
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
|
||||
);
|
||||
}
|
||||
@@ -215,7 +240,9 @@ impl WayVR {
|
||||
}
|
||||
|
||||
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<()> {
|
||||
@@ -266,8 +293,22 @@ impl WayVR {
|
||||
.map(|display| display.dmabuf_data.clone())
|
||||
}
|
||||
|
||||
pub fn get_display_by_name(&self, name: &str) -> Option<display::DisplayHandle> {
|
||||
for (idx, cell) in self.displays.vec.iter().enumerate() {
|
||||
pub fn get_primary_display(displays: &DisplayVec) -> Option<display::DisplayHandle> {
|
||||
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 cell.obj.name == name {
|
||||
return Some(DisplayVec::get_handle(cell, idx));
|
||||
@@ -276,11 +317,13 @@ impl WayVR {
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn create_display(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
name: &str,
|
||||
primary: bool,
|
||||
) -> anyhow::Result<display::DisplayHandle> {
|
||||
let display = display::Display::new(
|
||||
self.wm.clone(),
|
||||
@@ -290,6 +333,7 @@ impl WayVR {
|
||||
width,
|
||||
height,
|
||||
name,
|
||||
primary,
|
||||
)?;
|
||||
Ok(self.displays.add(display))
|
||||
}
|
||||
@@ -308,15 +352,15 @@ impl WayVR {
|
||||
) -> Option<process::ProcessHandle> {
|
||||
for (idx, cell) in self.processes.vec.iter().enumerate() {
|
||||
if let Some(cell) = &cell {
|
||||
let process = &cell.obj;
|
||||
if process.display_handle != display_handle
|
||||
|| process.exec_path != exec_path
|
||||
|| process.args != args
|
||||
{
|
||||
continue;
|
||||
if let process::Process::Managed(process) = &cell.obj {
|
||||
if process.display_handle != display_handle
|
||||
|| process.exec_path != exec_path
|
||||
|| process.args != args
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
display_handle: display::DisplayHandle,
|
||||
@@ -341,16 +397,18 @@ impl WayVR {
|
||||
.ok_or(anyhow::anyhow!(STR_INVALID_HANDLE_DISP))?;
|
||||
|
||||
let res = display.spawn_process(exec_path, args, env)?;
|
||||
Ok(self.processes.add(process::Process {
|
||||
auth_key: res.auth_key,
|
||||
child: res.child,
|
||||
display_handle,
|
||||
exec_path: String::from(exec_path),
|
||||
args: args.iter().map(|x| String::from(*x)).collect(),
|
||||
env: env
|
||||
.iter()
|
||||
.map(|(a, b)| (String::from(*a), String::from(*b)))
|
||||
.collect(),
|
||||
}))
|
||||
Ok(self
|
||||
.processes
|
||||
.add(process::Process::Managed(process::WayVRProcess {
|
||||
auth_key: res.auth_key,
|
||||
child: res.child,
|
||||
display_handle,
|
||||
exec_path: String::from(exec_path),
|
||||
args: args.iter().map(|x| String::from(*x)).collect(),
|
||||
env: env
|
||||
.iter()
|
||||
.map(|(a, b)| (String::from(*a), String::from(*b)))
|
||||
.collect(),
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::gen_id;
|
||||
|
||||
use super::display;
|
||||
|
||||
pub struct Process {
|
||||
pub struct WayVRProcess {
|
||||
pub auth_key: String,
|
||||
pub child: std::process::Child,
|
||||
pub display_handle: display::DisplayHandle,
|
||||
@@ -12,7 +12,40 @@ pub struct Process {
|
||||
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) {
|
||||
log::info!(
|
||||
"Sending SIGTERM (graceful exit) to process {}",
|
||||
@@ -22,8 +55,8 @@ impl Drop for Process {
|
||||
}
|
||||
}
|
||||
|
||||
impl Process {
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
impl WayVRProcess {
|
||||
fn is_running(&mut self) -> bool {
|
||||
match self.child.try_wait() {
|
||||
Ok(Some(_exit_status)) => false,
|
||||
Ok(None) => true,
|
||||
@@ -35,7 +68,7 @@ impl Process {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn terminate(&mut self) {
|
||||
fn terminate(&mut self) {
|
||||
unsafe {
|
||||
// Gracefully stop process
|
||||
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);
|
||||
|
||||
pub fn find_by_pid(processes: &ProcessVec, pid: u32) -> Option<ProcessHandle> {
|
||||
for (idx, cell) in processes.vec.iter().enumerate() {
|
||||
if let Some(cell) = cell {
|
||||
if cell.obj.child.id() == pid {
|
||||
return Some(ProcessVec::get_handle(cell, idx));
|
||||
match &cell.obj {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user