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 let mut xwayland = xwayland
.args([ .args([
"-rootless", "-rootless",
"-force-xrandr-emulation",
"-wm", "-wm",
&xsock_xwl.as_raw_fd().to_string(), &xsock_xwl.as_raw_fd().to_string(),
"-displayfd", "-displayfd",

View file

@ -23,7 +23,6 @@ use wayland_protocols::{
server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1 as RelativePointerManServer, server::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1 as RelativePointerManServer,
}, },
tablet::zv2::{client as c_tablet, server as s_tablet}, tablet::zv2::{client as c_tablet, server as s_tablet},
viewporter::{client as c_vp, server as s_vp},
}, },
xdg::xdg_output::zv1::{ xdg::xdg_output::zv1::{
client::zxdg_output_manager_v1::ZxdgOutputManagerV1 as OutputManClient, client::zxdg_output_manager_v1::ZxdgOutputManagerV1 as OutputManClient,
@ -228,6 +227,7 @@ impl<C: XConnection>
state.objects.insert_with_key(|key| { state.objects.insert_with_key(|key| {
let client = client.create_surface(&state.qh, key); let client = client.create_surface(&state.qh, key);
let server = data_init.init(id, key); let server = data_init.init(id, key);
let viewport = state.viewporter.get_viewport(&client, &state.qh, ());
surface_id = Some(server.id().protocol_id()); surface_id = Some(server.id().protocol_id());
debug!("new surface with key {key:?} ({surface_id:?})"); debug!("new surface with key {key:?} ({surface_id:?})");
@ -242,6 +242,8 @@ impl<C: XConnection>
xwl: None, xwl: None,
window: None, window: None,
output_key: None, output_key: None,
scale_factor: 1,
viewport,
} }
.into() .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> { impl<C: XConnection> Dispatch<XdgOutputServer, ObjectKey> for ServerState<C> {
fn request( fn request(
state: &mut Self, state: &mut Self,
@ -1255,10 +1203,6 @@ global_dispatch_no_events!(
c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1 c_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1
); );
global_dispatch_no_events!(OutputManServer, OutputManClient); 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!(PointerConstraintsServer, PointerConstraintsClient);
global_dispatch_no_events!( global_dispatch_no_events!(
s_tablet::zwp_tablet_manager_v2::ZwpTabletManagerV2, s_tablet::zwp_tablet_manager_v2::ZwpTabletManagerV2,

View file

@ -108,6 +108,34 @@ impl SurfaceData {
output_name 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>( fn surface_event<C: XConnection>(
&mut self, &mut self,
event: client::wl_surface::Event, event: client::wl_surface::Event,
@ -122,12 +150,14 @@ impl SurfaceData {
return; return;
}; };
let output: &mut Output = object.as_mut(); let output: &mut Output = object.as_mut();
self.server.enter(&output.server); self.server.enter(&output.server);
self.scale_factor = output.scale;
self.output_key = Some(key); self.output_key = Some(key);
debug!("{} entered {}", self.server.id(), output.server.id()); debug!("{} entered {}", self.server.id(), output.server.id());
let windows = &mut state.windows; let windows = &mut state.windows;
if let Some(win_data) = self.window.as_ref().and_then(|win| windows.get_mut(win)) { 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( win_data.update_output_offset(
key, key,
WindowOutputOffset { WindowOutputOffset {
@ -157,7 +187,7 @@ impl SurfaceData {
self.output_key = None; self.output_key = None;
} }
} }
Event::PreferredBufferScale { factor } => self.server.preferred_buffer_scale(factor), Event::PreferredBufferScale { .. } => {}
other => warn!("unhandled surface request: {other:?}"), other => warn!("unhandled surface request: {other:?}"),
} }
} }
@ -175,19 +205,23 @@ impl SurfaceData {
if let Some(pending) = xdg.pending.take() { if let Some(pending) = xdg.pending.take() {
let window = state.associated_windows[self.key]; let window = state.associated_windows[self.key];
let window = state.windows.get_mut(&window).unwrap(); let window = state.windows.get_mut(&window).unwrap();
let x = pending.x + window.output_offset.x; let x = pending.x * self.scale_factor + window.output_offset.x;
let y = pending.y + window.output_offset.y; let y = pending.y * self.scale_factor + window.output_offset.y;
let width = if pending.width > 0 { let width = if pending.width > 0 {
pending.width as u16 (pending.width * self.scale_factor) as u16
} else { } else {
window.attrs.dims.width window.attrs.dims.width
}; };
let height = if pending.height > 0 { let height = if pending.height > 0 {
pending.height as u16 (pending.height * self.scale_factor) as u16
} else { } else {
window.attrs.dims.height 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( connection.set_window_dims(
window.window, window.window,
PendingSurfaceState { PendingSurfaceState {
@ -203,6 +237,7 @@ impl SurfaceData {
width, width,
height, height,
}; };
self.update_viewport(window.attrs.dims, window.attrs.size_hints);
} }
if let Some(SurfaceAttach { buffer, x, y }) = self.attach.take() { if let Some(SurfaceAttach { buffer, x, y }) = self.attach.take() {
@ -225,7 +260,10 @@ impl SurfaceData {
height, height,
states, 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 { if let Some(SurfaceRole::Toplevel(Some(toplevel))) = &mut self.role {
let prev_fs = toplevel.fullscreen; let prev_fs = toplevel.fullscreen;
toplevel.fullscreen = toplevel.fullscreen =
@ -250,6 +288,9 @@ impl SurfaceData {
let window = state.associated_windows[self.key]; let window = state.associated_windows[self.key];
state.close_x_window(window); 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:?}"), ref other => warn!("unhandled xdgtoplevel event: {other:?}"),
} }
} }
@ -262,7 +303,10 @@ impl SurfaceData {
width, width,
height, 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 { self.xdg_mut().unwrap().pending = Some(PendingSurfaceState {
x, x,
y, y,
@ -350,6 +394,7 @@ pub struct Pointer {
server: WlPointer, server: WlPointer,
pub client: client::wl_pointer::WlPointer, pub client: client::wl_pointer::WlPointer,
pending_enter: PendingEnter, pending_enter: PendingEnter,
scale: i32,
} }
impl Pointer { impl Pointer {
@ -358,6 +403,7 @@ impl Pointer {
server, server,
client, client,
pending_enter: PendingEnter(None), pending_enter: PendingEnter(None),
scale: 1,
} }
} }
} }
@ -393,10 +439,15 @@ impl HandleEvent for Pointer {
break 'enter; break 'enter;
}; };
self.scale = surface_data.scale_factor;
let mut do_enter = || { let mut do_enter = || {
debug!("entering surface ({serial})"); debug!("pointer entering {} ({serial})", surface_data.server.id());
self.server self.server.enter(
.enter(serial, &surface_data.server, surface_x, surface_y); serial,
&surface_data.server,
surface_x * self.scale as f64,
surface_y * self.scale as f64,
);
let window = surface_data.window.unwrap(); let window = surface_data.window.unwrap();
state.connection.as_mut().unwrap().raise_to_top(window); state.connection.as_mut().unwrap().raise_to_top(window);
state.last_hovered = Some(window); state.last_hovered = Some(window);
@ -473,7 +524,11 @@ impl HandleEvent for Pointer {
warn!("could not move pointer to surface ({serial}): stale surface"); warn!("could not move pointer to surface ({serial}): stale surface");
} }
} else { } 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! { _ => simple_event_shunt! {
@ -715,6 +770,7 @@ pub struct Output {
windows: HashSet<x::Window>, windows: HashSet<x::Window>,
pub(super) dimensions: OutputDimensions, pub(super) dimensions: OutputDimensions,
name: String, name: String,
scale: i32,
} }
impl Output { impl Output {
@ -739,6 +795,7 @@ impl Output {
height: 0, height: 0,
}, },
name: "<unknown>".to_string(), name: "<unknown>".to_string(),
scale: 1,
} }
} }
} }
@ -873,72 +930,90 @@ impl Output {
event: client::wl_output::Event, event: client::wl_output::Event,
state: &mut ServerState<C>, state: &mut ServerState<C>,
) { ) {
if let client::wl_output::Event::Geometry { use client::wl_output::Event;
x, match event {
y, Event::Geometry {
physical_width, x,
physical_height, y,
subpixel, physical_width,
make, physical_height,
model, subpixel,
transform, make,
} = &event model,
{ transform,
self.update_offset( } => {
OutputDimensionsSource::Wl { self.update_offset(
physical_width: *physical_width, OutputDimensionsSource::Wl {
physical_height: *physical_height, physical_width,
subpixel: *subpixel, physical_height,
make: make.clone(), subpixel,
model: model.clone(), make: make.clone(),
transform: *transform, model: model.clone(),
}, 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
}, },
x,
y,
state,
);
self.server.geometry(
x - state.global_output_offset.x.value,
y - state.global_output_offset.y.value,
physical_width, physical_width,
physical_height, physical_height,
|subpixel| convert_wenum(subpixel), convert_wenum(subpixel),
make, make,
model, model,
|transform| convert_wenum(transform) convert_wenum(transform),
}, );
Done }
] 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, event: zxdg_output_v1::Event,
state: &mut ServerState<C>, state: &mut ServerState<C>,
) { ) {
if let zxdg_output_v1::Event::LogicalPosition { x, y } = event { use zxdg_output_v1::Event;
self.update_offset(OutputDimensionsSource::Xdg, x, y, state);
}
let xdg = &self.xdg.as_ref().unwrap().server; let xdg = &self.xdg.as_ref().unwrap().server;
if let zxdg_output_v1::Event::LogicalSize { width, height } = event { match event {
self.dimensions.source = OutputDimensionsSource::Xdg; Event::LogicalPosition { x, y } => {
self.dimensions.width = width; self.update_offset(OutputDimensionsSource::Xdg, x, y, state);
self.dimensions.height = height; self.xdg.as_ref().unwrap().server.logical_position(
debug!("{} dimensions: {width}x{height} (xdg)", self.server.id()); x - state.global_output_offset.x.value,
} y - state.global_output_offset.y.value,
simple_event_shunt! { );
xdg, event: zxdg_output_v1::Event => [ }
LogicalPosition { Event::LogicalSize { .. } => {
|x| { xdg.logical_size(self.dimensions.width, self.dimensions.height);
x - state.global_output_offset.x.value }
}, _ => simple_event_shunt! {
|y| { xdg, event: zxdg_output_v1::Event => [
y - state.global_output_offset.y.value Done,
} Name { name },
}, Description { description }
LogicalSize { width, height }, ]
Done, },
Name { name },
Description { description }
]
} }
} }
} }

View file

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

View file

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

View file

@ -239,10 +239,6 @@ impl Fixture {
&mut self, &mut self,
connection: &mut Connection, connection: &mut Connection,
window: x::Window, window: x::Window,
x: i16,
y: i16,
width: u16,
height: u16,
) -> testwl::SurfaceId { ) -> testwl::SurfaceId {
connection.map_window(window); connection.map_window(window);
self.wait_and_dispatch(); self.wait_and_dispatch();
@ -258,15 +254,6 @@ impl Fixture {
); );
self.testwl.configure_popup(surface); self.testwl.configure_popup(surface);
self.wait_and_dispatch(); self.wait_and_dispatch();
let geometry = connection.get_reply(&x::GetGeometry {
drawable: x::Drawable::Window(window),
});
assert_eq!(geometry.x(), x);
assert_eq!(geometry.y(), y);
assert_eq!(geometry.width(), width);
assert_eq!(geometry.height(), height);
surface surface
} }
@ -796,8 +783,7 @@ fn input_focus() {
long_offset: 0, long_offset: 0,
long_length: 1, long_length: 1,
}) })
.value::<u32>() .value::<u32>().first()
.get(0)
.and_then(|state| WmState::try_from(*state).ok()), .and_then(|state| WmState::try_from(*state).ok()),
Some(WmState::Normal) Some(WmState::Normal)
); );
@ -1475,7 +1461,15 @@ fn popup_done() {
f.map_as_toplevel(&mut conn, toplevel); f.map_as_toplevel(&mut conn, toplevel);
let popup = conn.new_window(conn.root, 0, 0, 20, 20, true); let popup = conn.new_window(conn.root, 0, 0, 20, 20, true);
let surface = f.map_as_popup(&mut conn, popup, 0, 0, 20, 20); let surface = f.map_as_popup(&mut conn, popup);
let geometry = conn.get_reply(&x::GetGeometry {
drawable: x::Drawable::Window(popup),
});
assert_eq!(geometry.x(), 0);
assert_eq!(geometry.y(), 0);
assert_eq!(geometry.width(), 20);
assert_eq!(geometry.height(), 20);
f.testwl.popup_done(surface); f.testwl.popup_done(surface);
f.wait_and_dispatch(); f.wait_and_dispatch();
@ -1576,3 +1570,74 @@ fn xdg_decorations() {
Some(zxdg_toplevel_decoration_v1::Mode::ServerSide) Some(zxdg_toplevel_decoration_v1::Mode::ServerSide)
); );
} }
#[test]
fn forced_1x_scale_consistent_x11_size() {
let mut f = Fixture::new();
f.testwl.enable_xdg_output_manager();
let output = f.create_output(0, 0);
output.scale(2);
output.done();
let mut conn = Connection::new(&f.display);
let window = conn.new_window(conn.root, 0, 0, 200, 200, false);
let surface = f.map_as_toplevel(&mut conn, window);
f.testwl.move_surface_to_output(surface, &output);
f.testwl.move_pointer_to(surface, 30.0, 40.0);
f.wait_and_dispatch();
let tree = conn.get_reply(&x::QueryTree { window });
let geo = conn.get_reply(&x::GetGeometry {
drawable: x::Drawable::Window(window),
});
let reply = conn.get_reply(&x::TranslateCoordinates {
src_window: tree.parent(),
dst_window: conn.root,
src_x: geo.x(),
src_y: geo.y(),
});
assert!(reply.same_screen());
assert_eq!(reply.dst_x(), 0);
assert_eq!(reply.dst_y(), 0);
let ptr_reply = conn.get_reply(&x::QueryPointer { window: conn.root });
assert!(ptr_reply.same_screen());
assert_eq!(ptr_reply.child(), window);
assert_eq!(ptr_reply.win_x(), 60);
assert_eq!(ptr_reply.win_y(), 80);
// Update scale
output.scale(3);
output.done();
f.testwl
.configure_toplevel(surface, 100, 100, vec![xdg_toplevel::State::Activated]);
f.testwl.focus_toplevel(surface);
f.testwl.move_pointer_to(surface, 30.0, 40.0);
f.wait_and_dispatch();
let ptr_reply = conn.get_reply(&x::QueryPointer { window: conn.root });
assert!(ptr_reply.same_screen());
assert_eq!(ptr_reply.child(), window);
assert_eq!(ptr_reply.win_x(), 90);
assert_eq!(ptr_reply.win_y(), 120);
// Popup
let popup = conn.new_window(conn.root, 60, 60, 30, 30, true);
f.map_as_popup(&mut conn, popup);
let tree = conn.get_reply(&x::QueryTree { window: popup });
let geo = conn.get_reply(&x::GetGeometry {
drawable: x::Drawable::Window(popup),
});
let reply = conn.get_reply(&x::TranslateCoordinates {
src_window: tree.parent(),
dst_window: conn.root,
src_x: geo.x(),
src_y: geo.y(),
});
assert_eq!(reply.dst_x(), 60);
assert_eq!(reply.dst_y(), 60);
assert_eq!(geo.width(), 30);
assert_eq!(geo.height(), 30);
}

