Force buffers to be unscaled

Satellite will now force Xwayland to always render with the native
display resolution, and just scale surface sizes accordingly. As a result,
applications won't really respect DPI, but this can be adjusted through
the same means as with normal X11.

Part of #28.
This commit is contained in:
Shawn Wallace 2025-04-09 00:32:20 -04:00
parent 45c0556964
commit 8188df0e70
8 changed files with 636 additions and 244 deletions

View file

@ -67,6 +67,7 @@ pub fn main(data: impl RunData) -> Option<()> {
let mut xwayland = xwayland
.args([
"-rootless",
"-force-xrandr-emulation",
"-wm",
&xsock_xwl.as_raw_fd().to_string(),
"-displayfd",

View file

@ -23,7 +23,6 @@ use wayland_protocols::{
server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1 as RelativePointerManServer,
},
tablet::zv2::{client as c_tablet, server as s_tablet},
viewporter::{client as c_vp, server as s_vp},
},
xdg::xdg_output::zv1::{
client::zxdg_output_manager_v1::ZxdgOutputManagerV1 as OutputManClient,
@ -228,6 +227,7 @@ impl<C: XConnection>
state.objects.insert_with_key(|key| {
let client = client.create_surface(&state.qh, key);
let server = data_init.init(id, key);
let viewport = state.viewporter.get_viewport(&client, &state.qh, ());
surface_id = Some(server.id().protocol_id());
debug!("new surface with key {key:?} ({surface_id:?})");
@ -242,6 +242,8 @@ impl<C: XConnection>
xwl: None,
window: None,
output_key: None,
scale_factor: 1,
viewport,
}
.into()
});
@ -775,60 +777,6 @@ impl<C: XConnection> Dispatch<WlDrmServer, ObjectKey> for ServerState<C> {
}
}
impl<C: XConnection> Dispatch<s_vp::wp_viewport::WpViewport, c_vp::wp_viewport::WpViewport>
for ServerState<C>
{
fn request(
_: &mut Self,
_: &wayland_server::Client,
_: &s_vp::wp_viewport::WpViewport,
request: <s_vp::wp_viewport::WpViewport as Resource>::Request,
c_viewport: &c_vp::wp_viewport::WpViewport,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
simple_event_shunt! {
c_viewport, request: s_vp::wp_viewport::Request => [
SetSource { x, y, width, height },
SetDestination { width, height },
Destroy
]
}
}
}
impl<C: XConnection>
Dispatch<
s_vp::wp_viewporter::WpViewporter,
ClientGlobalWrapper<c_vp::wp_viewporter::WpViewporter>,
> for ServerState<C>
{
fn request(
state: &mut Self,
_: &wayland_server::Client,
_: &s_vp::wp_viewporter::WpViewporter,
request: <s_vp::wp_viewporter::WpViewporter as Resource>::Request,
client: &ClientGlobalWrapper<c_vp::wp_viewporter::WpViewporter>,
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
use s_vp::wp_viewporter;
match request {
wp_viewporter::Request::GetViewport { id, surface } => 'get_viewport: {
let Some(c_surface) = state.get_client_surface_from_server(surface) else {
break 'get_viewport;
};
let c_viewport = client.get_viewport(c_surface, &state.qh, ());
data_init.init(id, c_viewport);
}
wp_viewporter::Request::Destroy => {
client.destroy();
}
_ => unreachable!(),
}
}
}
impl<C: XConnection> Dispatch<XdgOutputServer, ObjectKey> for ServerState<C> {
fn request(
state: &mut Self,
@ -1255,10 +1203,6 @@ global_dispatch_no_events!(
c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1
);
global_dispatch_no_events!(OutputManServer, OutputManClient);
global_dispatch_no_events!(
s_vp::wp_viewporter::WpViewporter,
c_vp::wp_viewporter::WpViewporter
);
global_dispatch_no_events!(PointerConstraintsServer, PointerConstraintsClient);
global_dispatch_no_events!(
s_tablet::zwp_tablet_manager_v2::ZwpTabletManagerV2,

View file

@ -108,6 +108,34 @@ impl SurfaceData {
output_name
}
fn update_viewport(&self, dims: WindowDims, size_hints: Option<WmNormalHints>) {
let width = dims.width as i32 / self.scale_factor;
let height = dims.height as i32 / self.scale_factor;
self.viewport.set_destination(width, height);
debug!("{} viewport: {width}x{height}", self.server.id());
if let Some(hints) = size_hints {
let Some(SurfaceRole::Toplevel(Some(data))) = &self.role else {
warn!(
"Trying to update size hints on {}, but toplevel role data is missing",
self.server.id()
);
return;
};
if let Some(min) = hints.min_size {
data.toplevel.set_min_size(
min.width / self.scale_factor,
min.height / self.scale_factor,
);
}
if let Some(max) = hints.max_size {
data.toplevel.set_max_size(
max.width / self.scale_factor,
max.height / self.scale_factor,
);
}
}
}
fn surface_event<C: XConnection>(
&mut self,
event: client::wl_surface::Event,
@ -122,12 +150,14 @@ impl SurfaceData {
return;
};
let output: &mut Output = object.as_mut();
self.server.enter(&output.server);
self.scale_factor = output.scale;
self.output_key = Some(key);
debug!("{} entered {}", self.server.id(), output.server.id());
let windows = &mut state.windows;
if let Some(win_data) = self.window.as_ref().and_then(|win| windows.get_mut(win)) {
self.update_viewport(win_data.attrs.dims, win_data.attrs.size_hints);
win_data.update_output_offset(
key,
WindowOutputOffset {
@ -157,7 +187,7 @@ impl SurfaceData {
self.output_key = None;
}
}
Event::PreferredBufferScale { factor } => self.server.preferred_buffer_scale(factor),
Event::PreferredBufferScale { .. } => {}
other => warn!("unhandled surface request: {other:?}"),
}
}
@ -175,19 +205,23 @@ impl SurfaceData {
if let Some(pending) = xdg.pending.take() {
let window = state.associated_windows[self.key];
let window = state.windows.get_mut(&window).unwrap();
let x = pending.x + window.output_offset.x;
let y = pending.y + window.output_offset.y;
let x = pending.x * self.scale_factor + window.output_offset.x;
let y = pending.y * self.scale_factor + window.output_offset.y;
let width = if pending.width > 0 {
pending.width as u16
(pending.width * self.scale_factor) as u16
} else {
window.attrs.dims.width
};
let height = if pending.height > 0 {
pending.height as u16
(pending.height * self.scale_factor) as u16
} else {
window.attrs.dims.height
};
debug!("configuring {:?}: {x}x{y}, {width}x{height}", window.window);
debug!(
"configuring {} ({:?}): {x}x{y}, {width}x{height}",
self.server.id(),
window.window
);
connection.set_window_dims(
window.window,
PendingSurfaceState {
@ -203,6 +237,7 @@ impl SurfaceData {
width,
height,
};
self.update_viewport(window.attrs.dims, window.attrs.size_hints);
}
if let Some(SurfaceAttach { buffer, x, y }) = self.attach.take() {
@ -225,7 +260,10 @@ impl SurfaceData {
height,
states,
} => {
debug!("configuring toplevel {width}x{height}, {states:?}");
debug!(
"configuring toplevel {} {width}x{height}, {states:?}",
self.server.id()
);
if let Some(SurfaceRole::Toplevel(Some(toplevel))) = &mut self.role {
let prev_fs = toplevel.fullscreen;
toplevel.fullscreen =
@ -250,6 +288,9 @@ impl SurfaceData {
let window = state.associated_windows[self.key];
state.close_x_window(window);
}
// TODO: support capabilities (minimize, maximize, etc)
xdg_toplevel::Event::WmCapabilities { .. } => {}
xdg_toplevel::Event::ConfigureBounds { .. } => {}
ref other => warn!("unhandled xdgtoplevel event: {other:?}"),
}
}
@ -262,7 +303,10 @@ impl SurfaceData {
width,
height,
} => {
trace!("popup configure: {x}x{y}, {width}x{height}");
trace!(
"popup configure {}: {x}x{y}, {width}x{height}",
self.server.id()
);
self.xdg_mut().unwrap().pending = Some(PendingSurfaceState {
x,
y,
@ -350,6 +394,7 @@ pub struct Pointer {
server: WlPointer,
pub client: client::wl_pointer::WlPointer,
pending_enter: PendingEnter,
scale: i32,
}
impl Pointer {
@ -358,6 +403,7 @@ impl Pointer {
server,
client,
pending_enter: PendingEnter(None),
scale: 1,
}
}
}
@ -393,10 +439,15 @@ impl HandleEvent for Pointer {
break 'enter;
};
self.scale = surface_data.scale_factor;
let mut do_enter = || {
debug!("entering surface ({serial})");
self.server
.enter(serial, &surface_data.server, surface_x, surface_y);
debug!("pointer entering {} ({serial})", surface_data.server.id());
self.server.enter(
serial,
&surface_data.server,
surface_x * self.scale as f64,
surface_y * self.scale as f64,
);
let window = surface_data.window.unwrap();
state.connection.as_mut().unwrap().raise_to_top(window);
state.last_hovered = Some(window);
@ -473,7 +524,11 @@ impl HandleEvent for Pointer {
warn!("could not move pointer to surface ({serial}): stale surface");
}
} else {
self.server.motion(time, surface_x, surface_y);
self.server.motion(
time,
surface_x * self.scale as f64,
surface_y * self.scale as f64,
);
}
}
_ => simple_event_shunt! {
@ -715,6 +770,7 @@ pub struct Output {
windows: HashSet<x::Window>,
pub(super) dimensions: OutputDimensions,
name: String,
scale: i32,
}
impl Output {
@ -739,6 +795,7 @@ impl Output {
height: 0,
},
name: "<unknown>".to_string(),
scale: 1,
}
}
}
@ -873,72 +930,90 @@ impl Output {
event: client::wl_output::Event,
state: &mut ServerState<C>,
) {
if let client::wl_output::Event::Geometry {
x,
y,
physical_width,
physical_height,
subpixel,
make,
model,
transform,
} = &event
{
self.update_offset(
OutputDimensionsSource::Wl {
physical_width: *physical_width,
physical_height: *physical_height,
subpixel: *subpixel,
make: make.clone(),
model: model.clone(),
transform: *transform,
},
*x,
*y,
state,
);
}
if let client::wl_output::Event::Mode { width, height, .. } = event {
if matches!(self.dimensions.source, OutputDimensionsSource::Wl { .. }) {
self.dimensions.width = width;
self.dimensions.height = height;
debug!("{} dimensions: {width}x{height} (wl)", self.server.id());
}
}
simple_event_shunt! {
self.server, event: client::wl_output::Event => [
Name {
|name| {
self.name = name.clone();
name
}
},
Description { description },
Mode {
|flags| convert_wenum(flags),
width,
height,
refresh
},
Scale { factor },
Geometry {
|x| {
x - state.global_output_offset.x.value
},
|y| {
y - state.global_output_offset.y.value
use client::wl_output::Event;
match event {
Event::Geometry {
x,
y,
physical_width,
physical_height,
subpixel,
make,
model,
transform,
} => {
self.update_offset(
OutputDimensionsSource::Wl {
physical_width,
physical_height,
subpixel,
make: make.clone(),
model: model.clone(),
transform,
},
x,
y,
state,
);
self.server.geometry(
x - state.global_output_offset.x.value,
y - state.global_output_offset.y.value,
physical_width,
physical_height,
|subpixel| convert_wenum(subpixel),
convert_wenum(subpixel),
make,
model,
|transform| convert_wenum(transform)
},
Done
]
convert_wenum(transform),
);
}
Event::Mode {
flags,
width,
height,
refresh,
} => {
if matches!(self.dimensions.source, OutputDimensionsSource::Wl { .. }) {
self.dimensions.width = width;
self.dimensions.height = height;
debug!("{} dimensions: {width}x{height} (wl)", self.server.id());
}
self.server
.mode(convert_wenum(flags), width, height, refresh);
}
Event::Scale { factor } => {
debug!("{} scale: {factor}", self.server.id());
self.scale = factor;
self.windows.retain(|window| {
let Some(data): Option<&WindowData> = state.windows.get(window) else {
return false;
};
if let Some::<&mut SurfaceData>(surface) = data
.surface_key
.and_then(|key| state.objects.get_mut(key))
.map(AsMut::as_mut)
{
surface.scale_factor = factor;
surface.update_viewport(data.attrs.dims, data.attrs.size_hints);
}
true
});
self.server.scale(factor);
}
_ => simple_event_shunt! {
self.server, event: Event => [
Name {
|name| {
self.name = name.clone();
name
}
},
Description { description },
Done
]
},
}
}
@ -947,31 +1022,27 @@ impl Output {
event: zxdg_output_v1::Event,
state: &mut ServerState<C>,
) {
if let zxdg_output_v1::Event::LogicalPosition { x, y } = event {
self.update_offset(OutputDimensionsSource::Xdg, x, y, state);
}
use zxdg_output_v1::Event;
let xdg = &self.xdg.as_ref().unwrap().server;
if let zxdg_output_v1::Event::LogicalSize { width, height } = event {
self.dimensions.source = OutputDimensionsSource::Xdg;
self.dimensions.width = width;
self.dimensions.height = height;
debug!("{} dimensions: {width}x{height} (xdg)", self.server.id());
}
simple_event_shunt! {
xdg, event: zxdg_output_v1::Event => [
LogicalPosition {
|x| {
x - state.global_output_offset.x.value
},
|y| {
y - state.global_output_offset.y.value
}
},
LogicalSize { width, height },
Done,
Name { name },
Description { description }
]
match event {
Event::LogicalPosition { x, y } => {
self.update_offset(OutputDimensionsSource::Xdg, x, y, state);
self.xdg.as_ref().unwrap().server.logical_position(
x - state.global_output_offset.x.value,
y - state.global_output_offset.y.value,
);
}
Event::LogicalSize { .. } => {
xdg.logical_size(self.dimensions.width, self.dimensions.height);
}
_ => simple_event_shunt! {
xdg, event: zxdg_output_v1::Event => [
Done,
Name { name },
Description { description }
]
},
}
}
}

View file

@ -32,7 +32,7 @@ use wayland_protocols::{
pointer_constraints::zv1::server::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
relative_pointer::zv1::server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
tablet::zv2::server::zwp_tablet_manager_v2::ZwpTabletManagerV2,
viewporter::server as s_vp,
viewporter::client::{wp_viewport::WpViewport, wp_viewporter::WpViewporter},
},
xdg::{
shell::client::{
@ -188,6 +188,8 @@ pub struct SurfaceData {
xwl: Option<XwaylandSurfaceV1>,
window: Option<x::Window>,
output_key: Option<ObjectKey>,
scale_factor: i32,
viewport: WpViewport,
}
impl SurfaceData {
@ -467,7 +469,6 @@ fn handle_globals<'a, C: XConnection>(
WlDrmServer,
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
ZxdgOutputManagerV1,
s_vp::wp_viewporter::WpViewporter,
ZwpPointerConstraintsV1,
ZwpTabletManagerV2
];
@ -513,6 +514,7 @@ pub struct ServerState<C: XConnection> {
pub connection: Option<C>,
xdg_wm_base: XdgWmBase,
viewporter: WpViewporter,
clipboard_data: Option<ClipboardData<C::X11Selection>>,
last_kb_serial: Option<(client::wl_seat::WlSeat, u32)>,
activation_state: Option<ActivationState>,
@ -535,6 +537,11 @@ impl<C: XConnection> ServerState<C> {
warn!("xdg_wm_base version 2 detected. Popup repositioning will not work, and some popups may not work correctly.");
}
let viewporter = clientside
.global_list
.bind::<WpViewporter, _, _>(&qh, 1..=1, ())
.expect("Could not bind wp_viewporter");
let manager = DataDeviceManagerState::bind(&clientside.global_list, &qh)
.inspect_err(|e| {
warn!("Could not bind data device manager ({e:?}). Clipboard will not work.")
@ -582,6 +589,7 @@ impl<C: XConnection> ServerState<C> {
output_keys: Default::default(),
associated_windows: Default::default(),
xdg_wm_base,
viewporter,
clipboard_data,
last_kb_serial: None,
activation_state,
@ -722,10 +730,16 @@ impl<C: XConnection> ServerState<C> {
let surface: &SurfaceData = object.as_ref();
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
if let Some(min_size) = &hints.min_size {
data.toplevel.set_min_size(min_size.width, min_size.height);
data.toplevel.set_min_size(
min_size.width / surface.scale_factor,
min_size.height / surface.scale_factor,
);
}
if let Some(max_size) = &hints.max_size {
data.toplevel.set_max_size(max_size.width, max_size.height);
data.toplevel.set_max_size(
max_size.width / surface.scale_factor,
max_size.height / surface.scale_factor,
);
}
}
} else {
@ -817,12 +831,13 @@ impl<C: XConnection> ServerState<C> {
match &data.role {
Some(SurfaceRole::Popup(Some(popup))) => {
popup.positioner.set_offset(
event.x() as i32 - win.output_offset.x,
event.y() as i32 - win.output_offset.y,
(event.x() as i32 - win.output_offset.x) / data.scale_factor,
(event.y() as i32 - win.output_offset.y) / data.scale_factor,
);
popup.positioner.set_size(
event.width() as i32 / data.scale_factor,
event.height() as i32 / data.scale_factor,
);
popup
.positioner
.set_size(event.width().into(), event.height().into());
popup.popup.reposition(&popup.positioner, 0);
}
other => warn!("Non popup ({other:?}) being reconfigured, behavior may be off."),
@ -1169,30 +1184,34 @@ impl<C: XConnection> ServerState<C> {
self.objects[surface_key].0 = Some(surface.into());
let window = self.windows.get(&window).unwrap();
let initial_scale;
let role = if let Some(parent) = window.attrs.popup_for {
debug!(
"creating popup ({:?}) {:?} {:?} {:?} {surface_key:?}",
window.window, parent, window.attrs.dims, surface_id
);
let parent_window = self.windows.get(&parent).unwrap();
let parent_surface: &SurfaceData =
self.objects[parent_window.surface_key.unwrap()].as_ref();
let parent_dims = parent_window.attrs.dims;
let x = window.attrs.dims.x - parent_dims.x;
let y = window.attrs.dims.y - parent_dims.y;
initial_scale = parent_surface.scale_factor;
debug!(
"creating popup ({:?}) {:?} {:?} {:?} {surface_key:?} (scale: {initial_scale})",
window.window, parent, window.attrs.dims, surface_id
);
let positioner = self.xdg_wm_base.create_positioner(&self.qh, ());
positioner.set_size(window.attrs.dims.width as _, window.attrs.dims.height as _);
positioner.set_offset(x as i32, y as i32);
positioner.set_size(
1.max(window.attrs.dims.width as i32 / initial_scale),
1.max(window.attrs.dims.height as i32 / initial_scale),
);
let x = (window.attrs.dims.x - parent_dims.x) as i32 / initial_scale;
let y = (window.attrs.dims.y - parent_dims.y) as i32 / initial_scale;
positioner.set_offset(x, y);
positioner.set_anchor(Anchor::TopLeft);
positioner.set_gravity(Gravity::BottomRight);
positioner.set_anchor_rect(
0,
0,
parent_window.attrs.dims.width as _,
parent_window.attrs.dims.height as _,
parent_window.attrs.dims.width as i32 / initial_scale,
parent_window.attrs.dims.height as i32 / initial_scale,
);
let popup = xdg_surface.get_popup(
Some(&parent_surface.xdg().unwrap().surface),
@ -1211,11 +1230,13 @@ impl<C: XConnection> ServerState<C> {
};
SurfaceRole::Popup(Some(popup))
} else {
initial_scale = 1;
let data = self.create_toplevel(window, surface_key, xdg_surface, fullscreen);
SurfaceRole::Toplevel(Some(data))
};
let surface: &mut SurfaceData = self.objects[surface_key].as_mut();
surface.scale_factor = initial_scale;
let new_role_type = std::mem::discriminant(&role);
let prev = surface.role.replace(role);

View file

@ -1,5 +1,5 @@
use super::{ServerState, WindowDims};
use crate::xstate::{SetState, WmName};
use crate::xstate::{SetState, WinSize, WmName};
use rustix::event::{poll, PollFd, PollFlags};
use std::collections::HashMap;
use std::io::Write;
@ -44,7 +44,6 @@ use wayland_protocols::{
zwp_tablet_tool_v2::{self, ZwpTabletToolV2},
zwp_tablet_v2::{self, ZwpTabletV2},
},
viewporter::client::wp_viewporter::WpViewporter,
},
xdg::{
shell::server::{xdg_positioner, xdg_toplevel},
@ -157,11 +156,18 @@ struct FakeXConnection {
impl FakeXConnection {
#[track_caller]
fn window(&mut self, window: Window) -> &mut WindowData {
fn window_mut(&mut self, window: Window) -> &mut WindowData {
self.windows
.get_mut(&window)
.unwrap_or_else(|| panic!("Unknown window: {window:?}"))
}
#[track_caller]
fn window(&self, window: Window) -> &WindowData {
self.windows
.get(&window)
.unwrap_or_else(|| panic!("Unknown window: {window:?}"))
}
}
impl Default for FakeXConnection {
@ -204,17 +210,17 @@ impl super::XConnection for FakeXConnection {
#[track_caller]
fn close_window(&mut self, window: Window) {
log::debug!("closing window {window:?}");
self.window(window).mapped = false;
self.window_mut(window).mapped = false;
}
#[track_caller]
fn set_fullscreen(&mut self, window: xcb::x::Window, fullscreen: bool) {
self.window(window).fullscreen = fullscreen;
self.window_mut(window).fullscreen = fullscreen;
}
#[track_caller]
fn set_window_dims(&mut self, window: Window, state: super::PendingSurfaceState) {
self.window(window).dims = WindowDims {
self.window_mut(window).dims = WindowDims {
x: state.x as _,
y: state.y as _,
width: state.width as _,
@ -258,6 +264,63 @@ struct TestFixture {
static INIT: std::sync::Once = std::sync::Once::new();
struct PopupBuilder {
window: Window,
parent_window: Window,
parent_surface: testwl::SurfaceId,
dims: WindowDims,
scale: i32,
check_size_and_pos: bool,
}
impl PopupBuilder {
fn new(window: Window, parent_window: Window, parent_surface: testwl::SurfaceId) -> Self {
Self {
window,
parent_window,
parent_surface,
dims: WindowDims {
x: 0,
y: 0,
width: 50,
height: 50,
},
scale: 1,
check_size_and_pos: true,
}
}
fn x(mut self, x: i16) -> Self {
self.dims.x = x;
self
}
fn y(mut self, y: i16) -> Self {
self.dims.y = y;
self
}
fn width(mut self, width: u16) -> Self {
self.dims.width = width;
self
}
fn height(mut self, height: u16) -> Self {
self.dims.height = height;
self
}
fn check_size_and_pos(mut self, check: bool) -> Self {
self.check_size_and_pos = check;
self
}
fn scale(mut self, scale: i32) -> Self {
self.scale = scale;
self
}
}
impl TestFixture {
fn new() -> Self {
INIT.call_once(|| {
@ -639,24 +702,23 @@ impl TestFixture {
fn create_popup(
&mut self,
comp: &Compositor,
window: Window,
parent_window: Window,
parent_surface: testwl::SurfaceId,
x: i16,
y: i16,
builder: PopupBuilder,
) -> (TestObject<WlSurface>, testwl::SurfaceId) {
let (buffer, surface) = comp.create_surface();
let PopupBuilder {
window,
parent_window,
parent_surface,
dims,
scale,
check_size_and_pos,
} = builder;
let data = WindowData {
mapped: true,
dims: WindowDims {
x,
y,
width: 50,
height: 50,
},
dims,
fullscreen: false,
};
let dims = data.dims;
self.new_window(window, true, data, None);
self.map_window(comp, window, &surface.obj, &buffer);
self.run();
@ -689,26 +751,34 @@ impl TestFixture {
assert_eq!(&surface_data.popup().parent, toplevel_xdg);
let pos = &surface_data.popup().positioner_state;
assert_eq!(pos.size.as_ref().unwrap(), &testwl::Vec2 { x: 50, y: 50 });
if check_size_and_pos {
assert_eq!(
pos.size.as_ref().unwrap(),
&testwl::Vec2 {
x: 50 / scale,
y: 50 / scale
}
);
let parent_win = &self.connection().windows[&parent_window];
assert_eq!(
pos.anchor_rect.as_ref().unwrap(),
&testwl::Rect {
size: testwl::Vec2 {
x: parent_win.dims.width as _,
y: parent_win.dims.height as _
},
offset: testwl::Vec2::default()
}
);
assert_eq!(
pos.offset,
testwl::Vec2 {
x: (dims.x - parent_win.dims.x) as _,
y: (dims.y - parent_win.dims.y) as _
}
);
let parent_win = &self.connection().windows[&parent_window];
assert_eq!(
pos.anchor_rect.as_ref().unwrap(),
&testwl::Rect {
size: testwl::Vec2 {
x: parent_win.dims.width as i32 / scale,
y: parent_win.dims.height as i32 / scale
},
offset: testwl::Vec2::default()
}
);
assert_eq!(
pos.offset,
testwl::Vec2 {
x: (dims.x - parent_win.dims.x) as i32 / scale,
y: (dims.y - parent_win.dims.y) as i32 / scale
}
);
}
assert_eq!(pos.anchor, xdg_positioner::Anchor::TopLeft);
assert_eq!(pos.gravity, xdg_positioner::Gravity::BottomRight);
}
@ -850,8 +920,10 @@ fn popup_flow_simple() {
let (_, toplevel_id) = f.create_toplevel(&compositor, win_toplevel);
let win_popup = unsafe { Window::new(2) };
let (popup_surface, popup_id) =
f.create_popup(&compositor, win_popup, win_toplevel, toplevel_id, 10, 10);
let (popup_surface, popup_id) = f.create_popup(
&compositor,
PopupBuilder::new(win_popup, win_toplevel, toplevel_id),
);
f.satellite.unmap_window(win_popup);
f.satellite.destroy_window(win_popup);
@ -910,7 +982,6 @@ fn pass_through_globals() {
ZwpLinuxDmabufV1,
ZwpRelativePointerManagerV1,
ZxdgOutputManagerV1,
WpViewporter,
WlDrm,
ZwpPointerConstraintsV1,
XwaylandShellV1,
@ -968,7 +1039,7 @@ fn popup_window_changes_surface() {
let (_, toplevel_id) = f.create_toplevel(&comp, t_win);
let win = unsafe { Window::new(2) };
let (surface, old_id) = f.create_popup(&comp, win, t_win, toplevel_id, 0, 0);
let (surface, old_id) = f.create_popup(&comp, PopupBuilder::new(win, t_win, toplevel_id));
f.satellite.unmap_window(win);
surface.obj.destroy();
@ -1328,7 +1399,19 @@ fn override_redirect_choose_hover_window() {
let win3 = unsafe { Window::new(3) };
let (buffer, surface) = comp.create_surface();
f.new_window(win3, true, WindowData::default(), None);
f.new_window(
win3,
true,
WindowData {
dims: WindowDims {
width: 1,
height: 1,
..Default::default()
},
..Default::default()
},
None,
);
f.map_window(&comp, win3, &surface.obj, &buffer);
f.run();
let id3 = f.check_new_surface();
@ -1385,7 +1468,8 @@ fn output_offset() {
}
let popup = unsafe { Window::new(2) };
let (p_surface, p_id) = f.create_popup(&comp, popup, window, t_id, 510, 110);
let (p_surface, p_id) =
f.create_popup(&comp, PopupBuilder::new(popup, window, t_id).x(510).y(110));
f.testwl.move_surface_to_output(p_id, &output);
f.run();
let data = f.testwl.get_surface_data(p_id).unwrap();
@ -1456,7 +1540,7 @@ fn reposition_popup() {
let (_, t_id) = f.create_toplevel(&comp, toplevel);
let popup = unsafe { Window::new(2) };
let (_, p_id) = f.create_popup(&comp, popup, toplevel, t_id, 20, 40);
let (_, p_id) = f.create_popup(&comp, PopupBuilder::new(popup, toplevel, t_id).x(20).y(40));
f.satellite.reconfigure_window(x::ConfigureNotifyEvent::new(
popup,
@ -1839,6 +1923,143 @@ fn negative_output_position_remove_offset() {
check_output_position_event(&c_output, 500, 500);
}
#[test]
fn scaled_output_popup() {
let (mut f, comp) = TestFixture::new_with_compositor();
let (_, output) = f.new_output(0, 0);
let scale = 2;
output.scale(scale);
output.done();
f.run();
f.run();
let toplevel = unsafe { Window::new(1) };
let (_, toplevel_id) = f.create_toplevel(&comp, toplevel);
f.testwl.move_surface_to_output(toplevel_id, &output);
f.run();
let popup = unsafe { Window::new(2) };
let builder = PopupBuilder::new(popup, toplevel, toplevel_id)
.x(50)
.y(50)
.scale(scale);
let initial_dims = builder.dims;
let (_, popup_id) = f.create_popup(&comp, builder);
f.testwl.move_surface_to_output(popup_id, &output);
f.run();
assert_eq!(
initial_dims,
f.connection().window(popup).dims,
"X11 dimensions changed after configure"
);
}
#[test]
fn scaled_output_small_popup() {
let (mut f, comp) = TestFixture::new_with_compositor();
let (_, output) = f.new_output(0, 0);
output.scale(2);
output.done();
f.run();
f.run();
let toplevel = unsafe { Window::new(1) };
let (_, toplevel_id) = f.create_toplevel(&comp, toplevel);
f.testwl.move_surface_to_output(toplevel_id, &output);
f.run();
let popup = unsafe { Window::new(2) };
let builder = PopupBuilder::new(popup, toplevel, toplevel_id)
.x(50)
.y(50)
.width(1)
.height(1)
.scale(2)
.check_size_and_pos(false);
let (_, popup_id) = f.create_popup(&comp, builder);
f.testwl.move_surface_to_output(popup_id, &output);
f.run();
let dims = f.connection().window(popup).dims;
assert!(dims.width > 0);
assert!(dims.height > 0);
}
#[test]
fn toplevel_size_limits_scaled() {
let (mut f, comp) = TestFixture::new_with_compositor();
let (_, output) = f.new_output(0, 0);
output.scale(2);
output.done();
f.run();
f.run();
let window = unsafe { Window::new(1) };
let (buffer, surface) = comp.create_surface();
let data = WindowData {
mapped: true,
dims: WindowDims {
width: 50,
height: 50,
..Default::default()
},
fullscreen: false,
};
f.new_window(window, false, data, None);
f.satellite.set_size_hints(
window,
super::WmNormalHints {
min_size: Some(WinSize {
width: 20,
height: 20,
}),
max_size: Some(WinSize {
width: 100,
height: 100,
}),
},
);
f.map_window(&comp, window, &surface.obj, &buffer);
f.run();
let id = f.check_new_surface();
f.testwl.configure_toplevel(id, 50, 50, vec![]);
f.run();
f.testwl.move_surface_to_output(id, &output);
f.run();
let data = f.testwl.get_surface_data(id).unwrap();
let toplevel = data.toplevel();
assert_eq!(toplevel.min_size, Some(testwl::Vec2 { x: 10, y: 10 }));
assert_eq!(toplevel.max_size, Some(testwl::Vec2 { x: 50, y: 50 }));
f.satellite.set_size_hints(
window,
super::WmNormalHints {
min_size: Some(WinSize {
width: 40,
height: 40,
}),
max_size: Some(WinSize {
width: 200,
height: 200,
}),
},
);
f.run();
let data = f.testwl.get_surface_data(id).unwrap();
let toplevel = data.toplevel();
assert_eq!(toplevel.min_size, Some(testwl::Vec2 { x: 20, y: 20 }));
assert_eq!(toplevel.max_size, Some(testwl::Vec2 { x: 100, y: 100 }));
}
/// See Pointer::handle_event for an explanation.
#[test]
fn popup_pointer_motion_workaround() {}

View file

@ -842,13 +842,13 @@ bitflags! {
}
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct WinSize {
pub width: i32,
pub height: i32,
}
#[derive(Default, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
pub struct WmNormalHints {
pub min_size: Option<WinSize>,
pub max_size: Option<WinSize>,