diff --git a/Cargo.lock b/Cargo.lock index a81d0a6..2d9e70f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ab_glyph" @@ -432,6 +432,15 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -707,9 +716,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "calloop" @@ -903,6 +912,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1099,6 +1114,12 @@ dependencies = [ "libc", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -1362,6 +1383,12 @@ dependencies = [ "const-random", ] +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + [[package]] name = "downcast-rs" version = "1.2.1" @@ -1805,6 +1832,15 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1836,6 +1872,20 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -2139,6 +2189,19 @@ dependencies = [ "nix 0.29.0", ] +[[package]] +name = "interprocess" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "894148491d817cb36b6f778017b8ac46b17408d522dd90f539d677ea938362eb" +dependencies = [ + "doctest-file", + "libc", + "recvmsg", + "widestring", + "windows-sys 0.52.0", +] + [[package]] name = "io-lifetimes" version = "2.0.3" @@ -3221,6 +3284,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "postcard" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" +dependencies = [ + "cobs", + "heapless", + "serde", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -3393,6 +3467,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + [[package]] name = "redox_syscall" version = "0.4.1" @@ -3808,6 +3888,15 @@ dependencies = [ "serde", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -4512,6 +4601,19 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "wayvr_ipc" +version = "0.1.0" +source = "git+https://github.com/olekolek1000/wayvr-ipc.git?rev=c2a6438ffdcc78ff9c0637d914df1bc673723824#c2a6438ffdcc78ff9c0637d914df1bc673723824" +dependencies = [ + "anyhow", + "bytes", + "log", + "postcard", + "serde", + "smallvec", +] + [[package]] name = "web-sys" version = "0.3.72" @@ -4544,6 +4646,12 @@ dependencies = [ "rustix", ] +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + [[package]] name = "winapi" version = "0.3.9" @@ -4957,6 +5065,7 @@ version = "0.6.0" dependencies = [ "anyhow", "ash", + "bytes", "chrono", "chrono-tz", "clap", @@ -4973,6 +5082,7 @@ dependencies = [ "idmap-derive", "image_dds", "input-linux", + "interprocess", "json", "json5", "khronos-egl", @@ -4984,6 +5094,7 @@ dependencies = [ "once_cell", "openxr", "ovr_overlay", + "postcard", "regex", "rodio", "rosc", @@ -5001,6 +5112,7 @@ dependencies = [ "vulkano-shaders", "wayland-client", "wayland-egl", + "wayvr_ipc", "winit", "wlx-capture", "xcb", diff --git a/Cargo.toml b/Cargo.toml index 4c1018c..05be73f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,9 @@ image_dds = { version = "0.6.0", default-features = false, features = [ ] } mint = "0.5.9" -# WayVR-only deps +################################ +#WayVR-only deps +################################ khronos-egl = { version = "6.0.0", features = ["static"], optional = true } smithay = { git = "https://github.com/Smithay/smithay.git", default-features = false, features = [ "renderer_gl", @@ -81,12 +83,17 @@ smithay = { git = "https://github.com/Smithay/smithay.git", default-features = f uuid = { version = "1.10.0", features = ["v4", "fast-rng"], optional = true } wayland-client = { version = "0.31.6", optional = true } wayland-egl = { version = "0.32.4", optional = true } +interprocess = { version = "2.2.2", optional = true } +postcard = { version = "1.1.1", optional = true } +bytes = { version = "1.9.0", optional = true } +wayvr_ipc = { git = "https://github.com/olekolek1000/wayvr-ipc.git", rev = "c2a6438ffdcc78ff9c0637d914df1bc673723824", default-features = false, optional = true } +################################ [build-dependencies] regex = { version = "*" } [features] -default = ["openxr", "openvr", "osc", "x11", "wayland", "wayvr"] +default = ["openvr", "openxr", "osc", "x11", "wayland", "wayvr"] openvr = ["dep:ovr_overlay", "dep:json"] openxr = ["dep:openxr", "dep:libmonado-rs"] osc = ["dep:rosc"] @@ -101,5 +108,9 @@ wayvr = [ "dep:uuid", "dep:wayland-client", "dep:wayland-egl", + "dep:interprocess", + "dep:postcard", + "dep:bytes", + "dep:wayvr_ipc", ] as-raw-xcb-connection = [] diff --git a/src/backend/input.rs b/src/backend/input.rs index b7eedd0..7d05352 100644 --- a/src/backend/input.rs +++ b/src/backend/input.rs @@ -71,6 +71,13 @@ impl InputState { if hand.now.show_hide != hand.before.show_hide { log::debug!("Hand {}: show_hide {}", hand.idx, hand.now.show_hide); } + if hand.now.toggle_dashboard != hand.before.toggle_dashboard { + log::debug!( + "Hand {}: toggle_dashboard {}", + hand.idx, + hand.now.toggle_dashboard + ); + } if hand.now.space_drag != hand.before.space_drag { log::debug!("Hand {}: space_drag {}", hand.idx, hand.now.space_drag); } @@ -215,6 +222,7 @@ pub struct PointerState { pub grab: bool, pub alt_click: bool, pub show_hide: bool, + pub toggle_dashboard: bool, pub space_drag: bool, pub space_rotate: bool, pub space_reset: bool, diff --git a/src/backend/openvr/input.rs b/src/backend/openvr/input.rs index 329d633..9b549d8 100644 --- a/src/backend/openvr/input.rs +++ b/src/backend/openvr/input.rs @@ -30,16 +30,17 @@ const PATH_HAPTICS: [&str; 2] = [ "/actions/default/out/HapticsRight", ]; +const PATH_ALT_CLICK: &str = "/actions/default/in/AltClick"; +const PATH_CLICK_MODIFIER_MIDDLE: &str = "/actions/default/in/ClickModifierMiddle"; +const PATH_CLICK_MODIFIER_RIGHT: &str = "/actions/default/in/ClickModifierRight"; const PATH_CLICK: &str = "/actions/default/in/Click"; const PATH_GRAB: &str = "/actions/default/in/Grab"; +const PATH_MOVE_MOUSE: &str = "/actions/default/in/MoveMouse"; const PATH_SCROLL: &str = "/actions/default/in/Scroll"; -const PATH_ALT_CLICK: &str = "/actions/default/in/AltClick"; const PATH_SHOW_HIDE: &str = "/actions/default/in/ShowHide"; const PATH_SPACE_DRAG: &str = "/actions/default/in/SpaceDrag"; const PATH_SPACE_ROTATE: &str = "/actions/default/in/SpaceRotate"; -const PATH_CLICK_MODIFIER_RIGHT: &str = "/actions/default/in/ClickModifierRight"; -const PATH_CLICK_MODIFIER_MIDDLE: &str = "/actions/default/in/ClickModifierMiddle"; -const PATH_MOVE_MOUSE: &str = "/actions/default/in/MoveMouse"; +const PATH_TOGGLE_DASHBOARD: &str = "/actions/default/in/ToggleDashboard"; const INPUT_ANY: InputValueHandle = InputValueHandle(ovr_overlay::sys::k_ulInvalidInputValueHandle); @@ -51,6 +52,7 @@ pub(super) struct OpenVrInputSource { scroll_hnd: ActionHandle, alt_click_hnd: ActionHandle, show_hide_hnd: ActionHandle, + toggle_dashboard_hnd: ActionHandle, space_drag_hnd: ActionHandle, space_rotate_hnd: ActionHandle, click_modifier_right_hnd: ActionHandle, @@ -75,6 +77,7 @@ impl OpenVrInputSource { let scroll_hnd = input.get_action_handle(PATH_SCROLL)?; let alt_click_hnd = input.get_action_handle(PATH_ALT_CLICK)?; let show_hide_hnd = input.get_action_handle(PATH_SHOW_HIDE)?; + let toggle_dashboard_hnd = input.get_action_handle(PATH_TOGGLE_DASHBOARD)?; let space_drag_hnd = input.get_action_handle(PATH_SPACE_DRAG)?; let space_rotate_hnd = input.get_action_handle(PATH_SPACE_ROTATE)?; let click_modifier_right_hnd = input.get_action_handle(PATH_CLICK_MODIFIER_RIGHT)?; @@ -111,6 +114,7 @@ impl OpenVrInputSource { scroll_hnd, alt_click_hnd, show_hide_hnd, + toggle_dashboard_hnd, space_drag_hnd, space_rotate_hnd, click_modifier_right_hnd, @@ -196,6 +200,11 @@ impl OpenVrInputSource { .map(|x| x.0.bState) .unwrap_or(false); + app_hand.now.toggle_dashboard = input + .get_digital_action_data(self.toggle_dashboard_hnd, hand.input_hnd) + .map(|x| x.0.bState) + .unwrap_or(false); + app_hand.now.space_drag = input .get_digital_action_data(self.space_drag_hnd, hand.input_hnd) .map(|x| x.0.bState) diff --git a/src/backend/openvr/lines.rs b/src/backend/openvr/lines.rs index beea424..9b0b406 100644 --- a/src/backend/openvr/lines.rs +++ b/src/backend/openvr/lines.rs @@ -12,7 +12,7 @@ use vulkano::image::view::ImageView; use vulkano::image::ImageLayout; use crate::backend::overlay::{ - FrameTransform, OverlayData, OverlayRenderer, OverlayState, SplitOverlayBackend, + FrameTransform, OverlayData, OverlayRenderer, OverlayState, SplitOverlayBackend, Z_ORDER_LINES, }; use crate::graphics::WlxGraphics; use crate::state::AppState; @@ -82,7 +82,7 @@ impl LinePool { }, ..Default::default() }; - data.state.z_order = 69; + data.state.z_order = Z_ORDER_LINES; data.state.dirty = true; self.lines.insert(id, data); diff --git a/src/backend/openvr/mod.rs b/src/backend/openvr/mod.rs index 9d85ec4..21c57f0 100644 --- a/src/backend/openvr/mod.rs +++ b/src/backend/openvr/mod.rs @@ -43,7 +43,7 @@ use crate::{ }; #[cfg(feature = "wayvr")] -use crate::overlays::wayvr::wayvr_action; +use crate::overlays::wayvr::{wayvr_action, WayVRAction}; pub mod helpers; pub mod input; @@ -293,6 +293,16 @@ pub fn openvr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(), overlays.show_hide(&mut state); } + #[cfg(feature = "wayvr")] + if state + .input_state + .pointers + .iter() + .any(|p| p.now.toggle_dashboard && !p.before.toggle_dashboard) + { + wayvr_action(&mut state, &mut overlays, &WayVRAction::ToggleDashboard); + } + overlays .iter_mut() .for_each(|o| o.state.auto_movement(&mut state)); @@ -346,7 +356,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().state.tick_finish()?; + wayvr.borrow_mut().data.tick_finish()?; } // chaperone diff --git a/src/backend/openxr/input.rs b/src/backend/openxr/input.rs index 3b97803..67b101f 100644 --- a/src/backend/openxr/input.rs +++ b/src/backend/openxr/input.rs @@ -154,6 +154,7 @@ pub(super) struct OpenXrHandSource { action_grab: CustomClickAction, action_alt_click: CustomClickAction, action_show_hide: CustomClickAction, + action_toggle_dashboard: CustomClickAction, action_space_drag: CustomClickAction, action_space_rotate: CustomClickAction, action_space_reset: CustomClickAction, @@ -365,6 +366,12 @@ impl OpenXrHand { session, )?; + pointer.now.toggle_dashboard = self.source.action_toggle_dashboard.state( + pointer.before.toggle_dashboard, + xr, + session, + )?; + pointer.now.click_modifier_middle = self.source.action_modifier_middle.state( pointer.before.click_modifier_middle, xr, @@ -422,6 +429,7 @@ impl OpenXrHandSource { action_scroll, action_alt_click: CustomClickAction::new(action_set, "alt_click", side)?, action_show_hide: CustomClickAction::new(action_set, "show_hide", side)?, + action_toggle_dashboard: CustomClickAction::new(action_set, "toggle_dashboard", side)?, action_space_drag: CustomClickAction::new(action_set, "space_drag", side)?, action_space_rotate: CustomClickAction::new(action_set, "space_rotate", side)?, action_space_reset: CustomClickAction::new(action_set, "space_reset", side)?, @@ -578,6 +586,14 @@ fn suggest_bindings(instance: &xr::Instance, hands: &[&OpenXrHandSource; 2]) -> instance ); + add_custom!( + profile.toggle_dashboard, + &hands[0].action_toggle_dashboard, + &hands[1].action_toggle_dashboard, + bindings, + instance + ); + add_custom!( profile.space_drag, &hands[0].action_space_drag, @@ -655,6 +671,7 @@ struct OpenXrActionConfProfile { grab: Option<OpenXrActionConfAction>, alt_click: Option<OpenXrActionConfAction>, show_hide: Option<OpenXrActionConfAction>, + toggle_dashboard: Option<OpenXrActionConfAction>, space_drag: Option<OpenXrActionConfAction>, space_rotate: Option<OpenXrActionConfAction>, space_reset: Option<OpenXrActionConfAction>, diff --git a/src/backend/openxr/mod.rs b/src/backend/openxr/mod.rs index b53195d..431010c 100644 --- a/src/backend/openxr/mod.rs +++ b/src/backend/openxr/mod.rs @@ -32,7 +32,7 @@ use crate::{ }; #[cfg(feature = "wayvr")] -use crate::overlays::wayvr::wayvr_action; +use crate::overlays::wayvr::{wayvr_action, WayVRAction}; mod helpers; mod input; @@ -291,6 +291,16 @@ pub fn openxr_run(running: Arc<AtomicBool>, show_by_default: bool) -> Result<(), overlays.show_hide(&mut app_state); } + #[cfg(feature = "wayvr")] + if app_state + .input_state + .pointers + .iter() + .any(|p| p.now.toggle_dashboard && !p.before.toggle_dashboard) + { + wayvr_action(&mut app_state, &mut overlays, &WayVRAction::ToggleDashboard); + } + watch_fade(&mut app_state, overlays.mut_by_id(watch_id).unwrap()); // want panic if let Some(ref mut space_mover) = playspace { space_mover.update( @@ -414,7 +424,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().state.tick_finish()?; + wayvr.borrow_mut().data.tick_finish()?; } command_buffer.build_and_execute_now()?; diff --git a/src/backend/openxr/openxr_actions.json5 b/src/backend/openxr/openxr_actions.json5 index bbbaccf..432b98b 100644 --- a/src/backend/openxr/openxr_actions.json5 +++ b/src/backend/openxr/openxr_actions.json5 @@ -12,6 +12,9 @@ // -- space_drag -- // move your stage (playspace drag) // +// -- toggle_dashboard -- +// run or toggle visibility of a previously configured WayVR-compatible dashboard +// // -- space_rotate -- // rotate your stage (playspace rotate, WIP) // @@ -127,6 +130,10 @@ left: "/user/hand/left/input/thumbstick/y", right: "/user/hand/right/input/thumbstick/y" }, + toggle_dashboard: { + double_click: false, + right: "/user/hand/right/input/system/click", + }, show_hide: { double_click: true, left: "/user/hand/left/input/b/click", diff --git a/src/backend/overlay.rs b/src/backend/overlay.rs index 2154ede..2f9b356 100644 --- a/src/backend/overlay.rs +++ b/src/backend/overlay.rs @@ -28,6 +28,12 @@ pub trait OverlayBackend: OverlayRenderer + InteractionHandler { #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Default)] pub struct OverlayID(pub usize); +pub const Z_ORDER_TOAST: u32 = 70; +pub const Z_ORDER_LINES: u32 = 69; +pub const Z_ORDER_WATCH: u32 = 68; +pub const Z_ORDER_ANCHOR: u32 = 67; +pub const Z_ORDER_DASHBOARD: u32 = 66; + pub struct OverlayState { pub id: OverlayID, pub name: Arc<str>, diff --git a/src/backend/wayvr/client.rs b/src/backend/wayvr/client.rs index 3493e99..d060379 100644 --- a/src/backend/wayvr/client.rs +++ b/src/backend/wayvr/client.rs @@ -20,7 +20,7 @@ pub struct WayVRClient { pub pid: u32, } -pub struct WayVRManager { +pub struct WayVRCompositor { pub state: comp::Application, pub seat_keyboard: KeyboardHandle<comp::Application>, pub seat_pointer: PointerHandle<comp::Application>, @@ -60,7 +60,7 @@ fn get_wayvr_env_from_pid(pid: i32) -> anyhow::Result<ProcessWayVREnv> { Ok(env) } -impl WayVRManager { +impl WayVRCompositor { pub fn new( state: comp::Application, display: wayland_server::Display<comp::Application>, diff --git a/src/backend/wayvr/display.rs b/src/backend/wayvr/display.rs index 18efe9b..063eac1 100644 --- a/src/backend/wayvr/display.rs +++ b/src/backend/wayvr/display.rs @@ -14,6 +14,7 @@ use smithay::{ utils::{Logical, Point, Rectangle, Size, Transform}, wayland::shell::xdg::ToplevelSurface, }; +use wayvr_ipc::packet_server; use crate::{ backend::{overlay::OverlayID, wayvr::time::get_millis}, @@ -21,7 +22,7 @@ use crate::{ }; use super::{ - client::WayVRManager, comp::send_frames_surface_tree, egl_data, event_queue::SyncEventQueue, + client::WayVRCompositor, comp::send_frames_surface_tree, egl_data, event_queue::SyncEventQueue, process, smithay_wrapper, time, window, WayVRSignal, }; @@ -45,10 +46,12 @@ pub enum DisplayTask { ProcessCleanup(process::ProcessHandle), } +const MAX_DISPLAY_SIZE: u16 = 8192; + pub struct Display { // Display info stuff - pub width: u32, - pub height: u32, + pub width: u16, + pub height: u16, pub name: String, pub visible: bool, pub overlay_id: Option<OverlayID>, @@ -84,25 +87,41 @@ impl Display { renderer: &mut GlesRenderer, egl_data: Rc<egl_data::EGLData>, wayland_env: super::WaylandEnv, - width: u32, - height: u32, + width: u16, + height: u16, name: &str, primary: bool, ) -> anyhow::Result<Self> { + if width > MAX_DISPLAY_SIZE { + anyhow::bail!( + "display width ({}) is larger than {}", + width, + MAX_DISPLAY_SIZE + ); + } + + if height > MAX_DISPLAY_SIZE { + anyhow::bail!( + "display height ({}) is larger than {}", + height, + MAX_DISPLAY_SIZE + ); + } + let tex_format = ffi::RGBA; let internal_format = ffi::RGBA8; let tex_id = renderer.with_context(|gl| { smithay_wrapper::create_framebuffer_texture( gl, - width, - height, + width as u32, + height as u32, tex_format, internal_format, ) })?; - let egl_image = egl_data.create_egl_image(tex_id, width, height)?; + let egl_image = egl_data.create_egl_image(tex_id, width as u32, height as u32)?; let dmabuf_data = egl_data.create_dmabuf_data(&egl_image)?; let opaque = false; @@ -131,6 +150,16 @@ impl Display { }) } + 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, @@ -158,7 +187,7 @@ impl Display { let right = (d_next * self.width as f32) as i32; window.set_pos(left, 0); - window.set_size((right - left) as u32, self.height); + window.set_size((right - left) as u32, self.height as u32); } } } @@ -279,7 +308,7 @@ impl Display { pub fn send_mouse_move( &self, config: &super::Config, - manager: &mut WayVRManager, + manager: &mut WayVRCompositor, x: u32, y: u32, ) { @@ -320,7 +349,7 @@ impl Display { } } - pub fn send_mouse_down(&mut self, manager: &mut WayVRManager, index: super::MouseIndex) { + 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(); @@ -356,7 +385,7 @@ impl Display { manager.seat_pointer.frame(&mut manager.state); } - pub fn send_mouse_up(&self, manager: &mut WayVRManager, index: super::MouseIndex) { + pub fn send_mouse_up(&self, manager: &mut WayVRCompositor, index: super::MouseIndex) { manager.seat_pointer.button( &mut manager.state, &input::pointer::ButtonEvent { @@ -370,7 +399,7 @@ impl Display { manager.seat_pointer.frame(&mut manager.state); } - pub fn send_mouse_scroll(&self, manager: &mut WayVRManager, delta: f32) { + pub fn send_mouse_scroll(&self, manager: &mut WayVRCompositor, delta: f32) { manager.seat_pointer.axis( &mut manager.state, input::pointer::AxisFrame { @@ -426,3 +455,19 @@ impl Display { } gen_id!(DisplayVec, Display, DisplayCell, DisplayHandle); + +impl DisplayHandle { + pub fn from_packet(handle: packet_server::WvrDisplayHandle) -> Self { + Self { + generation: handle.generation, + idx: handle.idx, + } + } + + pub fn as_packet(&self) -> packet_server::WvrDisplayHandle { + packet_server::WvrDisplayHandle { + idx: self.idx, + generation: self.generation, + } + } +} diff --git a/src/backend/wayvr/handle.rs b/src/backend/wayvr/handle.rs index 2947e3e..a408d41 100644 --- a/src/backend/wayvr/handle.rs +++ b/src/backend/wayvr/handle.rs @@ -8,7 +8,7 @@ macro_rules! gen_id { //ThingCell pub struct $cell_name { pub obj: $instance_name, - generation: u64, + pub generation: u64, } //ThingVec @@ -39,6 +39,10 @@ macro_rules! gen_id { pub fn id(&self) -> u32 { self.idx } + + pub fn new(idx: u32, generation: u64) -> Self { + Self { idx, generation } + } } //ThingVec diff --git a/src/backend/wayvr/mod.rs b/src/backend/wayvr/mod.rs index 1cb0134..bcbd5fc 100644 --- a/src/backend/wayvr/mod.rs +++ b/src/backend/wayvr/mod.rs @@ -3,19 +3,18 @@ mod comp; pub mod display; pub mod egl_data; mod egl_ex; -mod event_queue; +pub mod event_queue; mod handle; mod process; +pub mod server_ipc; mod smithay_wrapper; mod time; mod window; - -use std::{cell::RefCell, collections::HashSet, rc::Rc}; - use comp::Application; use display::DisplayVec; use event_queue::SyncEventQueue; use process::ProcessVec; +use server_ipc::WayVRServer; use smallvec::SmallVec; use smithay::{ backend::renderer::gles::GlesRenderer, @@ -29,7 +28,9 @@ use smithay::{ shm::ShmState, }, }; +use std::{cell::RefCell, collections::HashSet, rc::Rc}; use time::get_millis; +use wayvr_ipc::packet_client; const STR_INVALID_HANDLE_DISP: &str = "Invalid display handle"; const STR_INVALID_HANDLE_PROCESS: &str = "Invalid process handle"; @@ -78,17 +79,21 @@ pub struct Config { pub auto_hide_delay: Option<u32>, // if None, auto-hide is disabled } -#[allow(dead_code)] -pub struct WayVR { +pub struct WayVRState { time_start: u64, gles_renderer: GlesRenderer, pub displays: display::DisplayVec, - pub manager: client::WayVRManager, + pub manager: client::WayVRCompositor, wm: Rc<RefCell<window::WindowManager>>, egl_data: Rc<egl_data::EGLData>, pub processes: process::ProcessVec, config: Config, + dashboard_display: Option<display::DisplayHandle>, +} +pub struct WayVR { + pub state: WayVRState, + ipc_server: WayVRServer, tasks: SyncEventQueue<WayVRTask>, pub signals: SyncEventQueue<WayVRSignal>, } @@ -99,8 +104,9 @@ pub enum MouseIndex { Right, } -pub enum TickResult { - NewExternalProcess(ExternalProcessRequest), // Call WayVRManager::add_client after receiving this message +pub enum TickTask { + NewExternalProcess(ExternalProcessRequest), // Call WayVRCompositor::add_client after receiving this message + NewDisplay(packet_client::WvrDisplayCreateParams), } impl WayVR { @@ -163,23 +169,32 @@ impl WayVR { let smithay_context = smithay_wrapper::get_egl_context(&egl_data, &smithay_display)?; let gles_renderer = unsafe { GlesRenderer::new(smithay_context)? }; - Ok(Self { + let ipc_server = WayVRServer::new()?; + + let state = WayVRState { gles_renderer, time_start, - manager: client::WayVRManager::new(state, display, seat_keyboard, seat_pointer)?, + 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, + }; + + Ok(Self { + state, signals: SyncEventQueue::new(), tasks, - config, + ipc_server, }) } pub fn tick_display(&mut self, display: display::DisplayHandle) -> anyhow::Result<()> { // millis since the start of wayvr let display = self + .state .displays .get_mut(&display) .ok_or(anyhow::anyhow!(STR_INVALID_HANDLE_DISP))?; @@ -194,21 +209,27 @@ impl WayVR { return Ok(()); } - let time_ms = get_millis() - self.time_start; + let time_ms = get_millis() - self.state.time_start; - display.tick_render(&mut self.gles_renderer, time_ms)?; + display.tick_render(&mut self.state.gles_renderer, time_ms)?; display.wants_redraw = false; Ok(()) } - pub fn tick_events(&mut self) -> anyhow::Result<Vec<TickResult>> { - let mut res: Vec<TickResult> = Vec::new(); + pub fn tick_events(&mut self) -> anyhow::Result<Vec<TickTask>> { + let mut tasks: Vec<TickTask> = Vec::new(); + + self.ipc_server.tick(&mut server_ipc::TickParams { + state: &mut self.state, + tasks: &mut tasks, + })?; // Check for redraw events - self.displays.iter_mut(&mut |_, disp| { + self.state.displays.iter_mut(&mut |_, disp| { for disp_window in &disp.displayed_windows { if self + .state .manager .state .check_redraw(disp_window.toplevel.wl_surface()) @@ -221,16 +242,17 @@ impl WayVR { // Tick all child processes let mut to_remove: SmallVec<[(process::ProcessHandle, display::DisplayHandle); 2]> = SmallVec::new(); - self.processes.iter_mut(&mut |handle, process| { + + self.state.processes.iter_mut(&mut |handle, process| { if !process.is_running() { to_remove.push((handle, process.display_handle())); } }); for (p_handle, disp_handle) in to_remove { - self.processes.remove(&p_handle); + self.state.processes.remove(&p_handle); - if let Some(display) = self.displays.get_mut(&disp_handle) { + if let Some(display) = self.state.displays.get_mut(&disp_handle) { display .tasks .send(display::DisplayTask::ProcessCleanup(p_handle)); @@ -238,25 +260,26 @@ impl WayVR { } } - self.displays.iter_mut(&mut |handle, display| { - display.tick(&self.config, &handle, &mut self.signals); + self.state.displays.iter_mut(&mut |handle, display| { + display.tick(&self.state.config, &handle, &mut self.signals); }); while let Some(task) = self.tasks.read() { match task { WayVRTask::NewExternalProcess(req) => { - res.push(TickResult::NewExternalProcess(req)); + tasks.push(TickTask::NewExternalProcess(req)); } WayVRTask::NewToplevel(client_id, toplevel) => { // Attach newly created toplevel surfaces to displays - for client in &self.manager.clients { + for client in &self.state.manager.clients { if client.client.id() == client_id { - let window_handle = self.wm.borrow_mut().create_window(&toplevel); + let window_handle = self.state.wm.borrow_mut().create_window(&toplevel); if let Some(process_handle) = - process::find_by_pid(&self.processes, client.pid) + process::find_by_pid(&self.state.processes, client.pid) { - if let Some(display) = self.displays.get_mut(&client.display_handle) + if let Some(display) = + self.state.displays.get_mut(&client.display_handle) { display.add_window(window_handle, process_handle, &toplevel); } else { @@ -275,27 +298,60 @@ impl WayVR { } } WayVRTask::ProcessTerminationRequest(process_handle) => { - if let Some(process) = self.processes.get_mut(&process_handle) { + if let Some(process) = self.state.processes.get_mut(&process_handle) { process.terminate(); } } } } - self.manager - .tick_wayland(&mut self.displays, &mut self.processes)?; + self.state + .manager + .tick_wayland(&mut self.state.displays, &mut self.state.processes)?; - Ok(res) + Ok(tasks) } pub fn tick_finish(&mut self) -> anyhow::Result<()> { - self.gles_renderer.with_context(|gl| unsafe { + self.state.gles_renderer.with_context(|gl| unsafe { gl.Flush(); gl.Finish(); })?; Ok(()) } + 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)); + } + } + } + None + } + + pub fn terminate_process(&mut self, process_handle: process::ProcessHandle) { + self.tasks + .send(WayVRTask::ProcessTerminationRequest(process_handle)); + } +} + +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); @@ -336,35 +392,10 @@ impl WayVR { .map(|display| display.dmabuf_data.clone()) } - 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)); - } - } - } - None - } - pub fn create_display( &mut self, - width: u32, - height: u32, + width: u16, + height: u16, name: &str, primary: bool, ) -> anyhow::Result<display::DisplayHandle> { @@ -381,6 +412,25 @@ impl WayVR { Ok(self.displays.add(display)) } + 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)) + } + pub fn destroy_display(&mut self, handle: display::DisplayHandle) { self.displays.remove(&handle); } @@ -410,11 +460,6 @@ impl WayVR { None } - pub fn terminate_process(&mut self, process_handle: process::ProcessHandle) { - self.tasks - .send(WayVRTask::ProcessTerminationRequest(process_handle)); - } - pub fn add_external_process( &mut self, display_handle: display::DisplayHandle, diff --git a/src/backend/wayvr/process.rs b/src/backend/wayvr/process.rs index c6c08da..395c5d1 100644 --- a/src/backend/wayvr/process.rs +++ b/src/backend/wayvr/process.rs @@ -1,3 +1,5 @@ +use wayvr_ipc::packet_server; + use crate::gen_id; use super::display; @@ -43,6 +45,21 @@ impl Process { Process::External(p) => p.terminate(), } } + + pub fn to_packet(&self, handle: ProcessHandle) -> packet_server::WvrProcess { + match self { + Process::Managed(p) => packet_server::WvrProcess { + name: p.get_name().unwrap_or(String::from("unknown")), + display_handle: p.display_handle.as_packet(), + handle: handle.as_packet(), + }, + Process::External(p) => packet_server::WvrProcess { + name: p.get_name().unwrap_or(String::from("unknown")), + display_handle: p.display_handle.as_packet(), + handle: handle.as_packet(), + }, + } + } } impl Drop for WayVRProcess { @@ -74,6 +91,23 @@ impl WayVRProcess { libc::kill(self.child.id() as i32, libc::SIGTERM); } } + + pub fn get_name(&self) -> Option<String> { + get_exec_name_from_pid(self.child.id()) + } +} + +fn get_exec_name_from_pid(pid: u32) -> Option<String> { + let path = format!("/proc/{}/exe", pid); + match std::fs::read_link(&path) { + Ok(buf) => { + if let Some(process_name) = buf.file_name().and_then(|s| s.to_str()) { + return Some(String::from(process_name)); + } + None + } + Err(_) => None, + } } impl ExternalProcess { @@ -94,6 +128,10 @@ impl ExternalProcess { } self.pid = 0; } + + pub fn get_name(&self) -> Option<String> { + get_exec_name_from_pid(self.pid) + } } gen_id!(ProcessVec, Process, ProcessCell, ProcessHandle); @@ -117,3 +155,19 @@ pub fn find_by_pid(processes: &ProcessVec, pid: u32) -> Option<ProcessHandle> { } None } + +impl ProcessHandle { + pub fn from_packet(handle: packet_server::WvrProcessHandle) -> Self { + Self { + generation: handle.generation, + idx: handle.idx, + } + } + + pub fn as_packet(&self) -> packet_server::WvrProcessHandle { + packet_server::WvrProcessHandle { + idx: self.idx, + generation: self.generation, + } + } +} diff --git a/src/backend/wayvr/server_ipc.rs b/src/backend/wayvr/server_ipc.rs new file mode 100644 index 0000000..418f791 --- /dev/null +++ b/src/backend/wayvr/server_ipc.rs @@ -0,0 +1,418 @@ +use super::{display, process, TickTask}; +use bytes::BufMut; +use interprocess::local_socket::{self, traits::Listener, ToNsName}; +use smallvec::SmallVec; +use std::io::{Read, Write}; +use wayvr_ipc::{ + ipc::{self, binary_decode, binary_encode}, + packet_client::{self, PacketClient}, + packet_server::{self, PacketServer}, +}; + +pub struct Connection { + alive: bool, + conn: local_socket::Stream, + next_packet: Option<u32>, + handshaking: bool, +} + +pub fn send_packet(conn: &mut local_socket::Stream, data: &[u8]) -> anyhow::Result<()> { + let mut bytes = bytes::BytesMut::new(); + + // packet size + bytes.put_u32(data.len() as u32); + + // packet data + bytes.put_slice(data); + + conn.write_all(&bytes)?; + + Ok(()) +} + +fn read_check(expected_size: u32, res: std::io::Result<usize>) -> bool { + match res { + Ok(count) => { + if count == 0 { + return false; + } + if count as u32 != expected_size { + log::error!("count {} is not {}", count, expected_size); + false + } else { + true // read succeeded + } + } + Err(_e) => { + //log::error!("failed to get packet size: {}", e); + false + } + } +} + +type Payload = SmallVec<[u8; 64]>; + +fn read_payload(conn: &mut local_socket::Stream, size: u32) -> Option<Payload> { + let mut payload = Payload::new(); + payload.resize(size as usize, 0); + if !read_check(size, conn.read(&mut payload)) { + None + } else { + Some(payload) + } +} + +pub struct TickParams<'a> { + pub state: &'a mut super::WayVRState, + pub tasks: &'a mut Vec<TickTask>, +} + +pub fn gen_args_vec(input: &str) -> Vec<&str> { + input.split_whitespace().collect() +} + +pub fn gen_env_vec(input: &Vec<String>) -> Vec<(&str, &str)> { + let res = input + .iter() + .filter_map(|e| e.as_str().split_once('=')) + .collect(); + res +} + +impl Connection { + fn new(conn: local_socket::Stream) -> Self { + Self { + conn, + alive: true, + handshaking: true, + next_packet: None, + } + } + + fn kill(&mut self) { + self.alive = false; + } + + fn process_handshake(&mut self, payload: Payload) -> anyhow::Result<()> { + let Ok(handshake) = binary_decode::<ipc::Handshake>(&payload) else { + anyhow::bail!("Invalid handshake"); + }; + + if handshake.protocol_version != ipc::PROTOCOL_VERSION { + anyhow::bail!( + "Unsupported protocol version {}", + handshake.protocol_version + ); + } + + if handshake.magic != ipc::CONNECTION_MAGIC { + anyhow::bail!("Invalid magic"); + } + + log::info!("Accepted new connection"); + self.handshaking = false; + Ok(()) + } + + fn handle_wvr_display_list( + &mut self, + params: &TickParams, + serial: ipc::Serial, + ) -> anyhow::Result<()> { + let list: Vec<packet_server::WvrDisplay> = params + .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(display::DisplayHandle::new(idx as u32, cell.generation))) + }) + .collect(); + + send_packet( + &mut self.conn, + &binary_encode(&PacketServer::WvrDisplayListResponse( + serial, + packet_server::WvrDisplayList { list }, + )), + )?; + + Ok(()) + } + + fn handle_wvr_display_create( + &mut self, + params: &mut TickParams, + serial: ipc::Serial, + packet_params: packet_client::WvrDisplayCreateParams, + ) -> anyhow::Result<()> { + let display_handle = params.state.create_display( + packet_params.width, + packet_params.height, + &packet_params.name, + false, + )?; + + params + .tasks + .push(TickTask::NewDisplay(packet_params.clone())); + + send_packet( + &mut self.conn, + &binary_encode(&PacketServer::WvrDisplayCreateResponse( + serial, + display_handle.as_packet(), + )), + )?; + Ok(()) + } + + fn handle_wvr_process_launch( + &mut self, + params: &mut TickParams, + serial: ipc::Serial, + packet_params: packet_client::WvrProcessLaunchParams, + ) -> anyhow::Result<()> { + let args_vec = gen_args_vec(&packet_params.args); + let env_vec = gen_env_vec(&packet_params.env); + + let res = params.state.spawn_process( + super::display::DisplayHandle::from_packet(packet_params.target_display), + &packet_params.exec, + &args_vec, + &env_vec, + ); + + let res = res.map(|r| r.as_packet()).map_err(|e| e.to_string()); + + send_packet( + &mut self.conn, + &binary_encode(&PacketServer::WvrProcessLaunchResponse(serial, res)), + )?; + + Ok(()) + } + + fn handle_wvr_process_get( + &mut self, + params: &TickParams, + serial: ipc::Serial, + display_handle: packet_server::WvrDisplayHandle, + ) -> anyhow::Result<()> { + let native_handle = &display::DisplayHandle::from_packet(display_handle.clone()); + let disp = params + .state + .displays + .get(native_handle) + .map(|disp| disp.as_packet(*native_handle)); + + send_packet( + &mut self.conn, + &binary_encode(&PacketServer::WvrDisplayGetResponse(serial, disp)), + )?; + + Ok(()) + } + + fn handle_wvr_process_list( + &mut self, + params: &TickParams, + serial: ipc::Serial, + ) -> anyhow::Result<()> { + let list: Vec<packet_server::WvrProcess> = params + .state + .processes + .vec + .iter() + .enumerate() + .filter_map(|(idx, opt_cell)| { + let Some(cell) = opt_cell else { + return None; + }; + let process = &cell.obj; + Some(process.to_packet(process::ProcessHandle::new(idx as u32, cell.generation))) + }) + .collect(); + + send_packet( + &mut self.conn, + &binary_encode(&PacketServer::WvrProcessListResponse( + serial, + packet_server::WvrProcessList { list }, + )), + )?; + + Ok(()) + } + + // This request doesn't return anything to the client + fn handle_wvr_process_terminate( + &mut self, + params: &mut TickParams, + process_handle: packet_server::WvrProcessHandle, + ) -> anyhow::Result<()> { + let native_handle = &process::ProcessHandle::from_packet(process_handle.clone()); + let process = params.state.processes.get_mut(native_handle); + + let Some(process) = process else { + return Ok(()); + }; + + process.terminate(); + + Ok(()) + } + + fn process_payload(&mut self, params: &mut TickParams, payload: Payload) -> anyhow::Result<()> { + if self.handshaking { + self.process_handshake(payload)?; + return Ok(()); + } + + let packet: PacketClient = binary_decode(&payload)?; + match packet { + PacketClient::WvrDisplayList(serial) => { + self.handle_wvr_display_list(params, serial)?; + } + PacketClient::WvrDisplayGet(serial, display_handle) => { + self.handle_wvr_process_get(params, serial, display_handle)?; + } + PacketClient::WvrProcessList(serial) => { + self.handle_wvr_process_list(params, serial)?; + } + PacketClient::WvrProcessLaunch(serial, packet_params) => { + self.handle_wvr_process_launch(params, serial, packet_params)?; + } + PacketClient::WvrDisplayCreate(serial, packet_params) => { + self.handle_wvr_display_create(params, serial, packet_params)?; + } + PacketClient::WvrProcessTerminate(process_handle) => { + self.handle_wvr_process_terminate(params, process_handle)?; + } + } + + Ok(()) + } + + fn process_check_payload(&mut self, params: &mut TickParams, payload: Payload) -> bool { + log::debug!("payload size {}", payload.len()); + + if let Err(e) = self.process_payload(params, payload) { + log::error!("Invalid payload from the client, closing connection: {}", e); + self.kill(); + false + } else { + true + } + } + + fn read_packet(&mut self, params: &mut TickParams) -> bool { + if let Some(payload_size) = self.next_packet { + let Some(payload) = read_payload(&mut self.conn, payload_size) else { + // still failed to read payload, try in next tick + return false; + }; + + if !self.process_check_payload(params, payload) { + return false; + } + + self.next_packet = None; + } + + let mut buf_packet_header: [u8; 4] = [0; 4]; + if !read_check(4, self.conn.read(&mut buf_packet_header)) { + return false; + } + + let payload_size = u32::from_be_bytes(buf_packet_header[0..4].try_into().unwrap()); // 0-3 bytes (u32 size) + + let size_limit: u32 = 128 * 1024; + + if payload_size > size_limit { + // over 128 KiB? + log::error!( + "Client sent a packet header with the size over {} bytes, closing connection.", + size_limit + ); + self.kill(); + return false; + } + + let Some(payload) = read_payload(&mut self.conn, payload_size) else { + // failed to read payload, try in next tick + self.next_packet = Some(payload_size); + return false; + }; + + if !self.process_check_payload(params, payload) { + return false; + } + + true + } + + fn tick(&mut self, params: &mut TickParams) { + while self.read_packet(params) {} + } +} + +impl Drop for Connection { + fn drop(&mut self) { + log::info!("Connection closed"); + } +} + +pub struct WayVRServer { + listener: local_socket::Listener, + connections: Vec<Connection>, +} + +impl WayVRServer { + pub fn new() -> anyhow::Result<Self> { + let printname = "/tmp/wayvr_ipc.sock"; + let name = printname.to_ns_name::<local_socket::GenericNamespaced>()?; + let opts = local_socket::ListenerOptions::new() + .name(name) + .nonblocking(local_socket::ListenerNonblockingMode::Both); + let listener = match opts.create_sync() { + Ok(listener) => listener, + Err(e) => anyhow::bail!("Failed to start WayVRServer IPC listener. Reason: {}", e), + }; + + log::info!("WayVRServer IPC running at {}", printname); + + Ok(Self { + listener, + connections: Vec::new(), + }) + } + + fn accept_connections(&mut self) { + let Ok(conn) = self.listener.accept() else { + return; // No new connection or other error + }; + + self.connections.push(Connection::new(conn)); + } + + fn tick_connections(&mut self, params: &mut TickParams) { + for c in &mut self.connections { + c.tick(params); + } + + // remove killed connections + self.connections.retain(|c| c.alive); + } + + pub fn tick(&mut self, params: &mut TickParams) -> anyhow::Result<()> { + self.accept_connections(); + self.tick_connections(params); + Ok(()) + } +} diff --git a/src/config_wayvr.rs b/src/config_wayvr.rs index f212a9a..f8c5dd9 100644 --- a/src/config_wayvr.rs +++ b/src/config_wayvr.rs @@ -17,7 +17,7 @@ use crate::{ wayvr, }, config::{load_known_yaml, ConfigType}, - overlays::wayvr::{WayVRAction, WayVRState}, + overlays::wayvr::{WayVRAction, WayVRData}, }; // Flat version of RelativeTo @@ -40,6 +40,16 @@ impl AttachTo { AttachTo::Head => RelativeTo::Head, } } + + pub fn from_packet(input: &wayvr_ipc::packet_client::AttachTo) -> AttachTo { + match input { + wayvr_ipc::packet_client::AttachTo::None => AttachTo::None, + wayvr_ipc::packet_client::AttachTo::HandLeft => AttachTo::HandLeft, + wayvr_ipc::packet_client::AttachTo::HandRight => AttachTo::HandRight, + wayvr_ipc::packet_client::AttachTo::Head => AttachTo::Head, + wayvr_ipc::packet_client::AttachTo::Stage => AttachTo::Stage, + } + } } #[derive(Clone, Deserialize, Serialize)] @@ -60,8 +70,8 @@ pub struct WayVRAppEntry { #[derive(Clone, Deserialize, Serialize)] pub struct WayVRDisplay { - pub width: u32, - pub height: u32, + pub width: u16, + pub height: u16, pub scale: Option<f32>, pub rotation: Option<Rotation>, pub pos: Option<[f32; 3]>, @@ -96,12 +106,20 @@ fn def_keyboard_repeat_rate() -> u32 { 50 } +#[derive(Deserialize, Serialize)] +pub struct WayVRDashboard { + pub exec: String, + pub args: Option<String>, + pub env: Option<Vec<String>>, +} + #[derive(Deserialize, Serialize)] pub struct WayVRConfig { pub version: u32, pub run_compositor_at_start: bool, pub catalogs: HashMap<String, WayVRCatalog>, pub displays: BTreeMap<String, WayVRDisplay>, // sorted alphabetically + pub dashboard: WayVRDashboard, #[serde(default = "def_true")] pub auto_hide: bool, @@ -154,7 +172,7 @@ impl WayVRConfig { &self, config: &crate::config::GeneralConfig, tasks: &mut TaskContainer, - ) -> anyhow::Result<Option<Rc<RefCell<WayVRState>>>> { + ) -> anyhow::Result<Option<Rc<RefCell<WayVRData>>>> { let primary_count = self .displays .iter() @@ -182,8 +200,8 @@ impl WayVRConfig { if self.run_compositor_at_start { // Start Wayland server instantly - Ok(Some(Rc::new(RefCell::new(WayVRState::new( - Self::get_wayvr_config(config, &self), + Ok(Some(Rc::new(RefCell::new(WayVRData::new( + Self::get_wayvr_config(config, self), )?)))) } else { // Lazy-init WayVR later if the user requested diff --git a/src/overlays/anchor.rs b/src/overlays/anchor.rs index 1e44cf0..1643170 100644 --- a/src/overlays/anchor.rs +++ b/src/overlays/anchor.rs @@ -2,7 +2,7 @@ use glam::Vec3A; use once_cell::sync::Lazy; use std::sync::Arc; -use crate::backend::overlay::{OverlayData, OverlayState, RelativeTo}; +use crate::backend::overlay::{OverlayData, OverlayState, RelativeTo, Z_ORDER_ANCHOR}; use crate::config::{load_known_yaml, ConfigType}; use crate::gui::modular::{modular_canvas, ModularUiConfig}; use crate::state::AppState; @@ -21,7 +21,7 @@ where want_visible: false, interactable: false, grabbable: false, - z_order: 67, + z_order: Z_ORDER_ANCHOR, spawn_scale: config.width, spawn_point: Vec3A::NEG_Z * 0.5, relative_to: RelativeTo::Stage, diff --git a/src/overlays/keyboard.rs b/src/overlays/keyboard.rs index f68910c..80a2fb3 100644 --- a/src/overlays/keyboard.rs +++ b/src/overlays/keyboard.rs @@ -40,7 +40,7 @@ fn send_key(app: &mut AppState, key: VirtualKey, down: bool) { { #[cfg(feature = "wayvr")] if let Some(wayvr) = &app.wayvr { - wayvr.borrow_mut().state.send_key(key as u32, down); + wayvr.borrow_mut().data.state.send_key(key as u32, down); } } } diff --git a/src/overlays/toast.rs b/src/overlays/toast.rs index 0cab820..e042ae3 100644 --- a/src/overlays/toast.rs +++ b/src/overlays/toast.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::{ backend::{ common::OverlaySelector, - overlay::{OverlayBackend, OverlayState, RelativeTo}, + overlay::{OverlayBackend, OverlayState, RelativeTo, Z_ORDER_TOAST}, task::TaskType, }, gui::{canvas::builder::CanvasBuilder, color_parse}, @@ -199,7 +199,7 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box<dyn spawn_scale: size.0 * PIXELS_TO_METERS, spawn_rotation, spawn_point, - z_order: 70, + z_order: Z_ORDER_TOAST, relative_to, ..Default::default() }; diff --git a/src/overlays/watch.rs b/src/overlays/watch.rs index a28d847..eb770e7 100644 --- a/src/overlays/watch.rs +++ b/src/overlays/watch.rs @@ -1,7 +1,7 @@ use glam::Vec3A; use crate::{ - backend::overlay::{ui_transform, OverlayData, OverlayState, RelativeTo}, + backend::overlay::{ui_transform, OverlayData, OverlayState, RelativeTo, Z_ORDER_WATCH}, config::{load_known_yaml, ConfigType}, gui::{ canvas::Canvas, @@ -25,7 +25,7 @@ where name: WATCH_NAME.into(), want_visible: true, interactable: true, - z_order: 68, + z_order: Z_ORDER_WATCH, spawn_scale: config.width, spawn_point: state.session.config.watch_pos, spawn_rotation: state.session.config.watch_rot, diff --git a/src/overlays/wayvr.rs b/src/overlays/wayvr.rs index 0dd0998..5085ed4 100644 --- a/src/overlays/wayvr.rs +++ b/src/overlays/wayvr.rs @@ -10,23 +10,33 @@ use crate::{ input::{self, InteractionHandler}, overlay::{ ui_transform, FrameTransform, OverlayData, OverlayID, OverlayRenderer, OverlayState, - SplitOverlayBackend, + SplitOverlayBackend, Z_ORDER_DASHBOARD, }, task::TaskType, - wayvr::{self, display, WayVR}, + wayvr::{ + self, display, + server_ipc::{gen_args_vec, gen_env_vec}, + WayVR, + }, }, + config_wayvr, graphics::WlxGraphics, state::{self, AppState, KeyboardFocus}, }; +// Hard-coded for now +const DASHBOARD_WIDTH: u16 = 960; +const DASHBOARD_HEIGHT: u16 = 540; +const DASHBOARD_DISPLAY_NAME: &str = "_DASHBOARD"; + pub struct WayVRContext { - wayvr: Rc<RefCell<WayVRState>>, + wayvr: Rc<RefCell<WayVRData>>, display: wayvr::display::DisplayHandle, } impl WayVRContext { pub fn new( - wvr: Rc<RefCell<WayVRState>>, + wvr: Rc<RefCell<WayVRData>>, display: wayvr::display::DisplayHandle, ) -> anyhow::Result<Self> { Ok(Self { @@ -36,18 +46,46 @@ impl WayVRContext { } } -pub struct WayVRState { - pub display_handle_map: HashMap<display::DisplayHandle, OverlayID>, - pub state: WayVR, +struct OverlayToCreate { + pub conf_display: config_wayvr::WayVRDisplay, + pub disp_handle: display::DisplayHandle, +} + +pub struct WayVRData { + display_handle_map: HashMap<display::DisplayHandle, OverlayID>, + overlays_to_create: Vec<OverlayToCreate>, + pub data: WayVR, } -impl WayVRState { +impl WayVRData { pub fn new(config: wayvr::Config) -> anyhow::Result<Self> { Ok(Self { display_handle_map: Default::default(), - state: WayVR::new(config)?, + data: WayVR::new(config)?, + overlays_to_create: Vec::new(), }) } + + 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 + } } pub struct WayVRInteractionHandler { @@ -72,14 +110,15 @@ impl InteractionHandler for WayVRInteractionHandler { ) -> Option<input::Haptics> { let ctx = self.context.borrow(); - let wayvr = &mut ctx.wayvr.borrow_mut().state; - if let Some(disp) = wayvr.displays.get(&ctx.display) { + let wayvr = &mut ctx.wayvr.borrow_mut().data; + + if let Some(disp) = wayvr.state.displays.get(&ctx.display) { let pos = self.mouse_transform.transform_point2(hit.uv); let x = ((pos.x * disp.width as f32) as i32).max(0); let y = ((pos.y * disp.height as f32) as i32).max(0); let ctx = self.context.borrow(); - wayvr.send_mouse_move(ctx.display, x as u32, y as u32); + wayvr.state.send_mouse_move(ctx.display, x as u32, y as u32); } None @@ -100,11 +139,11 @@ impl InteractionHandler for WayVRInteractionHandler { } } { let ctx = self.context.borrow(); - let wayvr = &mut ctx.wayvr.borrow_mut().state; + let wayvr = &mut ctx.wayvr.borrow_mut().data; if pressed { - wayvr.send_mouse_down(ctx.display, index); + wayvr.state.send_mouse_down(ctx.display, index); } else { - wayvr.send_mouse_up(ctx.display, index); + wayvr.state.send_mouse_up(ctx.display, index); } } } @@ -113,6 +152,7 @@ impl InteractionHandler for WayVRInteractionHandler { let ctx = self.context.borrow(); ctx.wayvr .borrow_mut() + .data .state .send_mouse_scroll(ctx.display, delta); } @@ -127,8 +167,8 @@ pub struct WayVRRenderer { impl WayVRRenderer { pub fn new( - app: &mut state::AppState, - wvr: Rc<RefCell<WayVRState>>, + app: &state::AppState, + wvr: Rc<RefCell<WayVRData>>, display: wayvr::display::DisplayHandle, ) -> anyhow::Result<Self> { Ok(Self { @@ -140,19 +180,13 @@ impl WayVRRenderer { } } -fn get_or_create_display<O>( +fn get_or_create_display_by_name( app: &mut AppState, - wayvr: &mut WayVRState, + wayvr: &mut WayVRData, disp_name: &str, -) -> anyhow::Result<(display::DisplayHandle, Option<OverlayData<O>>)> -where - O: Default, -{ - let created_overlay: Option<OverlayData<O>>; - +) -> anyhow::Result<display::DisplayHandle> { let disp_handle = - if let Some(disp) = WayVR::get_display_by_name(&wayvr.state.displays, disp_name) { - created_overlay = None; + if let Some(disp) = WayVR::get_display_by_name(&wayvr.data.state.displays, disp_name) { disp } else { let conf_display = app @@ -165,117 +199,280 @@ where ))? .clone(); - let disp_handle = wayvr.state.create_display( + let disp_handle = wayvr.data.state.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, + wayvr.overlays_to_create.push(OverlayToCreate { + conf_display, disp_handle, - conf_display.scale.unwrap_or(1.0), - )?; + }); + disp_handle + }; + + Ok(disp_handle) +} + +fn toggle_dashboard<O>( + app: &mut AppState, + overlays: &mut OverlayContainer<O>, + wayvr: &mut WayVRData, +) -> anyhow::Result<()> +where + O: Default, +{ + 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 mut overlay = create_overlay::<O>( + app, + wayvr, + 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, + }, + }, + )?; + + overlay.state.want_visible = true; + overlay.state.spawn_scale = 1.25; + overlay.state.z_order = Z_ORDER_DASHBOARD; + overlay.state.reset(app, true); + + // FIXME: overlay curvature needs to be dispatched for some unknown reason, this value is not set otherwise + app.tasks.enqueue(TaskType::Overlay( + OverlaySelector::Id(overlay.state.id), + Box::new(move |_app, o| { + o.curvature = Some(0.15); + }), + )); + + overlays.add(overlay); + + let conf_dash = &app.session.wayvr_config.dashboard; + + let args_vec = match &conf_dash.args { + Some(args) => gen_args_vec(args), + None => vec![], + }; + + let env_vec = match &conf_dash.env { + Some(env) => gen_env_vec(env), + None => vec![], + }; + + // Start dashboard specified in the WayVR config + let _process_handle_unused = wayvr - .display_handle_map - .insert(disp_handle, overlay.state.id); + .data + .state + .spawn_process(disp_handle, &conf_dash.exec, &args_vec, &env_vec)?; - if let Some(attach_to) = &conf_display.attach_to { - overlay.state.relative_to = attach_to.get_relative_to(); - } + return Ok(()); + } - 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), - ); - } + 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"); + }; - if let Some(pos) = &conf_display.pos { - overlay.state.spawn_point = Vec3A::from_slice(pos); + app.tasks.enqueue(TaskType::Overlay( + OverlaySelector::Id(overlay_id), + Box::new(move |app, o| { + // Toggle visibility + o.want_visible = !o.want_visible; + if o.want_visible { + o.reset(app, true); } + }), + )); - let display = wayvr.state.displays.get_mut(&disp_handle).unwrap(); // Never fails - display.overlay_id = Some(overlay.state.id); + Ok(()) +} - created_overlay = Some(overlay); +fn create_overlay<O>( + app: &mut AppState, + data: &mut WayVRData, + name: &str, + cell: OverlayToCreate, +) -> anyhow::Result<OverlayData<O>> +where + O: Default, +{ + let conf_display = &cell.conf_display; + let disp_handle = cell.disp_handle; + + let mut overlay = create_wayvr_display_overlay::<O>( + app, + conf_display.width, + conf_display.height, + disp_handle, + conf_display.scale.unwrap_or(1.0), + name, + )?; + + data.display_handle_map + .insert(disp_handle, overlay.state.id); + + if let Some(attach_to) = &conf_display.attach_to { + overlay.state.relative_to = attach_to.get_relative_to(); + } - disp_handle + 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 = data.data.state.displays.get_mut(&disp_handle).unwrap(); // Never fails + display.overlay_id = Some(overlay.state.id); + + Ok(overlay) +} + +fn create_queued_displays<O>( + app: &mut AppState, + data: &mut WayVRData, + overlays: &mut OverlayContainer<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 }; - Ok((disp_handle, created_overlay)) + let name = disp.name.clone(); + + let overlay = create_overlay::<O>(app, data, name.as_str(), cell)?; + overlays.add(overlay); // Insert freshly created WayVR overlay into wlx stack + } + + Ok(()) } pub fn tick_events<O>(app: &mut AppState, overlays: &mut OverlayContainer<O>) -> anyhow::Result<()> where O: Default, { - if let Some(r_wayvr) = app.wayvr.clone() { - let mut wayvr = r_wayvr.borrow_mut(); - while let Some(signal) = wayvr.state.signals.read() { - match signal { - wayvr::WayVRSignal::DisplayHideRequest(display_handle) => { - if let Some(overlay_id) = wayvr.display_handle_map.get(&display_handle) { - let overlay_id = *overlay_id; - wayvr.state.set_display_visible(display_handle, false); - app.tasks.enqueue(TaskType::Overlay( - OverlaySelector::Id(overlay_id), - Box::new(move |_app, o| { - o.want_visible = false; - }), - )); - } + let Some(r_wayvr) = app.wayvr.clone() else { + return Ok(()); + }; + + let mut wayvr = r_wayvr.borrow_mut(); + + while let Some(signal) = wayvr.data.signals.read() { + match signal { + wayvr::WayVRSignal::DisplayHideRequest(display_handle) => { + if let Some(overlay_id) = wayvr.display_handle_map.get(&display_handle) { + let overlay_id = *overlay_id; + wayvr.data.state.set_display_visible(display_handle, false); + app.tasks.enqueue(TaskType::Overlay( + OverlaySelector::Id(overlay_id), + Box::new(move |_app, o| { + o.want_visible = false; + }), + )); } } } + } - let res = wayvr.state.tick_events()?; - drop(wayvr); - - 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) - }; + let res = wayvr.data.tick_events()?; + drop(wayvr); + + for result in res { + match result { + wayvr::TickTask::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 = r_wayvr.borrow_mut(); + if let Some(disp_name) = disp_name { + let mut wayvr = r_wayvr.borrow_mut(); - log::info!("Registering external process with PID {}", req.pid); + log::info!("Registering external process with PID {}", req.pid); - let (disp_handle, created_overlay) = - get_or_create_display::<O>(app, &mut wayvr, &disp_name)?; + let disp_handle = get_or_create_display_by_name(app, &mut wayvr, &disp_name)?; - wayvr.state.add_external_process(disp_handle, req.pid); + wayvr.data.state.add_external_process(disp_handle, req.pid); - wayvr.state.manager.add_client(wayvr::client::WayVRClient { + wayvr + .data + .state + .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); - } - } } } + wayvr::TickTask::NewDisplay(cpar) => { + 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); + + let disp_handle = wayvr.data.state.create_display( + cpar.width, + cpar.height, + &unique_name, + false, + )?; + + 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, + }, + }); + } } } + let mut wayvr = r_wayvr.borrow_mut(); + create_queued_displays(app, &mut wayvr, overlays)?; + Ok(()) } @@ -290,11 +487,11 @@ impl WayVRRenderer { let ctx = self.context.borrow_mut(); let wayvr = ctx.wayvr.borrow_mut(); - if let Some(disp) = wayvr.state.displays.get(&ctx.display) { + if let Some(disp) = wayvr.data.state.displays.get(&ctx.display) { let frame = DmabufFrame { format: FrameFormat { - width: disp.width, - height: disp.height, + width: disp.width as u32, + height: disp.height as u32, fourcc: FourCC { value: data.mod_info.fourcc, }, @@ -339,15 +536,15 @@ impl OverlayRenderer for WayVRRenderer { fn pause(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> { let ctx = self.context.borrow_mut(); - let wayvr = &mut ctx.wayvr.borrow_mut().state; - wayvr.set_display_visible(ctx.display, false); + 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().state; - wayvr.set_display_visible(ctx.display, true); + let wayvr = &mut ctx.wayvr.borrow_mut().data; + wayvr.state.set_display_visible(ctx.display, true); Ok(()) } @@ -355,9 +552,10 @@ impl OverlayRenderer for WayVRRenderer { let ctx = self.context.borrow(); let mut wayvr = ctx.wayvr.borrow_mut(); - wayvr.state.tick_display(ctx.display)?; + wayvr.data.tick_display(ctx.display)?; let dmabuf_data = wayvr + .data .state .get_dmabuf_data(ctx.display) .ok_or(anyhow::anyhow!("Failed to fetch dmabuf data"))? @@ -385,18 +583,19 @@ impl OverlayRenderer for WayVRRenderer { #[allow(dead_code)] pub fn create_wayvr_display_overlay<O>( app: &mut state::AppState, - display_width: u32, - display_height: u32, + display_width: u16, + display_height: u16, display_handle: wayvr::display::DisplayHandle, display_scale: f32, + name: &str, ) -> anyhow::Result<OverlayData<O>> where O: Default, { - let transform = ui_transform(&[display_width, display_height]); + let transform = ui_transform(&[display_width as u32, display_height as u32]); let state = OverlayState { - name: format!("WayVR Screen ({}x{})", display_width, display_height).into(), + name: format!("WayVR - {}", name).into(), keyboard_focus: Some(KeyboardFocus::WayVR), want_visible: true, interactable: true, @@ -440,20 +639,21 @@ pub enum WayVRAction { display_name: Arc<str>, action: WayVRDisplayClickAction, }, + ToggleDashboard, } -fn show_display<O>(wayvr: &mut WayVRState, overlays: &mut OverlayContainer<O>, display_name: &str) +fn show_display<O>(wayvr: &mut WayVRData, overlays: &mut OverlayContainer<O>, display_name: &str) where O: Default, { - if let Some(display) = WayVR::get_display_by_name(&wayvr.state.displays, display_name) { + 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) { if let Some(overlay) = overlays.mut_by_id(*overlay_id) { overlay.state.want_visible = true; } } - wayvr.state.set_display_visible(display, true); + wayvr.data.state.set_display_visible(display, true); } } @@ -462,7 +662,7 @@ fn action_app_click<O>( overlays: &mut OverlayContainer<O>, catalog_name: &Arc<str>, app_name: &Arc<str>, -) -> anyhow::Result<Option<OverlayData<O>>> +) -> anyhow::Result<()> where O: Default, { @@ -481,46 +681,40 @@ where if let Some(app_entry) = catalog.get_app(app_name) { let mut wayvr = wayvr.borrow_mut(); - let (disp_handle, created_overlay) = - get_or_create_display::<O>(app, &mut wayvr, &app_entry.target_display)?; + let disp_handle = + get_or_create_display_by_name(app, &mut wayvr, &app_entry.target_display)?; - // Parse additional args - let args_vec: Vec<&str> = if let Some(args) = &app_entry.args { - args.as_str().split_whitespace().collect() - } else { - vec![] + let args_vec = match &app_entry.args { + Some(args) => gen_args_vec(args), + None => vec![], }; - // Parse additional env - let env_vec: Vec<(&str, &str)> = if let Some(env) = &app_entry.env { - // splits "FOO=BAR=TEST,123" into (&"foo", &"bar=test,123") - env.iter() - .filter_map(|e| e.as_str().split_once('=')) - .collect() - } else { - vec![] + let env_vec = match &app_entry.env { + Some(env) => gen_env_vec(env), + None => vec![], }; // 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.state.terminate_process(process_handle); + wayvr.data.terminate_process(process_handle); } else { // Spawn process wayvr + .data .state .spawn_process(disp_handle, &app_entry.exec, &args_vec, &env_vec)?; - show_display(&mut wayvr, overlays, &app_entry.target_display.as_str()); + show_display::<O>(&mut wayvr, overlays, app_entry.target_display.as_str()); } - Ok(created_overlay) - } else { - Ok(None) } + + Ok(()) } pub fn action_display_click<O>( @@ -535,23 +729,31 @@ where let wayvr = app.get_wayvr()?; let mut wayvr = wayvr.borrow_mut(); - if let Some(handle) = WayVR::get_display_by_name(&wayvr.state.displays, display_name) { - if let Some(display) = wayvr.state.displays.get_mut(&handle) { - if let Some(overlay_id) = display.overlay_id { - if let Some(overlay) = overlays.mut_by_id(overlay_id) { - match action { - WayVRDisplayClickAction::ToggleVisibility => { - // Toggle visibility - overlay.state.want_visible = !overlay.state.want_visible; - } - WayVRDisplayClickAction::Reset => { - // Show it at the front - overlay.state.want_visible = true; - overlay.state.reset(app, true); - } - } - } - } + 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.state.want_visible = !overlay.state.want_visible; + } + WayVRDisplayClickAction::Reset => { + // Show it at the front + overlay.state.want_visible = true; + overlay.state.reset(app, true); } } @@ -567,17 +769,10 @@ where catalog_name, app_name, } => { - match action_app_click(app, overlays, catalog_name, app_name) { - Ok(res) => { - if let Some(created_overlay) = res { - overlays.add(created_overlay); - } - } - Err(e) => { - // Happens if something went wrong with initialization - // or input exec path is invalid. Do nothing, just print an error - log::error!("action_app_click failed: {}", e); - } + 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 + log::error!("action_app_click failed: {}", e); } } WayVRAction::DisplayClick { @@ -588,5 +783,13 @@ where log::error!("action_display_click failed: {}", e); } } + WayVRAction::ToggleDashboard => { + let wayvr = app.get_wayvr().unwrap(); /* safe */ + let mut wayvr = wayvr.borrow_mut(); + + if let Err(e) = toggle_dashboard::<O>(app, overlays, &mut wayvr) { + log::error!("toggle_dashboard failed: {}", e); + } + } } } diff --git a/src/res/wayvr.yaml b/src/res/wayvr.yaml index a3b378d..69309bb 100644 --- a/src/res/wayvr.yaml +++ b/src/res/wayvr.yaml @@ -5,8 +5,9 @@ version: 1 -# Set to true if you want to make Wyland server instantly available -# (used for remote starting external processes) +# Set to true if you want to make Wyland server instantly available. +# By default, WayVR starts only when it's needed. +# (this option is primarily used for remote starting external processes and development purposes) run_compositor_at_start: false # Automatically close overlays with zero window count? @@ -22,6 +23,17 @@ keyboard_repeat_delay: 200 # Chars per second keyboard_repeat_rate: 50 +# WayVR-compatible dashboard. +# For now, there is only one kind of dashboard with WayVR IPC support (WayVR Dashboard). +# Build instructions: https://github.com/olekolek1000/wayvr-dashboard +# exec: Executable path, for example /home/USER/wayvr_dashboard/src-tauri/target/release/wayvr_dashboard +# GDK_BACKEND=wayland: Force-use Wayland for GTK apps +# LIBGL_ALWAYS_SOFTWARE: Mesa crash mitigation for Tauri apps on AMD GPUs +dashboard: + exec: "wayvr_dashboard" + args: "" + env: ["GDK_BACKEND=wayland", "LIBGL_ALWAYS_SOFTWARE=1"] + displays: Watch: width: 400 diff --git a/src/state.rs b/src/state.rs index b3c4cb2..67b268f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -10,7 +10,7 @@ use vulkano::image::view::ImageView; #[cfg(feature = "wayvr")] use { crate::config_wayvr::{self, WayVRConfig}, - crate::overlays::wayvr::WayVRState, + crate::overlays::wayvr::WayVRData, std::{cell::RefCell, rc::Rc}, }; @@ -50,7 +50,7 @@ pub struct AppState { pub keyboard_focus: KeyboardFocus, #[cfg(feature = "wayvr")] - pub wayvr: Option<Rc<RefCell<WayVRState>>>, // Dynamically created if requested + pub wayvr: Option<Rc<RefCell<WayVRData>>>, // Dynamically created if requested } impl AppState { @@ -122,11 +122,11 @@ impl AppState { #[cfg(feature = "wayvr")] #[allow(dead_code)] - pub fn get_wayvr(&mut self) -> anyhow::Result<Rc<RefCell<WayVRState>>> { + pub fn get_wayvr(&mut self) -> anyhow::Result<Rc<RefCell<WayVRData>>> { if let Some(wvr) = &self.wayvr { Ok(wvr.clone()) } else { - let wayvr = Rc::new(RefCell::new(WayVRState::new( + let wayvr = Rc::new(RefCell::new(WayVRData::new( WayVRConfig::get_wayvr_config(&self.session.config, &self.session.wayvr_config), )?)); self.wayvr = Some(wayvr.clone());