View file

@ -20,7 +20,10 @@ use wayland_protocols::{
zwp_tablet_tool_v2::{self, ZwpTabletToolV2}, zwp_tablet_tool_v2::{self, ZwpTabletToolV2},
zwp_tablet_v2::ZwpTabletV2, zwp_tablet_v2::ZwpTabletV2,
}, },
viewporter::server::wp_viewporter::WpViewporter, viewporter::server::{
wp_viewport::WpViewport,
wp_viewporter::{self, WpViewporter},
},
}, },
xdg::{ xdg::{
activation::v1::server::{ activation::v1::server::{
@ -392,9 +395,9 @@ impl Server {
dh.create_global::<State, ZwpTabletManagerV2, _>(1, ()); dh.create_global::<State, ZwpTabletManagerV2, _>(1, ());
dh.create_global::<State, XdgActivationV1, _>(1, ()); dh.create_global::<State, XdgActivationV1, _>(1, ());
dh.create_global::<State, ZxdgDecorationManagerV1, _>(1, ()); dh.create_global::<State, ZxdgDecorationManagerV1, _>(1, ());
dh.create_global::<State, WpViewporter, _>(1, ());
global_noop!(ZwpLinuxDmabufV1); global_noop!(ZwpLinuxDmabufV1);
global_noop!(ZwpRelativePointerManagerV1); global_noop!(ZwpRelativePointerManagerV1);
global_noop!(WpViewporter);
global_noop!(ZwpPointerConstraintsV1); global_noop!(ZwpPointerConstraintsV1);
struct HandlerData; struct HandlerData;
@ -1239,14 +1242,33 @@ impl Dispatch<XdgSurface, SurfaceId> for State {
parent, parent,
positioner, positioner,
} => { } => {
let positioner_state =
state.positioners[&PositionerId(positioner.id().protocol_id())].clone();
if positioner_state
.size
.is_none_or(|size| size.x <= 0 || size.y <= 0)
{
// TODO: figure out why the client.kill here doesn't make satellite print the error message
let message =
format!("positioner had an invalid size {:?}", positioner_state.size);
eprintln!("{message}");
client.kill(
dh,
ProtocolError {
code: xdg_surface::Error::InvalidSize.into(),
object_id: resource.id().protocol_id(),
object_interface: XdgSurface::interface().name.to_string(),
message,
},
);
return;
}
let popup = data_init.init(id, *surface_id); let popup = data_init.init(id, *surface_id);
let p = Popup { let p = Popup {
xdg: XdgSurfaceData::new(resource.clone()), xdg: XdgSurfaceData::new(resource.clone()),
popup, popup,
parent: parent.unwrap(), parent: parent.unwrap(),
positioner_state: state.positioners positioner_state,
[&PositionerId(positioner.id().protocol_id())]
.clone(),
}; };
let data = state.surfaces.get_mut(surface_id).unwrap(); let data = state.surfaces.get_mut(surface_id).unwrap();
data.role = Some(SurfaceRole::Popup(p)); data.role = Some(SurfaceRole::Popup(p));
@ -1789,3 +1811,50 @@ impl Dispatch<ZxdgToplevelDecorationV1, SurfaceId> for State {
} }
} }
} }
impl GlobalDispatch<WpViewporter, ()> for State {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: wayland_server::New<WpViewporter>,
_: &(),
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<WpViewporter, ()> for State {
fn request(
_: &mut Self,
_: &Client,
_: &WpViewporter,
request: <WpViewporter as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
wp_viewporter::Request::GetViewport { surface: _, id } => {
data_init.init(id, ());
}
wp_viewporter::Request::Destroy => {}
_ => unreachable!(),
}
}
}
impl Dispatch<WpViewport, ()> for State {
fn request(
_: &mut Self,
_: &Client,
_: &WpViewport,
_: <WpViewport as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
//todo!()
}
}