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:
parent
45c0556964
commit
8188df0e70
8 changed files with 636 additions and 244 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue