wvr_server: scale windows based on resolution

This commit is contained in:
galister 2026-07-05 18:14:33 +09:00
parent 2d820f1d1b
commit b2037feacf
5 changed files with 173 additions and 38 deletions

View File

@ -424,12 +424,12 @@ impl WayVRClient {
pub async fn fn_wlx_handsfree( pub async fn fn_wlx_handsfree(
client: WayVRClientMutex, client: WayVRClientMutex,
params: HandsfreeParams params: HandsfreeParams,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
send_only!(client, &PacketClient::WlxHandsfree(params)); send_only!(client, &PacketClient::WlxHandsfree(params));
Ok(()) Ok(())
} }
pub async fn fn_wlx_modify_panel( pub async fn fn_wlx_modify_panel(
client: WayVRClientMutex, client: WayVRClientMutex,
params: packet_client::WlxModifyPanelParams, params: packet_client::WlxModifyPanelParams,

View File

@ -168,7 +168,7 @@ impl Application {
self.redraw_requests.remove(surface_id) self.redraw_requests.remove(surface_id)
} }
fn output_logical_size(&self) -> Size<i32, Logical> { pub fn output_logical_size(&self) -> Size<i32, Logical> {
self.output self.output
.current_mode() .current_mode()
.map(|mode| Size::new(mode.size.w, mode.size.h)) .map(|mode| Size::new(mode.size.w, mode.size.h))
@ -520,36 +520,60 @@ impl XdgShellHandler for Application {
surface.send_repositioned(token); 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( fn fullscreen_request(
&mut self, &mut self,
surface: ToplevelSurface, surface: ToplevelSurface,
_output: Option<wl_output::WlOutput>, _output: Option<wl_output::WlOutput>,
) { ) {
let size = self.output_logical_size();
surface.with_pending_state(|state| { surface.with_pending_state(|state| {
state.bounds = Some(size);
state.size = Some(size);
state.states.set(xdg_toplevel::State::Fullscreen); state.states.set(xdg_toplevel::State::Fullscreen);
}); });
surface.send_configure(); surface.send_configure();
} }
fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
surface.with_pending_state(|state| { // If the app requests maximize, make it fill the virtual output
state.states.unset(xdg_toplevel::State::Fullscreen);
});
surface.send_configure();
}
// If the app wants to be maximized, make it think that it's maximized.
fn maximize_request(&mut self, surface: ToplevelSurface) { fn maximize_request(&mut self, surface: ToplevelSurface) {
let size = self.output_logical_size();
surface.with_pending_state(|state| { surface.with_pending_state(|state| {
state.bounds = Some(size);
state.size = Some(size);
state.states.set(xdg_toplevel::State::Maximized); state.states.set(xdg_toplevel::State::Maximized);
}); });
surface.send_configure(); 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| { 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); state.states.unset(xdg_toplevel::State::Maximized);
}); });
surface.send_configure(); surface.send_configure();
} }
// If the app requests minimize, hide its window // If the app requests minimize, hide its window
fn minimize_request(&mut self, surface: ToplevelSurface) { fn minimize_request(&mut self, surface: ToplevelSurface) {
if let Some(client) = surface.wl_surface().client() { if let Some(client) = surface.wl_surface().client() {

View File

@ -343,28 +343,35 @@ impl WvrServerState {
continue; continue;
}; };
let output_bounds = wvr_server.manager.state.output_logical_size();
let (min_size, max_size) = with_states(toplevel.wl_surface(), |state| { let (min_size, max_size) = with_states(toplevel.wl_surface(), |state| {
let mut guard = state.cached_state.get::<SurfaceCachedState>(); let mut guard = state.cached_state.get::<SurfaceCachedState>();
let (mut min_size, mut max_size) = let current = guard.current();
{ (guard.current().min_size, guard.current().max_size) };
let mut min_size = current.min_size;
let mut max_size = current.max_size;
if min_size.is_empty() { if min_size.is_empty() {
min_size = Size::new(1, 1); min_size = Size::new(1, 1);
} }
if max_size.is_empty() { 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) (min_size, max_size)
}); });
// Size, icon & fallback title comes from process // 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) { match wvr_server.processes.get(&process_handle) {
Some(Process::Managed(p)) => { Some(Process::Managed(p)) => {
let size: Size<i32, Logical> = let size: Size<i32, Logical> =
Size::new(p.resolution[0] as _, p.resolution[1] as _); Size::new(p.resolution[0] as _, p.resolution[1] as _);
( (
size.clamp(min_size, max_size), size.clamp(min_size, max_size),
p.pos_mode, 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<str> = fallback_title let mut title: Arc<str> = fallback_title
.unwrap_or_else(|| format!("P{}", client.pid)) .unwrap_or_else(|| format!("P{}", client.pid))
.into(); .into();
@ -392,19 +414,6 @@ impl WvrServerState {
SpawnPos::Parent(OverlaySelector::Id(oid)) 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; let mut icon = icon;
// Try to get title from xdg_toplevel, unless it's running in cage // Try to get title from xdg_toplevel, unless it's running in cage
@ -456,7 +465,7 @@ impl WvrServerState {
app, app,
window_handle, window_handle,
icon, icon,
[size.w as _, size.h as _], [fallback_size.w as _, fallback_size.h as _],
pos, pos,
) )
.context("Could not create WvrWindow overlay") .context("Could not create WvrWindow overlay")

View File

@ -1,5 +1,6 @@
use std::rc::Rc; use std::rc::Rc;
use smithay::utils::{Logical, Size};
use smithay::wayland::shell::xdg::ToplevelSurface; use smithay::wayland::shell::xdg::ToplevelSurface;
use wayvr_ipc::packet_server; 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<Size<i32, Logical>>, bounds: Size<i32, Logical>) {
self.toplevel.with_pending_state(|state| { self.toplevel.with_pending_state(|state| {
//state.bounds = Some((size_x as i32, size_y as i32).into()); state.bounds = Some(bounds);
state.size = Some((size_x as i32, size_y as i32).into()); state.size = size;
}); });
self.toplevel.send_configure(); self.toplevel.send_configure();
if let Some(size) = size {
self.remember_committed_size(size);
}
}
pub fn request_size(&mut self, size: Size<i32, Logical>, bounds: Size<i32, Logical>) {
let size = size.clamp(Size::new(1, 1), bounds);
self.configure_size(Some(size), bounds);
}
pub fn remember_committed_size(&mut self, size: Size<i32, Logical>) -> 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_x = size_x;
self.size_y = size_y; self.size_y = size_y;
changed
} }
} }
@ -78,7 +97,7 @@ impl WindowManager {
size_y: u32, size_y: u32,
) -> WindowHandle { ) -> WindowHandle {
let mut window = Window::new(toplevel, process); 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) self.windows.add(window)
} }

