diff --git a/src/backend/openvr/mod.rs b/src/backend/openvr/mod.rs index 6a17f4a..d24294c 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::action_wayvr; +use crate::overlays::wayvr::wayvr_action; pub mod helpers; pub mod input; @@ -266,12 +266,8 @@ pub fn openvr_run(running: Arc, show_by_default: bool) -> Result<(), } }, #[cfg(feature = "wayvr")] - TaskType::WayVR(task) => { - if let Some(overlay) = - action_wayvr(&task.catalog_name, &task.app_name, &mut state) - { - overlays.add(overlay); - } + TaskType::WayVR(action) => { + wayvr_action(&mut state, &mut overlays, &action); } } } diff --git a/src/backend/openxr/mod.rs b/src/backend/openxr/mod.rs index 5f5a960..a0d305c 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::action_wayvr; +use crate::overlays::wayvr::wayvr_action; mod helpers; mod input; @@ -491,12 +491,8 @@ pub fn openxr_run(running: Arc, show_by_default: bool) -> Result<(), _ => {} }, #[cfg(feature = "wayvr")] - TaskType::WayVR(task) => { - if let Some(overlay) = - action_wayvr(&task.catalog_name, &task.app_name, &mut app_state) - { - overlays.add(overlay); - } + TaskType::WayVR(action) => { + wayvr_action(&mut app_state, &mut overlays, &action); } } } diff --git a/src/backend/task.rs b/src/backend/task.rs index 47c598f..76ce0e8 100644 --- a/src/backend/task.rs +++ b/src/backend/task.rs @@ -5,13 +5,13 @@ use std::{ time::Instant, }; -#[cfg(feature = "wayvr")] -use std::sync::Arc; - use serde::Deserialize; use crate::state::AppState; +#[cfg(feature = "wayvr")] +use crate::overlays::wayvr::WayVRAction; + use super::{ common::OverlaySelector, overlay::{OverlayBackend, OverlayState}, @@ -52,12 +52,6 @@ pub enum SystemTask { ShowHide, } -#[cfg(feature = "wayvr")] -pub struct WayVRTask { - pub catalog_name: Arc, - pub app_name: Arc, -} - pub type OverlayTask = dyn FnOnce(&mut AppState, &mut OverlayState) + Send; pub type CreateOverlayTask = dyn FnOnce(&mut AppState) -> Option<(OverlayState, Box)> + Send; @@ -69,7 +63,7 @@ pub enum TaskType { DropOverlay(OverlaySelector), System(SystemTask), #[cfg(feature = "wayvr")] - WayVR(WayVRTask), + WayVR(WayVRAction), } #[derive(Deserialize, Clone, Copy)] diff --git a/src/backend/wayvr/display.rs b/src/backend/wayvr/display.rs index f44964b..9adfd40 100644 --- a/src/backend/wayvr/display.rs +++ b/src/backend/wayvr/display.rs @@ -15,7 +15,7 @@ use smithay::{ wayland::shell::xdg::ToplevelSurface, }; -use crate::gen_id; +use crate::{backend::overlay::OverlayID, gen_id}; use super::{ client::WayVRManager, comp::send_frames_surface_tree, egl_data, smithay_wrapper, window, @@ -47,6 +47,8 @@ pub struct Display { pub width: u32, pub height: u32, pub name: String, + pub visible: bool, + pub overlay_id: Option, wm: Rc>, displayed_windows: Vec, wayland_env: super::WaylandEnv, @@ -112,6 +114,8 @@ impl Display { gles_texture, wayland_env, processes: Vec::new(), + visible: true, + overlay_id: None, }) } @@ -216,6 +220,11 @@ impl Display { None } + pub fn set_visible(&mut self, visible: bool) { + log::info!("Display \"{}\" visible: {}", self.name.as_str(), visible); + self.visible = visible; + } + pub fn send_mouse_move(&self, manager: &mut WayVRManager, x: u32, y: u32) { if let Some(window_handle) = self.get_hovered_window(x, y) { let wm = self.wm.borrow(); diff --git a/src/backend/wayvr/mod.rs b/src/backend/wayvr/mod.rs index 87b6521..8107f9a 100644 --- a/src/backend/wayvr/mod.rs +++ b/src/backend/wayvr/mod.rs @@ -43,7 +43,7 @@ impl WaylandEnv { pub struct WayVR { time_start: u64, gles_renderer: GlesRenderer, - displays: display::DisplayVec, + pub displays: display::DisplayVec, manager: client::WayVRManager, wm: Rc>, egl_data: Rc, @@ -102,13 +102,19 @@ impl WayVR { pub fn tick_display(&mut self, display: display::DisplayHandle) -> anyhow::Result<()> { // millis since the start of wayvr - let time_ms = get_millis() - self.time_start; let display = self .displays .get(&display) .ok_or(anyhow::anyhow!("Invalid display handle"))?; + let time_ms = get_millis() - self.time_start; + + if !display.visible { + // Display is invisible, do not render + return Ok(()); + } + display.tick_render(&mut self.gles_renderer, time_ms)?; Ok(()) @@ -172,6 +178,12 @@ impl WayVR { self.manager.send_key(virtual_key, down); } + pub fn set_display_visible(&mut self, display: display::DisplayHandle, visible: bool) { + if let Some(display) = self.displays.get_mut(&display) { + display.set_visible(visible); + } + } + pub fn get_dmabuf_data(&self, display: display::DisplayHandle) -> Option { self.displays .get(&display) @@ -188,14 +200,6 @@ impl WayVR { } None } - - pub fn get_display_by_handle( - &self, - display: display::DisplayHandle, - ) -> Option<&display::Display> { - self.displays.get(&display) - } - pub fn create_display( &mut self, width: u32, diff --git a/src/config_wayvr.rs b/src/config_wayvr.rs index 338b773..81905d6 100644 --- a/src/config_wayvr.rs +++ b/src/config_wayvr.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "wayvr"))] compile_error!("WayVR feature is not enabled"); -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use serde::{Deserialize, Serialize}; @@ -38,7 +38,7 @@ impl WayVRCatalog { pub struct WayVRConfig { pub version: u32, pub catalogs: HashMap, - pub displays: HashMap, + pub displays: BTreeMap, // sorted alphabetically } impl WayVRConfig { diff --git a/src/gui/modular/button.rs b/src/gui/modular/button.rs index 9f34615..d408d97 100644 --- a/src/gui/modular/button.rs +++ b/src/gui/modular/button.rs @@ -26,7 +26,7 @@ use crate::{ }; #[cfg(feature = "wayvr")] -use crate::backend::task::WayVRTask; +use crate::overlays::wayvr::WayVRAction; use super::{ExecArgs, ModularControl, ModularData}; @@ -138,10 +138,7 @@ pub enum ButtonAction { action: OverlayAction, }, #[cfg(feature = "wayvr")] - WayVR { - catalog_name: Arc, - app_name: Arc, - }, + WayVR(WayVRAction), Window { target: Arc, action: WindowAction, @@ -336,14 +333,8 @@ fn handle_action(action: &ButtonAction, press: &mut PressData, app: &mut AppStat ButtonAction::Overlay { target, action } => run_overlay(target, action, app), ButtonAction::Window { target, action } => run_window(target, action, app), #[cfg(feature = "wayvr")] - ButtonAction::WayVR { - catalog_name, - app_name, - } => { - app.tasks.enqueue(TaskType::WayVR(WayVRTask { - catalog_name: catalog_name.clone(), - app_name: app_name.clone(), - })); + ButtonAction::WayVR(action) => { + app.tasks.enqueue(TaskType::WayVR(action.clone())); } ButtonAction::VirtualKey { keycode, action } => app .hid_provider diff --git a/src/gui/modular/mod.rs b/src/gui/modular/mod.rs index 72ec058..d5cb909 100644 --- a/src/gui/modular/mod.rs +++ b/src/gui/modular/mod.rs @@ -12,6 +12,9 @@ use crate::{ graphics::dds::WlxCommandBufferDds, state::AppState, }; +#[cfg(feature = "wayvr")] +use crate::overlays::wayvr::{WayVRAction, WayVRDisplayClickAction}; + use self::{ button::{modular_button_init, ButtonAction, ButtonData, OverlayAction}, label::{modular_label_init, LabelContent, LabelData}, @@ -119,6 +122,14 @@ pub enum ModularElement { bg_color: Arc, catalog_name: Arc, }, + // Ignored if "wayvr" feature is not enabled + WayVRDisplayList { + rect: [f32; 4], + corner_radius: Option, + font_size: isize, + fg_color: Arc, + bg_color: Arc, + }, } #[derive(Deserialize, Clone)] @@ -440,10 +451,10 @@ pub fn modular_canvas( ); let data = ButtonData { - click_down: Some(vec![ButtonAction::WayVR { + click_up: Some(vec![ButtonAction::WayVR(WayVRAction::AppClick { catalog_name: catalog_name.clone(), app_name: Arc::from(app.name.as_str()), - }]), + })]), ..Default::default() }; @@ -456,7 +467,60 @@ pub fn modular_canvas( } #[cfg(not(feature = "wayvr"))] { - log::error!("WayVR feature is not available, ignoring"); + log::error!("WayVR feature is not enabled, ignoring"); + } + } + #[allow(unused_variables)] + ModularElement::WayVRDisplayList { + rect: [x, y, w, h], + corner_radius, + font_size, + fg_color, + bg_color, + } => { + #[cfg(feature = "wayvr")] + { + let mut button_x = *x; + let button_y = *y; + let displays = &state.session.wayvr_config.displays; + for (display_name, display) in displays { + let button_w: f32 = (*w / displays.len() as f32).min(80.0); + let button_h: f32 = *h; + + canvas.bg_color = color_parse(bg_color).unwrap_or(*FALLBACK_COLOR); + canvas.fg_color = color_parse(fg_color).unwrap_or(*FALLBACK_COLOR); + canvas.font_size = *font_size; + + let button = canvas.button( + button_x + 2., + button_y + 2., + button_w - 4., + button_h - 4., + corner_radius.unwrap_or_default(), + Arc::from(display_name.as_str()), + ); + + let data = ButtonData { + click_up: Some(vec![ButtonAction::WayVR(WayVRAction::DisplayClick { + display_name: Arc::from(display_name.as_str()), + action: WayVRDisplayClickAction::ToggleVisibility, + })]), + long_click_up: Some(vec![ButtonAction::WayVR( + WayVRAction::DisplayClick { + display_name: Arc::from(display_name.as_str()), + action: WayVRDisplayClickAction::Reset, + }, + )]), + ..Default::default() + }; + + modular_button_init(button, &data); + button_x += button_w; + } + } + #[cfg(not(feature = "wayvr"))] + { + log::error!("WayVR feature is not enabled, ignoring") } } } diff --git a/src/overlays/wayvr.rs b/src/overlays/wayvr.rs index b44dbb3..f404dbd 100644 --- a/src/overlays/wayvr.rs +++ b/src/overlays/wayvr.rs @@ -1,10 +1,12 @@ use glam::{vec3a, Affine2}; +use serde::Deserialize; use std::{cell::RefCell, rc::Rc, sync::Arc}; use vulkano::image::SubresourceLayout; use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane}; use crate::{ backend::{ + common::OverlayContainer, input::{self, InteractionHandler}, overlay::{ui_transform, OverlayData, OverlayRenderer, OverlayState, SplitOverlayBackend}, wayvr, @@ -53,7 +55,7 @@ impl InteractionHandler for WayVRInteractionHandler { let ctx = self.context.borrow(); let mut wayvr = ctx.wayvr.borrow_mut(); - if let Some(disp) = wayvr.get_display_by_handle(ctx.display) { + if let Some(disp) = wayvr.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); @@ -128,7 +130,7 @@ impl WayVRRenderer { let ctx = self.context.borrow_mut(); let wayvr = ctx.wayvr.borrow_mut(); - if let Some(disp) = wayvr.get_display_by_handle(ctx.display) { + if let Some(disp) = wayvr.displays.get(&ctx.display) { let frame = DmabufFrame { format: FrameFormat { width: disp.width, @@ -175,10 +177,16 @@ impl OverlayRenderer for WayVRRenderer { } fn pause(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> { + let ctx = self.context.borrow_mut(); + let mut wayvr = ctx.wayvr.borrow_mut(); + wayvr.set_display_visible(ctx.display, false); Ok(()) } fn resume(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> { + let ctx = self.context.borrow_mut(); + let mut wayvr = ctx.wayvr.borrow_mut(); + wayvr.set_display_visible(ctx.display, true); Ok(()) } @@ -212,17 +220,18 @@ impl OverlayRenderer for WayVRRenderer { #[allow(dead_code)] pub fn create_wayvr( app: &mut state::AppState, - display: &wayvr::display::Display, + display_width: u32, + display_height: u32, display_handle: wayvr::display::DisplayHandle, display_scale: f32, ) -> anyhow::Result> where O: Default, { - let transform = ui_transform(&[display.width, display.height]); + let transform = ui_transform(&[display_width, display_height]); let state = OverlayState { - name: format!("WayVR Screen ({}x{})", display.width, display.height).into(), + name: format!("WayVR Screen ({}x{})", display_width, display_height).into(), keyboard_focus: Some(KeyboardFocus::WayVR), want_visible: true, interactable: true, @@ -250,10 +259,28 @@ where }) } -fn action_wayvr_internal( +#[derive(Deserialize, Clone)] +pub enum WayVRDisplayClickAction { + ToggleVisibility, + Reset, +} + +#[derive(Deserialize, Clone)] +pub enum WayVRAction { + AppClick { + catalog_name: Arc, + app_name: Arc, + }, + DisplayClick { + display_name: Arc, + action: WayVRDisplayClickAction, + }, +} + +fn action_app_click( + app: &mut AppState, catalog_name: &Arc, app_name: &Arc, - app: &mut AppState, ) -> anyhow::Result>> where O: Default, @@ -294,13 +321,19 @@ where conf_display.height, &app_entry.target_display, )?; - let display = wayvr.get_display_by_handle(display_handle).unwrap(); // Never fails - created_overlay = Some(create_wayvr::( + + let overlay = create_wayvr::( app, - display, + conf_display.width, + conf_display.height, display_handle, conf_display.scale, - )?); + )?; + + let display = wayvr.displays.get_mut(&display_handle).unwrap(); // Never fails + display.overlay_id = Some(overlay.state.id); + + created_overlay = Some(overlay); display_handle }; @@ -327,22 +360,70 @@ where Ok(created_overlay) } -// Returns newly created overlay (if needed) -pub fn action_wayvr( - catalog_name: &Arc, - app_name: &Arc, +pub fn action_display_click( app: &mut AppState, -) -> Option> + overlays: &mut OverlayContainer, + display_name: &Arc, + action: &WayVRDisplayClickAction, +) -> anyhow::Result<()> where O: Default, { - match action_wayvr_internal(catalog_name, app_name, app) { - Ok(res) => res, - Err(e) => { - // Happens if something went wrong with initialization - // or input exec path is invalid. Do nothing, just print an error - log::error!("action_wayvr failed: {}", e); - None + let wayvr = app.get_wayvr()?; + let mut wayvr = wayvr.borrow_mut(); + + if let Some(handle) = wayvr.get_display_by_name(display_name) { + if let Some(display) = wayvr.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); + } + } + } + } + } + } + + Ok(()) +} + +pub fn wayvr_action(app: &mut AppState, overlays: &mut OverlayContainer, action: &WayVRAction) +where + O: Default, +{ + match action { + WayVRAction::AppClick { + catalog_name, + app_name, + } => { + match action_app_click(app, 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); + } + } + } + WayVRAction::DisplayClick { + display_name, + action, + } => { + if let Err(e) = action_display_click::(app, overlays, display_name, action) { + log::error!("action_display_click failed: {}", e); + } } } } diff --git a/src/res/wayvr.yaml b/src/res/wayvr.yaml index 1ce69e0..4298445 100644 --- a/src/res/wayvr.yaml +++ b/src/res/wayvr.yaml @@ -6,19 +6,11 @@ version: 1 displays: - disp_square: - width: 500 - height: 350 - scale: 1.0 - disp_term: + Disp1: width: 640 height: 480 scale: 1.25 - disp_browser: - width: 1280 - height: 720 - scale: 2.0 - disp_plasma: + Disp2: width: 1280 height: 720 scale: 2.0 @@ -27,21 +19,17 @@ catalogs: default_catalog: apps: - name: "Calc" - target_display: "disp_square" + target_display: "Disp1" exec: "kcalc" env: ["FOO=bar"] - - name: "Terminal" - target_display: "disp_term" + - name: "htop" + target_display: "Disp1" exec: "konsole" + args: "-e htop" - name: "Browser" - target_display: "disp_browser" + target_display: "Disp2" exec: "cage" args: "chromium -- --incognito" - - - name: "Plasma" - target_display: "disp_plasma" - exec: "cage" - args: "dbus-run-session -- kwin_wayland --xwayland plasmashell"