From b2037feacf1fff39dbec45b624bbed519e408e9c Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Sun, 5 Jul 2026 18:14:33 +0900 Subject: [PATCH] wvr_server: scale windows based on resolution --- wayvr-ipc/src/client.rs | 4 +- wayvr/src/backend/wayvr/comp.rs | 44 +++++++++++---- wayvr/src/backend/wayvr/mod.rs | 45 +++++++++------ wayvr/src/backend/wayvr/window.rs | 27 +++++++-- wayvr/src/overlays/wayvr.rs | 91 +++++++++++++++++++++++++++++-- 5 files changed, 173 insertions(+), 38 deletions(-) diff --git a/wayvr-ipc/src/client.rs b/wayvr-ipc/src/client.rs index b6fcfd60..a1a6cb0c 100644 --- a/wayvr-ipc/src/client.rs +++ b/wayvr-ipc/src/client.rs @@ -424,12 +424,12 @@ impl WayVRClient { pub async fn fn_wlx_handsfree( client: WayVRClientMutex, - params: HandsfreeParams + params: HandsfreeParams, ) -> anyhow::Result<()> { send_only!(client, &PacketClient::WlxHandsfree(params)); Ok(()) } - + pub async fn fn_wlx_modify_panel( client: WayVRClientMutex, params: packet_client::WlxModifyPanelParams, diff --git a/wayvr/src/backend/wayvr/comp.rs b/wayvr/src/backend/wayvr/comp.rs index 016e4dda..41c80464 100644 --- a/wayvr/src/backend/wayvr/comp.rs +++ b/wayvr/src/backend/wayvr/comp.rs @@ -168,7 +168,7 @@ impl Application { self.redraw_requests.remove(surface_id) } - fn output_logical_size(&self) -> Size { + pub fn output_logical_size(&self) -> Size { self.output .current_mode() .map(|mode| Size::new(mode.size.w, mode.size.h)) @@ -520,36 +520,60 @@ impl XdgShellHandler for Application { surface.send_repositioned(token); } - // If the app wants to be fullscreen, make it think that it's fullscreen. + // If the app requests fullscreen, make it fill the virtual output fn fullscreen_request( &mut self, surface: ToplevelSurface, _output: Option, ) { + let size = self.output_logical_size(); + surface.with_pending_state(|state| { + state.bounds = Some(size); + state.size = Some(size); state.states.set(xdg_toplevel::State::Fullscreen); }); + surface.send_configure(); } - fn unfullscreen_request(&mut self, surface: ToplevelSurface) { - surface.with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Fullscreen); - }); - surface.send_configure(); - } - // If the app wants to be maximized, make it think that it's maximized. + + // If the app requests maximize, make it fill the virtual output fn maximize_request(&mut self, surface: ToplevelSurface) { + let size = self.output_logical_size(); + surface.with_pending_state(|state| { + state.bounds = Some(size); + state.size = Some(size); state.states.set(xdg_toplevel::State::Maximized); }); + surface.send_configure(); } - fn unmaximize_request(&mut self, surface: ToplevelSurface) { + + fn unfullscreen_request(&mut self, surface: ToplevelSurface) { + let bounds = self.output_logical_size(); + surface.with_pending_state(|state| { + state.bounds = Some(bounds); + state.size = None; + state.states.unset(xdg_toplevel::State::Fullscreen); + }); + + surface.send_configure(); + } + + fn unmaximize_request(&mut self, surface: ToplevelSurface) { + let bounds = self.output_logical_size(); + + surface.with_pending_state(|state| { + state.bounds = Some(bounds); + state.size = None; state.states.unset(xdg_toplevel::State::Maximized); }); + surface.send_configure(); } + // If the app requests minimize, hide its window fn minimize_request(&mut self, surface: ToplevelSurface) { if let Some(client) = surface.wl_surface().client() { diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index fb489f80..5364ddb5 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -343,28 +343,35 @@ impl WvrServerState { continue; }; + let output_bounds = wvr_server.manager.state.output_logical_size(); + let (min_size, max_size) = with_states(toplevel.wl_surface(), |state| { let mut guard = state.cached_state.get::(); - let (mut min_size, mut max_size) = - { (guard.current().min_size, guard.current().max_size) }; + let current = guard.current(); + + let mut min_size = current.min_size; + let mut max_size = current.max_size; if min_size.is_empty() { min_size = Size::new(1, 1); } if max_size.is_empty() { - max_size = Size::new(4096, 4096); + max_size = output_bounds; + } else { + max_size = max_size.clamp(Size::new(1, 1), output_bounds); } (min_size, max_size) }); // Size, icon & fallback title comes from process - let (size, pos, fallback_title, icon, is_cage) = + let (fallback_size, pos, fallback_title, icon, is_cage) = match wvr_server.processes.get(&process_handle) { Some(Process::Managed(p)) => { let size: Size = Size::new(p.resolution[0] as _, p.resolution[1] as _); + ( size.clamp(min_size, max_size), p.pos_mode, @@ -382,6 +389,21 @@ impl WvrServerState { ), }; + let window_handle = wvr_server.wm.create_window( + toplevel.clone(), + process_handle, + fallback_size.w as _, + fallback_size.h as _, + ); + + toplevel.with_pending_state(|state| { + state.bounds = Some(output_bounds); + // suggest an initial size but let the app request a different one + state.size = Some(fallback_size); + state.states.set(xdg_toplevel::State::Activated); + }); + toplevel.send_configure(); + let mut title: Arc = fallback_title .unwrap_or_else(|| format!("P{}", client.pid)) .into(); @@ -392,19 +414,6 @@ impl WvrServerState { SpawnPos::Parent(OverlaySelector::Id(oid)) }); - let window_handle = wvr_server.wm.create_window( - toplevel.clone(), - process_handle, - size.w as _, - size.h as _, - ); - toplevel.with_pending_state(|state| { - state.size = Some(size); - state.states.set(xdg_toplevel::State::Activated); - state.states.set(xdg_toplevel::State::Maximized); - }); - toplevel.send_configure(); - let mut icon = icon; // Try to get title from xdg_toplevel, unless it's running in cage @@ -456,7 +465,7 @@ impl WvrServerState { app, window_handle, icon, - [size.w as _, size.h as _], + [fallback_size.w as _, fallback_size.h as _], pos, ) .context("Could not create WvrWindow overlay") diff --git a/wayvr/src/backend/wayvr/window.rs b/wayvr/src/backend/wayvr/window.rs index dc40149c..c302304a 100644 --- a/wayvr/src/backend/wayvr/window.rs +++ b/wayvr/src/backend/wayvr/window.rs @@ -1,5 +1,6 @@ use std::rc::Rc; +use smithay::utils::{Logical, Size}; use smithay::wayland::shell::xdg::ToplevelSurface; use wayvr_ipc::packet_server; @@ -25,15 +26,33 @@ impl Window { } } - pub fn set_size(&mut self, size_x: u32, size_y: u32) { + pub fn configure_size(&mut self, size: Option>, bounds: Size) { self.toplevel.with_pending_state(|state| { - //state.bounds = Some((size_x as i32, size_y as i32).into()); - state.size = Some((size_x as i32, size_y as i32).into()); + state.bounds = Some(bounds); + state.size = size; }); self.toplevel.send_configure(); + if let Some(size) = size { + self.remember_committed_size(size); + } + } + + pub fn request_size(&mut self, size: Size, bounds: Size) { + let size = size.clamp(Size::new(1, 1), bounds); + self.configure_size(Some(size), bounds); + } + + pub fn remember_committed_size(&mut self, size: Size) -> bool { + let size_x = size.w.max(1) as u32; + let size_y = size.h.max(1) as u32; + + let changed = self.size_x != size_x || self.size_y != size_y; + self.size_x = size_x; self.size_y = size_y; + + changed } } @@ -78,7 +97,7 @@ impl WindowManager { size_y: u32, ) -> WindowHandle { let mut window = Window::new(toplevel, process); - window.set_size(size_x, size_y); + window.remember_committed_size(Size::new(size_x as i32, size_y as i32)); self.windows.add(window) } diff --git a/wayvr/src/overlays/wayvr.rs b/wayvr/src/overlays/wayvr.rs index 8ed7dd63..818a9afe 100644 --- a/wayvr/src/overlays/wayvr.rs +++ b/wayvr/src/overlays/wayvr.rs @@ -1,8 +1,9 @@ use glam::{Affine2, Affine3A, Quat, Vec2, Vec3, vec2, vec3}; +use slotmap::Key; use smithay::{ desktop::PopupManager, reexports::wayland_server::{Resource, backend::ObjectId, protocol::wl_surface::WlSurface}, - utils::{Logical, Point}, + utils::{Logical, Point, Size}, wayland::{ compositor::{ SUBSURFACE_ROLE, SubsurfaceCachedState, SurfaceAttributes, TraversalAction, @@ -37,6 +38,7 @@ use crate::{ backend::{ XrBackend, input::{self, HoverResult}, + task::{OverlayTask, TaskType}, wayvr::{ self, PointerFocusTarget, SurfaceBufWithImage, process::KillSignal, window::WindowHandle, @@ -51,6 +53,7 @@ use crate::{ state::{self, AppState}, subsystem::{hid::WheelDelta, input::KeyboardFocus}, windowing::{ + OverlayID, OverlaySelector, backend::{ FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, ui_transform, @@ -75,8 +78,7 @@ pub fn create_wl_window_overlay( size: [u32; 2], pos_mode: PositionMode, ) -> anyhow::Result { - let scale = size[0].max(size[1]) as f32 / 1920.0; - let curve_scale = size[0] as f32 / 1920.0; + let (scale, curve_scale) = overlay_scale_from_extent(size); let z_dist = if matches!(pos_mode, PositionMode::Anchor) { 0.0 @@ -106,7 +108,11 @@ pub fn create_wl_window_overlay( category: OverlayCategory::WayVR, show_on_spawn: true, ..OverlayWindowConfig::from_backend(Box::new(WvrWindowBackend::new( - name, app, window, icon, + name, + app, + window, + icon, + (scale, curve_scale), )?)) }) } @@ -167,6 +173,8 @@ pub struct WvrWindowBackend { uv_range: RangeInclusive, panel_hovered: bool, scrolling: bool, + overlay_id: OverlayID, + scale: (f32, f32), } impl WvrWindowBackend { @@ -175,6 +183,7 @@ impl WvrWindowBackend { app: &mut AppState, window: wayvr::window::WindowHandle, icon: Arc, + scale: (f32, f32), ) -> anyhow::Result { let subsurface_pipeline = app.gfx.create_pipeline( app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic @@ -274,10 +283,43 @@ impl WvrWindowBackend { uv_range: 0.0..=1.0, panel_hovered: false, scrolling: false, + overlay_id: OverlayID::null(), + scale, }) } fn apply_extent(&mut self, app: &mut AppState, meta: &FrameMeta) -> anyhow::Result<()> { + let (old_scale, old_curve_scale) = self.scale; + let (new_scale, curve_scale) = overlay_scale_from_extent(meta.extent); + self.scale = (new_scale, curve_scale); + + if new_scale.abs() > f32::EPSILON { + let scale_delta = new_scale / old_scale; + let curve_scale_delta = curve_scale / old_curve_scale; + + if (scale_delta - 1.0).abs() > f32::EPSILON + || (curve_scale_delta - 1.0).abs() > f32::EPSILON + { + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Id(self.overlay_id), + Box::new(move |_app, owc| { + if let Some(state) = owc.active_state.as_mut() { + if let Some(curvature) = state.curvature.as_mut() { + *curvature = (curve_scale_delta * *curvature).clamp(0.0, 0.5); + } + + if let Some(saved) = state.saved_transform.as_mut() { + saved.matrix3 = saved.matrix3.mul_scalar(scale_delta); + } + + state.transform.matrix3 = + state.transform.matrix3.mul_scalar(scale_delta); + } + }), + ))); + } + } + self.interaction_transform = Some(ui_transform(meta.extent)); let mut scale = vec2( @@ -441,6 +483,35 @@ impl WvrWindowBackend { Ok(()) } + + fn sync_committed_toplevel_size( + &mut self, + app: &mut AppState, + inner_extent: [u32; 2], + ) -> anyhow::Result<()> { + let Some(wvr_server) = app.wvr_server.as_mut() else { + return Ok(()); + }; + let bounds = wvr_server.manager.state.output_logical_size(); + let requested = Size::new(inner_extent[0].max(1) as i32, inner_extent[1].max(1) as i32); + let clamped = requested.clamp(Size::new(1, 1), bounds); + + let Some(window) = wvr_server.wm.windows.get_mut(&self.window) else { + return Ok(()); + }; + + if requested == clamped { + // client picked a new size that fits + window.remember_committed_size(requested); + } else { + // client committed something outside the output bounds + if window.size_x != clamped.w as u32 || window.size_y != clamped.h as u32 { + window.request_size(clamped, bounds); + } + } + + Ok(()) + } } impl OverlayBackend for WvrWindowBackend { @@ -556,6 +627,8 @@ impl OverlayBackend for WvrWindowBackend { } let inner_extent = meta.extent; + self.sync_committed_toplevel_size(app, inner_extent)?; + meta.extent[0] += BORDER_SIZE * 2; meta.extent[1] += BORDER_SIZE * 2 + BAR_SIZE; @@ -695,6 +768,7 @@ impl OverlayBackend for WvrWindowBackend { ) -> anyhow::Result<()> { match event_data { OverlayEventData::IdAssigned(oid) => { + self.overlay_id = oid; let wvr_server = app.wvr_server.as_mut().unwrap(); //never None wvr_server.overlay_added(oid, self.window); } @@ -1093,3 +1167,12 @@ fn surface_tree_input_hit_test( hit } + +fn overlay_scale_from_extent(size: [u32; 2]) -> (f32, f32) { + const RELATIVE_SIZE: f32 = 1440.0; // sqrt(1920 * 1080) + + let w = size[0].max(1) as f32; + let h = size[1].max(1) as f32; + + ((w * h).sqrt() / RELATIVE_SIZE, w / 1920.0) +}