View File

@ -1,8 +1,9 @@
use glam::{Affine2, Affine3A, Quat, Vec2, Vec3, vec2, vec3}; use glam::{Affine2, Affine3A, Quat, Vec2, Vec3, vec2, vec3};
use slotmap::Key;
use smithay::{ use smithay::{
desktop::PopupManager, desktop::PopupManager,
reexports::wayland_server::{Resource, backend::ObjectId, protocol::wl_surface::WlSurface}, reexports::wayland_server::{Resource, backend::ObjectId, protocol::wl_surface::WlSurface},
utils::{Logical, Point}, utils::{Logical, Point, Size},
wayland::{ wayland::{
compositor::{ compositor::{
SUBSURFACE_ROLE, SubsurfaceCachedState, SurfaceAttributes, TraversalAction, SUBSURFACE_ROLE, SubsurfaceCachedState, SurfaceAttributes, TraversalAction,
@ -37,6 +38,7 @@ use crate::{
backend::{ backend::{
XrBackend, XrBackend,
input::{self, HoverResult}, input::{self, HoverResult},
task::{OverlayTask, TaskType},
wayvr::{ wayvr::{
self, PointerFocusTarget, SurfaceBufWithImage, process::KillSignal, self, PointerFocusTarget, SurfaceBufWithImage, process::KillSignal,
window::WindowHandle, window::WindowHandle,
@ -51,6 +53,7 @@ use crate::{
state::{self, AppState}, state::{self, AppState},
subsystem::{hid::WheelDelta, input::KeyboardFocus}, subsystem::{hid::WheelDelta, input::KeyboardFocus},
windowing::{ windowing::{
OverlayID, OverlaySelector,
backend::{ backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender,
ui_transform, ui_transform,
@ -75,8 +78,7 @@ pub fn create_wl_window_overlay(
size: [u32; 2], size: [u32; 2],
pos_mode: PositionMode, pos_mode: PositionMode,
) -> anyhow::Result<OverlayWindowConfig> { ) -> anyhow::Result<OverlayWindowConfig> {
let scale = size[0].max(size[1]) as f32 / 1920.0; let (scale, curve_scale) = overlay_scale_from_extent(size);
let curve_scale = size[0] as f32 / 1920.0;
let z_dist = if matches!(pos_mode, PositionMode::Anchor) { let z_dist = if matches!(pos_mode, PositionMode::Anchor) {
0.0 0.0
@ -106,7 +108,11 @@ pub fn create_wl_window_overlay(
category: OverlayCategory::WayVR, category: OverlayCategory::WayVR,
show_on_spawn: true, show_on_spawn: true,
..OverlayWindowConfig::from_backend(Box::new(WvrWindowBackend::new( ..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<f32>, uv_range: RangeInclusive<f32>,
panel_hovered: bool, panel_hovered: bool,
scrolling: bool, scrolling: bool,
overlay_id: OverlayID,
scale: (f32, f32),
} }
impl WvrWindowBackend { impl WvrWindowBackend {
@ -175,6 +183,7 @@ impl WvrWindowBackend {
app: &mut AppState, app: &mut AppState,
window: wayvr::window::WindowHandle, window: wayvr::window::WindowHandle,
icon: Arc<str>, icon: Arc<str>,
scale: (f32, f32),
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let subsurface_pipeline = app.gfx.create_pipeline( let subsurface_pipeline = app.gfx.create_pipeline(
app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic
@ -274,10 +283,43 @@ impl WvrWindowBackend {
uv_range: 0.0..=1.0, uv_range: 0.0..=1.0,
panel_hovered: false, panel_hovered: false,
scrolling: false, scrolling: false,
overlay_id: OverlayID::null(),
scale,
}) })
} }
fn apply_extent(&mut self, app: &mut AppState, meta: &FrameMeta) -> anyhow::Result<()> { 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)); self.interaction_transform = Some(ui_transform(meta.extent));
let mut scale = vec2( let mut scale = vec2(
@ -441,6 +483,35 @@ impl WvrWindowBackend {
Ok(()) 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 { impl OverlayBackend for WvrWindowBackend {
@ -556,6 +627,8 @@ impl OverlayBackend for WvrWindowBackend {
} }
let inner_extent = meta.extent; let inner_extent = meta.extent;
self.sync_committed_toplevel_size(app, inner_extent)?;
meta.extent[0] += BORDER_SIZE * 2; meta.extent[0] += BORDER_SIZE * 2;
meta.extent[1] += BORDER_SIZE * 2 + BAR_SIZE; meta.extent[1] += BORDER_SIZE * 2 + BAR_SIZE;
@ -695,6 +768,7 @@ impl OverlayBackend for WvrWindowBackend {
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
match event_data { match event_data {
OverlayEventData::IdAssigned(oid) => { OverlayEventData::IdAssigned(oid) => {
self.overlay_id = oid;
let wvr_server = app.wvr_server.as_mut().unwrap(); //never None let wvr_server = app.wvr_server.as_mut().unwrap(); //never None
wvr_server.overlay_added(oid, self.window); wvr_server.overlay_added(oid, self.window);
} }
@ -1093,3 +1167,12 @@ fn surface_tree_input_hit_test(
hit 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)
}