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:
Aleksander
2024-10-27 21:26:33 +01:00
parent 66fb2fc48c
commit 7e09c010db
10 changed files with 397 additions and 123 deletions
+1 -3
View File
@@ -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");
+1 -3
View File
@@ -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
View File
@@ -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))
}
+9 -1
View File
@@ -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
View File
@@ -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(),
})))
}
}
+69 -7
View File
@@ -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));
}
}
}
}
}