Compare commits
10 commits
75c9f5e775
...
725720f1cb
| Author | SHA1 | Date | |
|---|---|---|---|
| 725720f1cb | |||
|
|
a879e5e089 | ||
|
|
309d8e2a29 | ||
|
|
10f985b84c | ||
|
|
ecde06ed3a | ||
|
|
33c344fee5 | ||
|
|
536bd32efc | ||
|
|
86f5bd5d86 | ||
|
|
e6dd3c05c0 | ||
|
|
0947c4685f |
13 changed files with 645 additions and 333 deletions
|
|
@ -34,13 +34,16 @@ The code for the X11 portion of satellite lives in `src/xstate`. Satellite must
|
||||||
as any other standard X11 window manager. This includes:
|
as any other standard X11 window manager. This includes:
|
||||||
|
|
||||||
- Setting SubstructureRedirect and SubstructureNotify on the root window, to get notifications for when new windows are being created
|
- Setting SubstructureRedirect and SubstructureNotify on the root window, to get notifications for when new windows are being created
|
||||||
- Follwing (most of) the [ICCCM](https://www.x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html) and [EWMH](https://specifications.freedesktop.org/wm-spec/latest/) specs
|
- Following (most of) the [ICCCM](https://www.x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html) and [EWMH](https://specifications.freedesktop.org/wm-spec/latest/) specs
|
||||||
|
|
||||||
In addition, satellite must do some other things that a normal X11 window manager wouldn't - but a compositor integrating
|
In addition, satellite must do some other things that a normal X11 window manager wouldn't - but a compositor integrating
|
||||||
Xwayland would - such as synchronize X11 and Wayland selections. This is explained further in the Wayland server section.
|
Xwayland would - such as synchronize X11 and Wayland selections. This is explained further in the Wayland server section.
|
||||||
|
|
||||||
The way that satellite manages windows from the X11 point of view is as follows:
|
The way that satellite manages windows from the X11 point of view is as follows:
|
||||||
|
|
||||||
|
- All monitors maintain their relative positions to one another. Their absolute position is such that
|
||||||
|
the top-most monitor's top edge is on the X-axis and the left-most monitor's left edge is on the Y-axis.
|
||||||
|
- All monitors are on non-negative coordinates with no gaps between the screen and any monitor, matching what `xrandr` does.
|
||||||
- All toplevels on a monitor are positioned at 0x0 on that monitor. So if you have one monitor at 0x0,
|
- All toplevels on a monitor are positioned at 0x0 on that monitor. So if you have one monitor at 0x0,
|
||||||
all the windows are located at 0x0. If you have a monitor at 300x600, all the windows on that monitor are at 300x600.
|
all the windows are located at 0x0. If you have a monitor at 300x600, all the windows on that monitor are at 300x600.
|
||||||
- This offset is needed because all monitors rest in the same coordinate plane in X11, so missing this offset would
|
- This offset is needed because all monitors rest in the same coordinate plane in X11, so missing this offset would
|
||||||
|
|
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1241,7 +1241,7 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xwayland-satellite"
|
name = "xwayland-satellite"
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ab_glyph",
|
"ab_glyph",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ edition = "2024"
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "xwayland-satellite"
|
name = "xwayland-satellite"
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
authors = ["Shawn Wallace"]
|
authors = ["Shawn Wallace"]
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "xwayland-satellite grants rootless Xwayland integration to any Wayland compositor implementing xdg_wm_base and viewporter. This is particularly useful for compositors that (understandably) do not want to go through implementing support for rootless Xwayland themselves."
|
description = "xwayland-satellite grants rootless Xwayland integration to any Wayland compositor implementing xdg_wm_base and viewporter. This is particularly useful for compositors that (understandably) do not want to go through implementing support for rootless Xwayland themselves."
|
||||||
|
|
|
||||||
19
src/lib.rs
19
src/lib.rs
|
|
@ -9,7 +9,7 @@ use server::selection::{Clipboard, Primary};
|
||||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||||
use std::io::{BufRead, BufReader, Read, Write};
|
use std::io::{BufRead, BufReader, Read, Write};
|
||||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd};
|
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd};
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::{net::UnixStream, process::ExitStatusExt};
|
||||||
use std::process::{Command, ExitStatus, Stdio};
|
use std::process::{Command, ExitStatus, Stdio};
|
||||||
use wayland_server::{Display, ListeningSocket};
|
use wayland_server::{Display, ListeningSocket};
|
||||||
use xcb::x;
|
use xcb::x;
|
||||||
|
|
@ -121,9 +121,9 @@ pub fn main(mut data: impl RunData) -> Option<()> {
|
||||||
let line = line.unwrap();
|
let line = line.unwrap();
|
||||||
info!(target: "xwayland_process", "{line}");
|
info!(target: "xwayland_process", "{line}");
|
||||||
}
|
}
|
||||||
let status = Box::new(xwayland.wait().unwrap());
|
let status = xwayland.wait().unwrap().into_raw();
|
||||||
let status = Box::into_raw(status) as usize;
|
// On a successful integration test, the rx will be dropped, so keep logs/GDB clean
|
||||||
finish_tx.write_all(&status.to_ne_bytes()).unwrap();
|
let _ = finish_tx.write_all(&status.to_ne_bytes());
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut ready_fds = [
|
let mut ready_fds = [
|
||||||
|
|
@ -131,11 +131,10 @@ pub fn main(mut data: impl RunData) -> Option<()> {
|
||||||
PollFd::new(&finish_rx, PollFlags::IN),
|
PollFd::new(&finish_rx, PollFlags::IN),
|
||||||
];
|
];
|
||||||
|
|
||||||
fn xwayland_exit_code(rx: &mut UnixStream) -> Box<ExitStatus> {
|
fn xwayland_exit_code(rx: &mut UnixStream) -> ExitStatus {
|
||||||
let mut data = [0; (usize::BITS / 8) as usize];
|
let mut data = [0; std::mem::size_of::<i32>()];
|
||||||
rx.read_exact(&mut data).unwrap();
|
rx.read_exact(&mut data).unwrap();
|
||||||
let data = usize::from_ne_bytes(data);
|
ExitStatus::from_raw(i32::from_ne_bytes(data))
|
||||||
unsafe { Box::from_raw(data as *mut _) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let connection = match poll(&mut ready_fds, None) {
|
let connection = match poll(&mut ready_fds, None) {
|
||||||
|
|
@ -179,7 +178,7 @@ pub fn main(mut data: impl RunData) -> Option<()> {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
if !fds[3].revents().is_empty() {
|
if !fds[3].revents().is_empty() {
|
||||||
let status = xwayland_exit_code(&mut quit_rx);
|
let status = xwayland_exit_code(&mut quit_rx);
|
||||||
if *status != ExitStatus::default() {
|
if status != ExitStatus::default() {
|
||||||
error!("Xwayland exited early with {status}");
|
error!("Xwayland exited early with {status}");
|
||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -246,7 +245,7 @@ pub fn main(mut data: impl RunData) -> Option<()> {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
if !fds[3].revents().is_empty() {
|
if !fds[3].revents().is_empty() {
|
||||||
let status = xwayland_exit_code(&mut quit_rx);
|
let status = xwayland_exit_code(&mut quit_rx);
|
||||||
if *status != ExitStatus::default() {
|
if status != ExitStatus::default() {
|
||||||
error!("Xwayland exited early with {status}");
|
error!("Xwayland exited early with {status}");
|
||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::decoration::DecorationMarker;
|
use super::decoration::DecorationMarker;
|
||||||
|
|
||||||
use super::ObjectEvent;
|
use super::{GlobalName, ObjectEvent};
|
||||||
use hecs::{Entity, World};
|
use hecs::{Entity, World};
|
||||||
use smithay_client_toolkit::{
|
use smithay_client_toolkit::{
|
||||||
activation::{ActivationHandler, RequestData, RequestDataExt},
|
activation::{ActivationHandler, RequestData, RequestDataExt},
|
||||||
|
|
@ -115,6 +115,7 @@ pub(super) struct MyWorld {
|
||||||
pub world: World,
|
pub world: World,
|
||||||
pub global_list: GlobalList,
|
pub global_list: GlobalList,
|
||||||
pub new_globals: Vec<Global>,
|
pub new_globals: Vec<Global>,
|
||||||
|
pub removed_globals: Vec<GlobalName>,
|
||||||
events: Vec<(Entity, ObjectEvent)>,
|
events: Vec<(Entity, ObjectEvent)>,
|
||||||
queued_events: Vec<mpsc::Receiver<(Entity, ObjectEvent)>>,
|
queued_events: Vec<mpsc::Receiver<(Entity, ObjectEvent)>>,
|
||||||
pub clipboard: SelectionEvents<SelectionOffer>,
|
pub clipboard: SelectionEvents<SelectionOffer>,
|
||||||
|
|
@ -128,6 +129,7 @@ impl MyWorld {
|
||||||
world: World::new(),
|
world: World::new(),
|
||||||
global_list,
|
global_list,
|
||||||
new_globals: Vec::new(),
|
new_globals: Vec::new(),
|
||||||
|
removed_globals: Vec::new(),
|
||||||
events: Vec::new(),
|
events: Vec::new(),
|
||||||
queued_events: Vec::new(),
|
queued_events: Vec::new(),
|
||||||
clipboard: Default::default(),
|
clipboard: Default::default(),
|
||||||
|
|
@ -204,18 +206,23 @@ impl Dispatch<WlRegistry, GlobalListContents> for MyWorld {
|
||||||
_: &wayland_client::Connection,
|
_: &wayland_client::Connection,
|
||||||
_: &wayland_client::QueueHandle<Self>,
|
_: &wayland_client::QueueHandle<Self>,
|
||||||
) {
|
) {
|
||||||
if let Event::<WlRegistry>::Global {
|
match event {
|
||||||
name,
|
Event::<WlRegistry>::Global {
|
||||||
interface,
|
|
||||||
version,
|
|
||||||
} = event
|
|
||||||
{
|
|
||||||
state.new_globals.push(Global {
|
|
||||||
name,
|
name,
|
||||||
interface,
|
interface,
|
||||||
version,
|
version,
|
||||||
});
|
} => {
|
||||||
};
|
state.new_globals.push(Global {
|
||||||
|
name,
|
||||||
|
interface,
|
||||||
|
version,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Event::<WlRegistry>::GlobalRemove { name } => {
|
||||||
|
state.removed_globals.push(GlobalName(name));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ impl DecorationsDataSatellite {
|
||||||
|
|
||||||
// Draw the bar and its components
|
// Draw the bar and its components
|
||||||
let mut bar = Pixmap::new(drawn_width as u32, drawn_height as u32).unwrap();
|
let mut bar = Pixmap::new(drawn_width as u32, drawn_height as u32).unwrap();
|
||||||
bar.fill(Color::WHITE);
|
bar.fill(Color::from_rgba(0.2, 0.2, 0.2, 1.0).unwrap());
|
||||||
|
|
||||||
if let Some(title) = title {
|
if let Some(title) = title {
|
||||||
bar.draw_pixmap(
|
bar.draw_pixmap(
|
||||||
|
|
@ -350,9 +350,9 @@ fn draw_pixmap_to_buffer(pixmap: &Pixmap, buffer: &mut [u8]) {
|
||||||
fn x_pixmap(bar_height: u32, scale: f32, hovered: bool) -> Pixmap {
|
fn x_pixmap(bar_height: u32, scale: f32, hovered: bool) -> Pixmap {
|
||||||
let mut x = Pixmap::new(bar_height, bar_height).unwrap();
|
let mut x = Pixmap::new(bar_height, bar_height).unwrap();
|
||||||
if hovered {
|
if hovered {
|
||||||
x.fill(Color::from_rgba(1.0, 0.0, 0.0, 0.8).unwrap());
|
x.fill(Color::from_rgba(1.0, 0.2, 0.2, 0.8).unwrap());
|
||||||
} else {
|
} else {
|
||||||
x.fill(Color::WHITE);
|
x.fill(Color::from_rgba(0.2, 0.2, 0.2, 1.0).unwrap());
|
||||||
}
|
}
|
||||||
let size = x.width() as f32;
|
let size = x.width() as f32;
|
||||||
let margin = 8.4 * scale;
|
let margin = 8.4 * scale;
|
||||||
|
|
@ -363,9 +363,11 @@ fn x_pixmap(bar_height: u32, scale: f32, hovered: bool) -> Pixmap {
|
||||||
line.move_to(size - margin, margin);
|
line.move_to(size - margin, margin);
|
||||||
line.line_to(margin, size - margin);
|
line.line_to(margin, size - margin);
|
||||||
let line = line.finish().unwrap();
|
let line = line.finish().unwrap();
|
||||||
|
let mut paint = Paint::default();
|
||||||
|
paint.set_color(Color::WHITE);
|
||||||
x.stroke_path(
|
x.stroke_path(
|
||||||
&line,
|
&line,
|
||||||
&Default::default(),
|
&paint,
|
||||||
&Stroke {
|
&Stroke {
|
||||||
width: scale + 0.5,
|
width: scale + 0.5,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -400,7 +402,7 @@ fn title_pixmap(title: &str, max_width: u32, height: u32, scale: f32) -> Option<
|
||||||
((bounds.min.x as u32 + x) + (bounds.min.y as u32 + y) * width) as usize;
|
((bounds.min.x as u32 + x) + (bounds.min.y as u32 + y) * width) as usize;
|
||||||
|
|
||||||
data[pixel_idx] =
|
data[pixel_idx] =
|
||||||
ColorU8::from_rgba(0, 0, 0, (coverage * 255.0) as u8).premultiply();
|
ColorU8::from_rgba(255, 255, 255, (coverage * 255.0) as u8).premultiply();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1479,6 +1479,7 @@ impl<S: X11Selection> GlobalDispatch<WlOutput, Global> for InnerServerState<S> {
|
||||||
client,
|
client,
|
||||||
event::OutputScaleFactor::Output(1),
|
event::OutputScaleFactor::Output(1),
|
||||||
event::OutputDimensions::default(),
|
event::OutputDimensions::default(),
|
||||||
|
GlobalName(data.name),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
state.updated_outputs.push(entity);
|
state.updated_outputs.push(entity);
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,9 @@ impl SurfaceEvents {
|
||||||
|
|
||||||
let mut query = data.query::<(&x::Window, &mut WindowData)>();
|
let mut query = data.query::<(&x::Window, &mut WindowData)>();
|
||||||
if let Some((window, win_data)) = query.get() {
|
if let Some((window, win_data)) = query.get() {
|
||||||
let dimensions = output_data.get::<&OutputDimensions>().unwrap();
|
let Some(dimensions) = output_data.get::<&OutputDimensions>() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
win_data.update_output_offset(
|
win_data.update_output_offset(
|
||||||
*window,
|
*window,
|
||||||
WindowOutputOffset {
|
WindowOutputOffset {
|
||||||
|
|
@ -472,8 +474,8 @@ pub(super) fn update_surface_viewport(
|
||||||
let dims = &window_data.attrs.dims;
|
let dims = &window_data.attrs.dims;
|
||||||
let size_hints = &window_data.attrs.size_hints;
|
let size_hints = &window_data.attrs.size_hints;
|
||||||
|
|
||||||
let width = (dims.width as f64 / scale_factor.0) as i32;
|
let width = (dims.width as f64 / scale_factor.0).ceil() as i32;
|
||||||
let height = (dims.height as f64 / scale_factor.0) as i32;
|
let height = (dims.height as f64 / scale_factor.0).ceil() as i32;
|
||||||
if width > 0 && height > 0 {
|
if width > 0 && height > 0 {
|
||||||
viewport.set_destination(width, height);
|
viewport.set_destination(width, height);
|
||||||
}
|
}
|
||||||
|
|
@ -983,7 +985,7 @@ impl Event for client::wl_touch::Event {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub(super) struct OnOutput(pub Entity);
|
pub(super) struct OnOutput(pub Entity);
|
||||||
struct OutputName(String);
|
struct OutputName(String);
|
||||||
fn get_output_name(output: Option<&OnOutput>, world: &World) -> Option<String> {
|
fn get_output_name(output: Option<&OnOutput>, world: &World) -> Option<String> {
|
||||||
|
|
@ -1077,7 +1079,9 @@ fn update_output_offset(
|
||||||
let connection = &mut state.connection;
|
let connection = &mut state.connection;
|
||||||
let state = &mut state.inner;
|
let state = &mut state.inner;
|
||||||
{
|
{
|
||||||
let mut dimensions = state.world.get::<&mut OutputDimensions>(output).unwrap();
|
let Ok(mut dimensions) = state.world.get::<&mut OutputDimensions>(output) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
if matches!(source, OutputDimensionsSource::Wl { .. })
|
if matches!(source, OutputDimensionsSource::Wl { .. })
|
||||||
&& matches!(dimensions.source, OutputDimensionsSource::Xdg)
|
&& matches!(dimensions.source, OutputDimensionsSource::Xdg)
|
||||||
{
|
{
|
||||||
|
|
@ -1093,7 +1097,8 @@ fn update_output_offset(
|
||||||
};
|
};
|
||||||
state.global_offset_updated = true;
|
state.global_offset_updated = true;
|
||||||
} else if dim.owner == Some(output) && value > dim.value {
|
} else if dim.owner == Some(output) && value > dim.value {
|
||||||
*dim = Default::default();
|
// Another output's position could be less than the new value, so recalculate
|
||||||
|
dim.owner = None;
|
||||||
state.global_offset_updated = true;
|
state.global_offset_updated = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1124,7 +1129,9 @@ fn update_window_output_offsets(
|
||||||
world: &World,
|
world: &World,
|
||||||
connection: &mut impl XConnection,
|
connection: &mut impl XConnection,
|
||||||
) {
|
) {
|
||||||
let dimensions = world.get::<&OutputDimensions>(output).unwrap();
|
let Ok(dimensions) = world.get::<&OutputDimensions>(output) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let mut query = world.query::<(&x::Window, &mut WindowData, &OnOutput)>();
|
let mut query = world.query::<(&x::Window, &mut WindowData, &OnOutput)>();
|
||||||
|
|
||||||
for (_, (window, data, _)) in query
|
for (_, (window, data, _)) in query
|
||||||
|
|
@ -1150,7 +1157,9 @@ pub(super) fn update_global_output_offset(
|
||||||
) {
|
) {
|
||||||
let entity = world.entity(output).unwrap();
|
let entity = world.entity(output).unwrap();
|
||||||
let mut query = entity.query::<(&OutputDimensions, &WlOutput)>();
|
let mut query = entity.query::<(&OutputDimensions, &WlOutput)>();
|
||||||
let (dimensions, server) = query.get().unwrap();
|
let Some((dimensions, server)) = query.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let x = dimensions.x - global_output_offset.x.value;
|
let x = dimensions.x - global_output_offset.x.value;
|
||||||
let y = dimensions.y - global_output_offset.y.value;
|
let y = dimensions.y - global_output_offset.y.value;
|
||||||
|
|
@ -1249,24 +1258,28 @@ impl OutputEvent {
|
||||||
state,
|
state,
|
||||||
);
|
);
|
||||||
let global_output_offset = state.global_output_offset;
|
let global_output_offset = state.global_output_offset;
|
||||||
|
let global_offset_updated = state.global_offset_updated;
|
||||||
|
|
||||||
let (output, dimensions, xdg) = state
|
let Ok((output, dimensions, xdg)) = state.world.query_one_mut::<(
|
||||||
.world
|
&WlOutput,
|
||||||
.query_one_mut::<(&WlOutput, &mut OutputDimensions, Option<&XdgOutputServer>)>(
|
&mut OutputDimensions,
|
||||||
target,
|
Option<&XdgOutputServer>,
|
||||||
)
|
)>(target) else {
|
||||||
.unwrap();
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
output.geometry(
|
if !global_offset_updated {
|
||||||
x - global_output_offset.x.value,
|
output.geometry(
|
||||||
y - global_output_offset.y.value,
|
x - global_output_offset.x.value,
|
||||||
physical_width,
|
y - global_output_offset.y.value,
|
||||||
physical_height,
|
physical_width,
|
||||||
convert_wenum(subpixel),
|
physical_height,
|
||||||
make,
|
convert_wenum(subpixel),
|
||||||
model,
|
make,
|
||||||
convert_wenum(transform),
|
model,
|
||||||
);
|
convert_wenum(transform),
|
||||||
|
);
|
||||||
|
}
|
||||||
dimensions.rotated_90 = transform.into_result().is_ok_and(|t| {
|
dimensions.rotated_90 = transform.into_result().is_ok_and(|t| {
|
||||||
matches!(
|
matches!(
|
||||||
t,
|
t,
|
||||||
|
|
@ -1290,10 +1303,12 @@ impl OutputEvent {
|
||||||
height,
|
height,
|
||||||
refresh,
|
refresh,
|
||||||
} => {
|
} => {
|
||||||
let (output, dimensions) = state
|
let Ok((output, dimensions)) = state
|
||||||
.world
|
.world
|
||||||
.query_one_mut::<(&WlOutput, &mut OutputDimensions)>(target)
|
.query_one_mut::<(&WlOutput, &mut OutputDimensions)>(target)
|
||||||
.unwrap();
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if flags
|
if flags
|
||||||
.into_result()
|
.into_result()
|
||||||
|
|
@ -1348,20 +1363,24 @@ impl OutputEvent {
|
||||||
match event {
|
match event {
|
||||||
Event::LogicalPosition { x, y } => {
|
Event::LogicalPosition { x, y } => {
|
||||||
update_output_offset(target, OutputDimensionsSource::Xdg, x, y, state);
|
update_output_offset(target, OutputDimensionsSource::Xdg, x, y, state);
|
||||||
state
|
if !state.global_offset_updated {
|
||||||
.world
|
state
|
||||||
.get::<&XdgOutputServer>(target)
|
.world
|
||||||
.unwrap()
|
.get::<&XdgOutputServer>(target)
|
||||||
.logical_position(
|
.unwrap()
|
||||||
x - state.global_output_offset.x.value,
|
.logical_position(
|
||||||
y - state.global_output_offset.y.value,
|
x - state.global_output_offset.x.value,
|
||||||
);
|
y - state.global_output_offset.y.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Event::LogicalSize { .. } => {
|
Event::LogicalSize { .. } => {
|
||||||
let (xdg, dimensions) = state
|
let Ok((xdg, dimensions)) = state
|
||||||
.world
|
.world
|
||||||
.query_one_mut::<(&XdgOutputServer, &OutputDimensions)>(target)
|
.query_one_mut::<(&XdgOutputServer, &OutputDimensions)>(target)
|
||||||
.unwrap();
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
if dimensions.rotated_90 {
|
if dimensions.rotated_90 {
|
||||||
xdg.logical_size(dimensions.height, dimensions.width);
|
xdg.logical_size(dimensions.height, dimensions.width);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ use wayland_protocols::{
|
||||||
use wayland_server::protocol::wl_seat::WlSeat;
|
use wayland_server::protocol::wl_seat::WlSeat;
|
||||||
use wayland_server::{
|
use wayland_server::{
|
||||||
Client, DisplayHandle, Resource, WEnum,
|
Client, DisplayHandle, Resource, WEnum,
|
||||||
|
backend::GlobalId,
|
||||||
protocol::{
|
protocol::{
|
||||||
wl_callback::WlCallback, wl_compositor::WlCompositor, wl_output::WlOutput, wl_shm::WlShm,
|
wl_callback::WlCallback, wl_compositor::WlCompositor, wl_output::WlOutput, wl_shm::WlShm,
|
||||||
wl_surface::WlSurface,
|
wl_surface::WlSurface,
|
||||||
|
|
@ -142,7 +143,7 @@ impl WindowData {
|
||||||
offset: WindowOutputOffset,
|
offset: WindowOutputOffset,
|
||||||
connection: &mut C,
|
connection: &mut C,
|
||||||
) {
|
) {
|
||||||
log::trace!("offset: {offset:?}");
|
log::trace!(target: "output_offset", "offset: {offset:?}");
|
||||||
if offset == self.output_offset {
|
if offset == self.output_offset {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +162,7 @@ impl WindowData {
|
||||||
height: self.attrs.dims.height as _,
|
height: self.attrs.dims.height as _,
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
debug!("set {:?} offset to {:?}", window, self.output_offset);
|
debug!(target: "output_offset", "set {:?} offset to {:?}", window, self.output_offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -342,7 +343,8 @@ enum ObjectEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_globals<'a, S: X11Selection + 'static>(
|
fn handle_new_globals<'a, S: X11Selection + 'static>(
|
||||||
|
globals_map: &mut HashMap<GlobalName, (Global, GlobalId)>,
|
||||||
dh: &DisplayHandle,
|
dh: &DisplayHandle,
|
||||||
globals: impl IntoIterator<Item = &'a Global>,
|
globals: impl IntoIterator<Item = &'a Global>,
|
||||||
) {
|
) {
|
||||||
|
|
@ -353,7 +355,8 @@ fn handle_globals<'a, S: X11Selection + 'static>(
|
||||||
$(
|
$(
|
||||||
ref x if x == <$global>::interface().name => {
|
ref x if x == <$global>::interface().name => {
|
||||||
let version = u32::min(global.version, <$global>::interface().version);
|
let version = u32::min(global.version, <$global>::interface().version);
|
||||||
dh.create_global::<InnerServerState<S>, $global, Global>(version, global.clone());
|
let global_id = dh.create_global::<InnerServerState<S>, $global, Global>(version, global.clone());
|
||||||
|
globals_map.insert(GlobalName(global.name), (global.clone(), global_id));
|
||||||
}
|
}
|
||||||
)+
|
)+
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -377,6 +380,9 @@ fn handle_globals<'a, S: X11Selection + 'static>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub(super) struct GlobalName(pub u32);
|
||||||
|
|
||||||
struct FocusData {
|
struct FocusData {
|
||||||
window: x::Window,
|
window: x::Window,
|
||||||
output_name: Option<String>,
|
output_name: Option<String>,
|
||||||
|
|
@ -446,6 +452,7 @@ pub struct InnerServerState<S: X11Selection> {
|
||||||
world: MyWorld,
|
world: MyWorld,
|
||||||
queue: EventQueue<MyWorld>,
|
queue: EventQueue<MyWorld>,
|
||||||
qh: QueueHandle<MyWorld>,
|
qh: QueueHandle<MyWorld>,
|
||||||
|
globals_map: HashMap<GlobalName, (Global, GlobalId)>,
|
||||||
client: Client,
|
client: Client,
|
||||||
to_focus: Option<FocusData>,
|
to_focus: Option<FocusData>,
|
||||||
unfocus: bool,
|
unfocus: bool,
|
||||||
|
|
@ -532,9 +539,10 @@ impl<S: X11Selection> ServerState<NoConnection<S>> {
|
||||||
|
|
||||||
dh.create_global::<InnerServerState<S>, XwaylandShellV1, _>(1, ());
|
dh.create_global::<InnerServerState<S>, XwaylandShellV1, _>(1, ());
|
||||||
|
|
||||||
|
let mut globals_map = HashMap::new();
|
||||||
global_list
|
global_list
|
||||||
.contents()
|
.contents()
|
||||||
.with_list(|globals| handle_globals::<S>(&dh, globals));
|
.with_list(|globals| handle_new_globals::<S>(&mut globals_map, &dh, globals));
|
||||||
|
|
||||||
let world = MyWorld::new(global_list);
|
let world = MyWorld::new(global_list);
|
||||||
let client = dh.insert_client(client, std::sync::Arc::new(())).unwrap();
|
let client = dh.insert_client(client, std::sync::Arc::new(())).unwrap();
|
||||||
|
|
@ -545,6 +553,7 @@ impl<S: X11Selection> ServerState<NoConnection<S>> {
|
||||||
client,
|
client,
|
||||||
queue,
|
queue,
|
||||||
qh,
|
qh,
|
||||||
|
globals_map,
|
||||||
dh,
|
dh,
|
||||||
to_focus: None,
|
to_focus: None,
|
||||||
unfocus: false,
|
unfocus: false,
|
||||||
|
|
@ -613,7 +622,7 @@ impl<C: XConnection> ServerState<C> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_clientside_events(&mut self) {
|
pub fn handle_clientside_events(&mut self) {
|
||||||
self.handle_new_globals();
|
self.handle_globals();
|
||||||
|
|
||||||
for (target, event) in self.world.read_events() {
|
for (target, event) in self.world.read_events() {
|
||||||
if !self.world.contains(target) {
|
if !self.world.contains(target) {
|
||||||
|
|
@ -635,14 +644,15 @@ impl<C: XConnection> ServerState<C> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.global_output_offset.x.owner.is_none()
|
||||||
|
|| self.global_output_offset.y.owner.is_none()
|
||||||
|
{
|
||||||
|
self.calc_global_output_offset();
|
||||||
|
self.global_offset_updated = true;
|
||||||
|
}
|
||||||
if self.global_offset_updated {
|
if self.global_offset_updated {
|
||||||
if self.global_output_offset.x.owner.is_none()
|
|
||||||
|| self.global_output_offset.y.owner.is_none()
|
|
||||||
{
|
|
||||||
self.calc_global_output_offset();
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
|
target: "output_offset",
|
||||||
"updated global output offset: {}x{}",
|
"updated global output offset: {}x{}",
|
||||||
self.global_output_offset.x.value, self.global_output_offset.y.value
|
self.global_output_offset.x.value, self.global_output_offset.y.value
|
||||||
);
|
);
|
||||||
|
|
@ -659,8 +669,10 @@ impl<C: XConnection> ServerState<C> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.updated_outputs.is_empty() {
|
if !self.updated_outputs.is_empty() {
|
||||||
for output in self.updated_outputs.iter() {
|
for output in std::mem::take(&mut self.updated_outputs).iter() {
|
||||||
let output_scale = self.world.get::<&OutputScaleFactor>(*output).unwrap();
|
let Ok(output_scale) = self.world.get::<&OutputScaleFactor>(*output) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
if matches!(*output_scale, OutputScaleFactor::Output(..)) {
|
if matches!(*output_scale, OutputScaleFactor::Output(..)) {
|
||||||
let mut surface_query = self
|
let mut surface_query = self
|
||||||
.world
|
.world
|
||||||
|
|
@ -684,31 +696,30 @@ impl<C: XConnection> ServerState<C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.updated_outputs.clear();
|
|
||||||
|
|
||||||
let mut mixed_scale = false;
|
let mut mixed_scale = false;
|
||||||
let mut scale;
|
let mut scale;
|
||||||
|
|
||||||
let mut outputs = self.world.query_mut::<&OutputScaleFactor>().into_iter();
|
let mut outputs = self.world.query_mut::<&OutputScaleFactor>().into_iter();
|
||||||
let (_, output_scale) = outputs.next().unwrap();
|
if let Some((_, output_scale)) = outputs.next() {
|
||||||
|
scale = output_scale.get();
|
||||||
|
|
||||||
scale = output_scale.get();
|
for (_, output_scale) in outputs {
|
||||||
|
if output_scale.get() != scale {
|
||||||
for (_, output_scale) in outputs {
|
mixed_scale = true;
|
||||||
if output_scale.get() != scale {
|
scale = scale.min(output_scale.get());
|
||||||
mixed_scale = true;
|
}
|
||||||
scale = scale.min(output_scale.get());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if mixed_scale {
|
if mixed_scale {
|
||||||
warn!(
|
warn!(
|
||||||
"Mixed output scales detected, choosing to give apps the smallest detected scale ({scale}x)"
|
"Mixed output scales detected, choosing to give apps the smallest detected scale ({scale}x)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Using new scale {scale}");
|
debug!("Using new scale {scale}");
|
||||||
self.new_scale = Some(scale);
|
self.new_scale = Some(scale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -780,9 +791,55 @@ impl<S: X11Selection + 'static> InnerServerState<S> {
|
||||||
self.queue.as_fd()
|
self.queue.as_fd()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_new_globals(&mut self) {
|
fn handle_globals(&mut self) {
|
||||||
let globals = std::mem::take(&mut self.world.new_globals);
|
let globals = std::mem::take(&mut self.world.new_globals);
|
||||||
handle_globals::<S>(&self.dh, globals.iter());
|
handle_new_globals::<S>(&mut self.globals_map, &self.dh, &globals);
|
||||||
|
|
||||||
|
let globals = std::mem::take(&mut self.world.removed_globals);
|
||||||
|
for global in globals {
|
||||||
|
let (global_struct, global_id) = self.globals_map.remove(&global).unwrap();
|
||||||
|
self.dh.disable_global::<InnerServerState<S>>(global_id);
|
||||||
|
if global_struct.interface == <WlOutput>::interface().name {
|
||||||
|
self.remove_output(global);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_output(&mut self, global: GlobalName) {
|
||||||
|
let query = self
|
||||||
|
.world
|
||||||
|
.query_mut::<(&WlOutput, &GlobalName)>()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(e, (_, name))| (e, *name))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
for (entity, name) in query.iter() {
|
||||||
|
if *name == global {
|
||||||
|
self.updated_outputs.push(*entity);
|
||||||
|
self.world
|
||||||
|
.remove::<(OutputScaleFactor, OutputDimensions)>(*entity)
|
||||||
|
.unwrap();
|
||||||
|
let query = self
|
||||||
|
.world
|
||||||
|
.query_mut::<&OnOutput>()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(e, on_out)| (e, *on_out))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
for (e, on_out) in query.iter() {
|
||||||
|
if *on_out == OnOutput(*entity) {
|
||||||
|
self.world.remove_one::<OnOutput>(*e).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.global_output_offset.x.owner == Some(*entity) {
|
||||||
|
self.global_offset_updated = true;
|
||||||
|
self.global_output_offset.x.owner = None;
|
||||||
|
}
|
||||||
|
if self.global_output_offset.y.owner == Some(*entity) {
|
||||||
|
self.global_offset_updated = true;
|
||||||
|
self.global_output_offset.y.owner = None;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_window(
|
pub fn new_window(
|
||||||
|
|
@ -1277,6 +1334,8 @@ impl<S: X11Selection + 'static> InnerServerState<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calc_global_output_offset(&mut self) {
|
fn calc_global_output_offset(&mut self) {
|
||||||
|
self.global_output_offset.x.value = i32::MAX;
|
||||||
|
self.global_output_offset.y.value = i32::MAX;
|
||||||
for (entity, dimensions) in self.world.query_mut::<&OutputDimensions>() {
|
for (entity, dimensions) in self.world.query_mut::<&OutputDimensions>() {
|
||||||
if dimensions.x < self.global_output_offset.x.value {
|
if dimensions.x < self.global_output_offset.x.value {
|
||||||
self.global_output_offset.x = GlobalOutputOffsetDimension {
|
self.global_output_offset.x = GlobalOutputOffsetDimension {
|
||||||
|
|
|
||||||
|
|
@ -530,7 +530,19 @@ impl<C: XConnection> TestFixture<C> {
|
||||||
);
|
);
|
||||||
self.run();
|
self.run();
|
||||||
self.run();
|
self.run();
|
||||||
(output, self.testwl.last_created_output())
|
(output, self.testwl.finalize_output())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_output(&mut self, output_s: wayland_server::protocol::wl_output::WlOutput) {
|
||||||
|
self.testwl.remove_output(output_s);
|
||||||
|
self.run();
|
||||||
|
self.run();
|
||||||
|
let mut events = std::mem::take(&mut *self.registry.data.events.lock().unwrap());
|
||||||
|
assert_eq!(events.len(), 1);
|
||||||
|
let event = events.pop().unwrap();
|
||||||
|
let Ev::<WlRegistry>::GlobalRemove { .. } = event else {
|
||||||
|
panic!("Unexpected event: {event:?}");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -852,13 +864,6 @@ impl TestFixture<FakeXConnection> {
|
||||||
let data = self.testwl.get_surface_data(surface_id).unwrap();
|
let data = self.testwl.get_surface_data(surface_id).unwrap();
|
||||||
match data.role {
|
match data.role {
|
||||||
Some(SurfaceRole::Popup(_)) => {
|
Some(SurfaceRole::Popup(_)) => {
|
||||||
assert_eq!(
|
|
||||||
data.popup().positioner_state.offset,
|
|
||||||
testwl::Vec2 {
|
|
||||||
x: dims.x as _,
|
|
||||||
y: dims.y as _
|
|
||||||
}
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
data.popup().positioner_state.size,
|
data.popup().positioner_state.size,
|
||||||
Some(testwl::Vec2 {
|
Some(testwl::Vec2 {
|
||||||
|
|
@ -1608,86 +1613,257 @@ fn override_redirect_choose_hover_window() {
|
||||||
assert_eq!(&popup_data.popup().parent, win1_xdg);
|
assert_eq!(&popup_data.popup().parent, win1_xdg);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[track_caller]
|
||||||
fn output_offset() {
|
fn check_output_position_event(output: &TestObject<WlOutput>, pos: (i32, i32)) {
|
||||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
let mut geo = None;
|
||||||
let (output_obj, output) = f.new_output(0, 0);
|
let events = std::mem::take(&mut *output.data.events.lock().unwrap());
|
||||||
let man = f.enable_xdg_output();
|
log::debug!("events: {events:?}");
|
||||||
f.create_xdg_output(&man, output_obj.obj);
|
for event in events {
|
||||||
f.testwl.move_xdg_output(&output, 500, 100);
|
match event {
|
||||||
f.run();
|
wl_output::Event::Geometry { x, y, .. } => {
|
||||||
let window = Window::new(1);
|
geo = Some((x, y));
|
||||||
|
}
|
||||||
{
|
wl_output::Event::Done => {
|
||||||
let (surface, surface_id) = f.create_toplevel(&comp, window);
|
if let Some(geo) = geo {
|
||||||
f.testwl.move_surface_to_output(surface_id, &output);
|
assert_eq!(geo, pos);
|
||||||
f.run();
|
return;
|
||||||
let data = &f.connection().windows[&window];
|
}
|
||||||
assert_eq!(data.dims.x, 500);
|
}
|
||||||
assert_eq!(data.dims.y, 100);
|
_ => {}
|
||||||
|
}
|
||||||
f.satellite.unmap_window(window);
|
|
||||||
surface.obj.destroy();
|
|
||||||
f.run();
|
|
||||||
}
|
}
|
||||||
|
if geo.is_none() {
|
||||||
let (t_buffer, t_surface) = comp.create_surface();
|
panic!("Did not receive any geometry events");
|
||||||
f.map_window(&comp, window, &t_surface.obj, &t_buffer);
|
} else {
|
||||||
f.run();
|
panic!("Did not receive a done event");
|
||||||
let t_id = f.testwl.last_created_surface_id().unwrap();
|
|
||||||
f.testwl.move_surface_to_output(t_id, &output);
|
|
||||||
f.run();
|
|
||||||
{
|
|
||||||
let data = f.testwl.get_surface_data(t_id).unwrap();
|
|
||||||
assert!(
|
|
||||||
matches!(data.role, Some(testwl::SurfaceRole::Toplevel(_))),
|
|
||||||
"surface role: {:?}",
|
|
||||||
data.role
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
f.testwl.configure_toplevel(t_id, 100, 100, vec![]);
|
}
|
||||||
f.testwl.focus_toplevel(t_id);
|
|
||||||
f.run();
|
|
||||||
|
|
||||||
{
|
#[track_caller]
|
||||||
let data = &f.connection().windows[&window];
|
fn check_output_position_event_xdg(
|
||||||
assert_eq!(data.dims.x, 500);
|
xdg_out: &TestObject<ZxdgOutputV1>,
|
||||||
assert_eq!(data.dims.y, 100);
|
out: &TestObject<WlOutput>,
|
||||||
|
pos: (i32, i32),
|
||||||
|
goo_updated: bool,
|
||||||
|
) {
|
||||||
|
let mut done = false;
|
||||||
|
let events = std::mem::take(&mut *xdg_out.data.events.lock().unwrap())
|
||||||
|
.into_iter()
|
||||||
|
.rev();
|
||||||
|
for event in events {
|
||||||
|
if let zxdg_output_v1::Event::LogicalPosition { x, y } = event {
|
||||||
|
assert_eq!(pos, (x, y));
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
assert!(done, "Did not get zxdg_output_v1 logical_position");
|
||||||
let popup = Window::new(2);
|
let events = std::mem::take(&mut *out.data.events.lock().unwrap());
|
||||||
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();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
data.popup().positioner_state.offset,
|
events
|
||||||
testwl::Vec2 { x: 10, y: 10 }
|
.into_iter()
|
||||||
);
|
.filter(|e| matches!(*e, wl_output::Event::Done))
|
||||||
|
.count(),
|
||||||
f.satellite.unmap_window(popup);
|
goo_updated as usize,
|
||||||
p_surface.obj.destroy();
|
"Did not get expected wl_output done event"
|
||||||
f.run();
|
|
||||||
|
|
||||||
let (buffer, surface) = comp.create_surface();
|
|
||||||
f.map_window(&comp, popup, &surface.obj, &buffer);
|
|
||||||
f.run();
|
|
||||||
let p_id = f.testwl.last_created_surface_id().unwrap();
|
|
||||||
f.testwl.move_surface_to_output(p_id, &output);
|
|
||||||
f.testwl.configure_popup(p_id);
|
|
||||||
f.run();
|
|
||||||
let data = f.testwl.get_surface_data(p_id).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
data.popup().positioner_state.offset,
|
|
||||||
testwl::Vec2 { x: 10, y: 10 }
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn output_offset_change() {
|
fn output_offset_one_output() {
|
||||||
|
// If there is only one output, that output is always positioned at 0x0
|
||||||
|
let (mut f, _) = TestFixture::new_with_compositor();
|
||||||
|
let (output_obj, output) = f.new_output(0, 0);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output_obj, (0, 0));
|
||||||
|
|
||||||
|
f.testwl.move_output(&output, 500, 100);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output_obj, (0, 0));
|
||||||
|
|
||||||
|
f.testwl.move_output(&output, -500, -100);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output_obj, (0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_offset_multi_output() {
|
||||||
|
// With multiple outputs, the top-most output is on the X-axis, the left-most output is on the
|
||||||
|
// Y-axis, and they always maintain relative positioning.
|
||||||
|
let (mut f, _) = TestFixture::new_with_compositor();
|
||||||
|
|
||||||
|
let (output_obj_1, output_1) = f.new_output(1000, 0);
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output_obj_1, (0, 0));
|
||||||
|
|
||||||
|
let (output_obj_2, _) = f.new_output(0, 1000);
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output_obj_1, (1000, 0));
|
||||||
|
check_output_position_event(&output_obj_2, (0, 1000));
|
||||||
|
|
||||||
|
f.testwl.move_output(&output_1, 1000, 2000);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output_obj_1, (1000, 1000));
|
||||||
|
check_output_position_event(&output_obj_2, (0, 0));
|
||||||
|
|
||||||
|
// Global output offset does not change
|
||||||
|
f.testwl.move_output(&output_1, 1000, 1000);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output_obj_1, (1000, 0));
|
||||||
|
assert!(&output_obj_2.data.events.lock().unwrap().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_offset_multi_output_xdg() {
|
||||||
|
let (mut f, _) = TestFixture::new_with_compositor();
|
||||||
|
let man = f.enable_xdg_output();
|
||||||
|
|
||||||
|
let (output_obj_1, output_1) = f.new_output(0, 0);
|
||||||
|
f.run();
|
||||||
|
std::mem::take(&mut *output_obj_1.data.events.lock().unwrap());
|
||||||
|
let output_xdg_1 = f.create_xdg_output(&man, output_obj_1.obj.clone());
|
||||||
|
f.testwl.move_xdg_output(&output_1, 1000, 0);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event_xdg(&output_xdg_1, &output_obj_1, (0, 0), true);
|
||||||
|
|
||||||
|
let (output_obj_2, output_2) = f.new_output(1000, 1000);
|
||||||
|
f.run();
|
||||||
|
std::mem::take(&mut *output_obj_2.data.events.lock().unwrap());
|
||||||
|
let output_xdg_2 = f.create_xdg_output(&man, output_obj_2.obj.clone());
|
||||||
|
f.testwl.move_xdg_output(&output_2, 0, 1000);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event_xdg(&output_xdg_1, &output_obj_1, (1000, 0), true);
|
||||||
|
check_output_position_event_xdg(&output_xdg_2, &output_obj_2, (0, 1000), true);
|
||||||
|
|
||||||
|
f.testwl.move_xdg_output(&output_1, 1000, 2000);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event_xdg(&output_xdg_1, &output_obj_1, (1000, 1000), true);
|
||||||
|
check_output_position_event_xdg(&output_xdg_2, &output_obj_2, (0, 0), true);
|
||||||
|
|
||||||
|
f.testwl.move_xdg_output(&output_1, 1000, 1000);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event_xdg(&output_xdg_1, &output_obj_1, (1000, 0), false);
|
||||||
|
assert!(output_xdg_2.data.events.lock().unwrap().is_empty());
|
||||||
|
assert!(output_obj_2.data.events.lock().unwrap().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_offset_remove_output() {
|
||||||
|
let (mut f, _) = TestFixture::new_with_compositor();
|
||||||
|
|
||||||
|
let (output_ext_c, output_ext) = f.new_output(0, 0);
|
||||||
|
let (output_main_c, _) = f.new_output(1000, 500);
|
||||||
|
f.run();
|
||||||
|
|
||||||
|
check_output_position_event(&output_ext_c, (0, 0));
|
||||||
|
check_output_position_event(&output_main_c, (1000, 500));
|
||||||
|
|
||||||
|
f.remove_output(output_ext);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output_main_c, (0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_all_outputs() {
|
||||||
|
let (mut f, _) = TestFixture::new_with_compositor();
|
||||||
|
|
||||||
|
let (_, output) = f.new_output(0, 0);
|
||||||
|
f.run();
|
||||||
|
|
||||||
|
f.remove_output(output);
|
||||||
|
f.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_offset_surface_positioning() {
|
||||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||||
|
|
||||||
|
f.new_output(0, 0);
|
||||||
|
let (_, output) = f.new_output(500, 100);
|
||||||
|
f.run();
|
||||||
|
|
||||||
|
let window = Window::new(1);
|
||||||
|
let (_, toplevel_id) = f.create_toplevel(&comp, window);
|
||||||
|
f.testwl.move_surface_to_output(toplevel_id, &output);
|
||||||
|
f.run();
|
||||||
|
|
||||||
|
let mut toplevel_pos = WindowDims {
|
||||||
|
x: 500,
|
||||||
|
y: 100,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
};
|
||||||
|
f.assert_window_dimensions(window, toplevel_id, toplevel_pos);
|
||||||
|
|
||||||
|
let popup = Window::new(2);
|
||||||
|
let (_, p_id) = f.create_popup(
|
||||||
|
&comp,
|
||||||
|
PopupBuilder::new(popup, window, toplevel_id).x(510).y(110),
|
||||||
|
);
|
||||||
|
let mut popup_dims = WindowDims {
|
||||||
|
x: 510,
|
||||||
|
y: 110,
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
};
|
||||||
|
f.testwl.move_surface_to_output(p_id, &output);
|
||||||
|
f.run();
|
||||||
|
let data = f.testwl.get_surface_data(p_id).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
data.popup().positioner_state.offset,
|
||||||
|
testwl::Vec2 { x: 10, y: 10 }
|
||||||
|
);
|
||||||
|
f.assert_window_dimensions(popup, p_id, popup_dims);
|
||||||
|
|
||||||
|
f.testwl.move_output(&output, 600, 200);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
|
||||||
|
toplevel_pos.x = 600;
|
||||||
|
toplevel_pos.y = 200;
|
||||||
|
f.assert_window_dimensions(window, toplevel_id, toplevel_pos);
|
||||||
|
let data = f.testwl.get_surface_data(p_id).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
data.popup().positioner_state.offset,
|
||||||
|
testwl::Vec2 { x: 10, y: 10 }
|
||||||
|
);
|
||||||
|
popup_dims.x = 610;
|
||||||
|
popup_dims.y = 210;
|
||||||
|
f.assert_window_dimensions(popup, p_id, popup_dims);
|
||||||
|
|
||||||
|
f.testwl.move_output(&output, -100, -200);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
|
||||||
|
toplevel_pos.x = 0;
|
||||||
|
toplevel_pos.y = 0;
|
||||||
|
f.assert_window_dimensions(window, toplevel_id, toplevel_pos);
|
||||||
|
let data = f.testwl.get_surface_data(p_id).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
data.popup().positioner_state.offset,
|
||||||
|
testwl::Vec2 { x: 10, y: 10 }
|
||||||
|
);
|
||||||
|
popup_dims.x = 10;
|
||||||
|
popup_dims.y = 10;
|
||||||
|
f.assert_window_dimensions(popup, p_id, popup_dims);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_offset_xdg_override() {
|
||||||
|
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||||
|
f.new_output(0, 0);
|
||||||
|
f.run();
|
||||||
|
|
||||||
let (output_obj, output) = f.new_output(500, 100);
|
let (output_obj, output) = f.new_output(500, 100);
|
||||||
let window = Window::new(1);
|
let window = Window::new(1);
|
||||||
let (_, id) = f.create_toplevel(&comp, window);
|
let (_, id) = f.create_toplevel(&comp, window);
|
||||||
|
|
@ -1701,13 +1877,8 @@ fn output_offset_change() {
|
||||||
};
|
};
|
||||||
test_position(&f, 500, 100);
|
test_position(&f, 500, 100);
|
||||||
|
|
||||||
f.testwl.move_output(&output, 600, 200);
|
|
||||||
f.run();
|
|
||||||
f.run();
|
|
||||||
test_position(&f, 600, 200);
|
|
||||||
|
|
||||||
let man = f.enable_xdg_output();
|
let man = f.enable_xdg_output();
|
||||||
f.create_xdg_output(&man, output_obj.obj);
|
f.create_xdg_output(&man, output_obj.obj.clone());
|
||||||
// testwl inits xdg output position to 0, and it should take priority over wl_output position
|
// testwl inits xdg output position to 0, and it should take priority over wl_output position
|
||||||
test_position(&f, 0, 0);
|
test_position(&f, 0, 0);
|
||||||
|
|
||||||
|
|
@ -1721,6 +1892,82 @@ fn output_offset_change() {
|
||||||
test_position(&f, 1000, 22);
|
test_position(&f, 1000, 22);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_offset_negative_position() {
|
||||||
|
let mut f = TestFixture::new();
|
||||||
|
std::mem::take(&mut *f.registry.data.events.lock().unwrap());
|
||||||
|
let (output, _) = f.new_output(-500, -500);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output, (0, 0));
|
||||||
|
|
||||||
|
let (output2, _) = f.new_output(0, 0);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output2, (500, 500));
|
||||||
|
assert!(output.data.events.lock().unwrap().is_empty());
|
||||||
|
|
||||||
|
let (output3, _) = f.new_output(500, 500);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output3, (1000, 1000));
|
||||||
|
assert!(output.data.events.lock().unwrap().is_empty());
|
||||||
|
assert!(output2.data.events.lock().unwrap().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_offset_negative_position_update() {
|
||||||
|
let mut f = TestFixture::new();
|
||||||
|
std::mem::take(&mut *f.registry.data.events.lock().unwrap());
|
||||||
|
|
||||||
|
let (output, _) = f.new_output(-500, -500);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output, (0, 0));
|
||||||
|
|
||||||
|
let (output2, _) = f.new_output(0, -1000);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output, (0, 500));
|
||||||
|
check_output_position_event(&output2, (500, 0));
|
||||||
|
|
||||||
|
let (output3, o3) = f.new_output(-1000, 0);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output, (500, 500));
|
||||||
|
check_output_position_event(&output2, (1000, 0));
|
||||||
|
check_output_position_event(&output3, (0, 1000));
|
||||||
|
|
||||||
|
f.testwl.move_output(&o3, 0, 0);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output, (0, 500));
|
||||||
|
check_output_position_event(&output2, (500, 0));
|
||||||
|
check_output_position_event(&output3, (500, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_offset_negative_position_update_xdg() {
|
||||||
|
let mut f = TestFixture::new();
|
||||||
|
std::mem::take(&mut *f.registry.data.events.lock().unwrap());
|
||||||
|
let xdg = f.enable_xdg_output();
|
||||||
|
|
||||||
|
let (output, _) = f.new_output(-500, -500);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output, (0, 0));
|
||||||
|
|
||||||
|
let (output2, output_s) = f.new_output(0, 0);
|
||||||
|
f.run();
|
||||||
|
std::mem::take(&mut *output2.data.events.lock().unwrap());
|
||||||
|
let xdg_output = f.create_xdg_output(&xdg, output2.obj.clone());
|
||||||
|
f.testwl.move_xdg_output(&output_s, 0, -1000);
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
check_output_position_event(&output, (0, 500));
|
||||||
|
check_output_position_event_xdg(&xdg_output, &output2, (500, 0), true);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reconfigure_popup() {
|
fn reconfigure_popup() {
|
||||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||||
|
|
@ -2025,140 +2272,6 @@ fn fullscreen_heuristic() {
|
||||||
check_fullscreen(3, true);
|
check_fullscreen(3, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn check_output_position_event(output: &TestObject<WlOutput>, x: i32, y: i32) {
|
|
||||||
let events = std::mem::take(&mut *output.data.events.lock().unwrap());
|
|
||||||
assert!(!events.is_empty());
|
|
||||||
let mut done = false;
|
|
||||||
let mut geo = false;
|
|
||||||
for event in events {
|
|
||||||
match event {
|
|
||||||
wl_output::Event::Geometry {
|
|
||||||
x: geo_x, y: geo_y, ..
|
|
||||||
} => {
|
|
||||||
assert_eq!(geo_x, x);
|
|
||||||
assert_eq!(geo_y, y);
|
|
||||||
geo = true;
|
|
||||||
}
|
|
||||||
wl_output::Event::Done => {
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(geo, "Didn't get geometry event");
|
|
||||||
assert!(done, "Didn't get done event");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn negative_output_position() {
|
|
||||||
let mut f = TestFixture::new();
|
|
||||||
std::mem::take(&mut *f.registry.data.events.lock().unwrap());
|
|
||||||
let (output, _) = f.new_output(-500, -500);
|
|
||||||
f.run();
|
|
||||||
f.run();
|
|
||||||
check_output_position_event(&output, 0, 0);
|
|
||||||
|
|
||||||
let (output2, _) = f.new_output(0, 0);
|
|
||||||
f.run();
|
|
||||||
f.run();
|
|
||||||
check_output_position_event(&output2, 500, 500);
|
|
||||||
assert!(output.data.events.lock().unwrap().is_empty());
|
|
||||||
|
|
||||||
let (output3, _) = f.new_output(500, 500);
|
|
||||||
f.run();
|
|
||||||
f.run();
|
|
||||||
check_output_position_event(&output3, 1000, 1000);
|
|
||||||
assert!(output.data.events.lock().unwrap().is_empty());
|
|
||||||
assert!(output2.data.events.lock().unwrap().is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn negative_output_position_update_offset() {
|
|
||||||
let mut f = TestFixture::new();
|
|
||||||
std::mem::take(&mut *f.registry.data.events.lock().unwrap());
|
|
||||||
|
|
||||||
let (output, _) = f.new_output(-500, -500);
|
|
||||||
f.run();
|
|
||||||
f.run();
|
|
||||||
check_output_position_event(&output, 0, 0);
|
|
||||||
|
|
||||||
let (output2, _) = f.new_output(0, -1000);
|
|
||||||
f.run();
|
|
||||||
f.run();
|
|
||||||
check_output_position_event(&output, 0, 500);
|
|
||||||
check_output_position_event(&output2, 500, 0);
|
|
||||||
|
|
||||||
let (output3, _) = f.new_output(-1000, 0);
|
|
||||||
f.run();
|
|
||||||
f.run();
|
|
||||||
check_output_position_event(&output, 500, 500);
|
|
||||||
check_output_position_event(&output2, 1000, 0);
|
|
||||||
check_output_position_event(&output3, 0, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn negative_output_xdg_position_update_offset() {
|
|
||||||
let mut f = TestFixture::new();
|
|
||||||
std::mem::take(&mut *f.registry.data.events.lock().unwrap());
|
|
||||||
let xdg = f.enable_xdg_output();
|
|
||||||
|
|
||||||
let (output, _) = f.new_output(-500, -500);
|
|
||||||
f.run();
|
|
||||||
f.run();
|
|
||||||
check_output_position_event(&output, 0, 0);
|
|
||||||
|
|
||||||
let (output2, output_s) = f.new_output(0, 0);
|
|
||||||
let xdg_output = f.create_xdg_output(&xdg, output2.obj);
|
|
||||||
f.testwl.move_xdg_output(&output_s, 0, -1000);
|
|
||||||
f.run();
|
|
||||||
f.run();
|
|
||||||
check_output_position_event(&output, 0, 500);
|
|
||||||
|
|
||||||
let mut found = false;
|
|
||||||
let mut first = false;
|
|
||||||
for event in std::mem::take(&mut *xdg_output.data.events.lock().unwrap()) {
|
|
||||||
if let zxdg_output_v1::Event::LogicalPosition { x, y } = event {
|
|
||||||
// Testwl sends a logical position event when the output is first created
|
|
||||||
// We are interested in the second one generated by satellite
|
|
||||||
if !first {
|
|
||||||
first = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
assert_eq!(x, 500);
|
|
||||||
assert_eq!(y, 0);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(found, "Did not get xdg output logical position");
|
|
||||||
found = false;
|
|
||||||
for event in std::mem::take(&mut *output2.data.events.lock().unwrap()) {
|
|
||||||
if let wl_output::Event::Done = event {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(found, "Did not get done event");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn negative_output_position_remove_offset() {
|
|
||||||
let mut f = TestFixture::new();
|
|
||||||
std::mem::take(&mut *f.registry.data.events.lock().unwrap());
|
|
||||||
|
|
||||||
let (c_output, s_output) = f.new_output(-500, -500);
|
|
||||||
f.run();
|
|
||||||
f.run();
|
|
||||||
check_output_position_event(&c_output, 0, 0);
|
|
||||||
|
|
||||||
f.testwl.move_output(&s_output, 500, 500);
|
|
||||||
f.run();
|
|
||||||
f.run();
|
|
||||||
check_output_position_event(&c_output, 500, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scaled_output_popup() {
|
fn scaled_output_popup() {
|
||||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||||
|
|
@ -2288,8 +2401,8 @@ fn fractional_scale_small_popup() {
|
||||||
{
|
{
|
||||||
let data = f.testwl.get_surface_data(toplevel_id).unwrap();
|
let data = f.testwl.get_surface_data(toplevel_id).unwrap();
|
||||||
let viewport = data.viewport.as_ref().expect("Missing viewport");
|
let viewport = data.viewport.as_ref().expect("Missing viewport");
|
||||||
assert_eq!(viewport.width, 66);
|
assert_eq!(viewport.width, 67);
|
||||||
assert_eq!(viewport.height, 66);
|
assert_eq!(viewport.height, 67);
|
||||||
}
|
}
|
||||||
|
|
||||||
let popup = Window::new(2);
|
let popup = Window::new(2);
|
||||||
|
|
@ -2725,6 +2838,63 @@ fn scaled_pointer_lock_position_hint() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn disconnected_output_rescaling() {
|
||||||
|
let mut f = TestFixture::new_pre_connect(|testwl| {
|
||||||
|
testwl.enable_fractional_scale();
|
||||||
|
});
|
||||||
|
let comp = f.compositor();
|
||||||
|
let (_, output_main) = f.new_output(0, 0);
|
||||||
|
let (_, output_ext) = f.new_output(1000, 0);
|
||||||
|
|
||||||
|
let window = Window::new(1);
|
||||||
|
let (_, id) = f.create_toplevel(&comp, window);
|
||||||
|
|
||||||
|
let surface_data = f.testwl.get_surface_data(id).expect("No surface data");
|
||||||
|
let fractional = surface_data
|
||||||
|
.fractional
|
||||||
|
.as_ref()
|
||||||
|
.expect("No fractional scale for surface");
|
||||||
|
fractional.preferred_scale(240); // 2.0 scale
|
||||||
|
f.testwl.move_surface_to_output(id, &output_main);
|
||||||
|
f.run();
|
||||||
|
|
||||||
|
let surface_data = f.testwl.get_surface_data(id).expect("No surface data");
|
||||||
|
let fractional = surface_data
|
||||||
|
.fractional
|
||||||
|
.as_ref()
|
||||||
|
.expect("No fractional scale for surface");
|
||||||
|
fractional.preferred_scale(180); // 1.5 scale
|
||||||
|
f.testwl.move_surface_to_output(id, &output_ext);
|
||||||
|
f.run();
|
||||||
|
// Multiple monitors with different scaling will select the lowest scale across monitors
|
||||||
|
assert_eq!(f.satellite.inner.new_scale, Some(1.5));
|
||||||
|
|
||||||
|
f.remove_output(output_ext);
|
||||||
|
let surface_data = f.testwl.get_surface_data(id).expect("No surface data");
|
||||||
|
let fractional = surface_data
|
||||||
|
.fractional
|
||||||
|
.as_ref()
|
||||||
|
.expect("No fractional scale for surface");
|
||||||
|
fractional.preferred_scale(120); // 1.0 scale
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
// An fractional scale change done while the surface is on a removed output is ignored
|
||||||
|
assert_eq!(f.satellite.inner.new_scale, Some(1.5));
|
||||||
|
|
||||||
|
f.testwl.move_surface_to_output(id, &output_main);
|
||||||
|
let surface_data = f.testwl.get_surface_data(id).expect("No surface data");
|
||||||
|
let fractional = surface_data
|
||||||
|
.fractional
|
||||||
|
.as_ref()
|
||||||
|
.expect("No fractional scale for surface");
|
||||||
|
fractional.preferred_scale(240); // 2.0 scale
|
||||||
|
f.run();
|
||||||
|
f.run();
|
||||||
|
// After the output is disconnected, only the 2x scale output remains, so use that scale
|
||||||
|
assert_eq!(f.satellite.inner.new_scale, Some(2.0));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn client_side_decorations() {
|
fn client_side_decorations() {
|
||||||
let (mut f, compositor) = TestFixture::new_with_compositor();
|
let (mut f, compositor) = TestFixture::new_with_compositor();
|
||||||
|
|
|
||||||
|
|
@ -333,6 +333,14 @@ impl XState {
|
||||||
time: x::CURRENT_TIME,
|
time: x::CURRENT_TIME,
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
self.connection
|
||||||
|
.send_and_check_request(&x::SetSelectionOwner {
|
||||||
|
owner: self.wm_window,
|
||||||
|
selection: self.atoms.net_wm_cm_s0,
|
||||||
|
time: x::CURRENT_TIME,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_events(&mut self, server_state: &mut super::RealServerState) {
|
pub fn handle_events(&mut self, server_state: &mut super::RealServerState) {
|
||||||
|
|
@ -723,9 +731,12 @@ impl XState {
|
||||||
wmhint_popup = motif_popup
|
wmhint_popup = motif_popup
|
||||||
&& wm_hints.is_some_and(|h| !h.acquire_input_via_wm)
|
&& wm_hints.is_some_and(|h| !h.acquire_input_via_wm)
|
||||||
&& !hints.functions.as_ref().is_some_and(|f| {
|
&& !hints.functions.as_ref().is_some_and(|f| {
|
||||||
f.contains(motif::Functions::Minimize)
|
f.intersects(
|
||||||
|| f.contains(motif::Functions::Maximize)
|
motif::Functions::Minimize
|
||||||
|| f.contains(motif::Functions::All)
|
| motif::Functions::Maximize
|
||||||
|
| motif::Functions::Resize
|
||||||
|
| motif::Functions::All,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
// If the motif hints indicate the user shouldn't be able to do anything
|
// If the motif hints indicate the user shouldn't be able to do anything
|
||||||
// to the window at all, it stands to reason it's probably a popup.
|
// to the window at all, it stands to reason it's probably a popup.
|
||||||
|
|
@ -1031,6 +1042,7 @@ xcb::atoms_struct! {
|
||||||
wm_transient_for => b"WM_TRANSIENT_FOR" only_if_exists = false,
|
wm_transient_for => b"WM_TRANSIENT_FOR" only_if_exists = false,
|
||||||
wm_state => b"WM_STATE" only_if_exists = false,
|
wm_state => b"WM_STATE" only_if_exists = false,
|
||||||
wm_s0 => b"WM_S0" only_if_exists = false,
|
wm_s0 => b"WM_S0" only_if_exists = false,
|
||||||
|
net_wm_cm_s0 => b"_NET_WM_CM_S0" only_if_exists = false,
|
||||||
wm_check => b"_NET_SUPPORTING_WM_CHECK" only_if_exists = false,
|
wm_check => b"_NET_SUPPORTING_WM_CHECK" only_if_exists = false,
|
||||||
net_wm_name => b"_NET_WM_NAME" only_if_exists = false,
|
net_wm_name => b"_NET_WM_NAME" only_if_exists = false,
|
||||||
wm_pid => b"_NET_WM_PID" only_if_exists = false,
|
wm_pid => b"_NET_WM_PID" only_if_exists = false,
|
||||||
|
|
|
||||||
|
|
@ -102,12 +102,11 @@ impl Drop for Fixture {
|
||||||
let thread = unsafe { ManuallyDrop::take(&mut self.thread) };
|
let thread = unsafe { ManuallyDrop::take(&mut self.thread) };
|
||||||
// Sending anything to the quit receiver to stop the main loop. Then we guarantee a main
|
// Sending anything to the quit receiver to stop the main loop. Then we guarantee a main
|
||||||
// thread does not use file descriptors which outlive the Fixture's BorrowedFd
|
// thread does not use file descriptors which outlive the Fixture's BorrowedFd
|
||||||
let return_ptr = Box::into_raw(Box::new(0_usize)) as usize;
|
|
||||||
// If the receiver end of the pipe closed, the main thread dropped it, which means that
|
// If the receiver end of the pipe closed, the main thread dropped it, which means that
|
||||||
// thread already terminated
|
// thread already terminated
|
||||||
if self
|
if self
|
||||||
.quit_tx
|
.quit_tx
|
||||||
.write_all(&return_ptr.to_ne_bytes())
|
.write_all(&0_i32.to_ne_bytes())
|
||||||
.is_err_and(|e| e.kind() != std::io::ErrorKind::BrokenPipe)
|
.is_err_and(|e| e.kind() != std::io::ErrorKind::BrokenPipe)
|
||||||
{
|
{
|
||||||
panic!("could not message the main thread to terminate");
|
panic!("could not message the main thread to terminate");
|
||||||
|
|
@ -319,7 +318,7 @@ impl Fixture {
|
||||||
fn create_output(&mut self, x: i32, y: i32) -> wayland_server::protocol::wl_output::WlOutput {
|
fn create_output(&mut self, x: i32, y: i32) -> wayland_server::protocol::wl_output::WlOutput {
|
||||||
self.testwl.new_output(x, y);
|
self.testwl.new_output(x, y);
|
||||||
self.wait_and_dispatch();
|
self.wait_and_dispatch();
|
||||||
self.testwl.last_created_output()
|
self.testwl.finalize_output()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2174,6 +2173,27 @@ fn popup_heuristics() {
|
||||||
&[0x1_u32, 0, 0, 0, 0, 0, 0, 0, 0],
|
&[0x1_u32, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
);
|
);
|
||||||
f.map_as_toplevel(&mut connection, battle_net);
|
f.map_as_toplevel(&mut connection, battle_net);
|
||||||
|
|
||||||
|
let wallpaper_engine = connection.new_window(connection.root, 10, 10, 50, 50, false);
|
||||||
|
connection.set_property(
|
||||||
|
wallpaper_engine,
|
||||||
|
x::ATOM_ATOM,
|
||||||
|
connection.atoms.win_type,
|
||||||
|
&[connection.atoms.win_type_normal],
|
||||||
|
);
|
||||||
|
connection.set_property(
|
||||||
|
wallpaper_engine,
|
||||||
|
connection.atoms.motif_wm_hints,
|
||||||
|
connection.atoms.motif_wm_hints,
|
||||||
|
&[0x3_u32, 0x6, 0x0, 0x0, 0x0],
|
||||||
|
);
|
||||||
|
connection.set_property(
|
||||||
|
wallpaper_engine,
|
||||||
|
connection.atoms.wm_hints,
|
||||||
|
connection.atoms.wm_hints,
|
||||||
|
&[0x1_u32, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
);
|
||||||
|
f.map_as_toplevel(&mut connection, wallpaper_engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -2233,7 +2253,7 @@ fn xsettings_fractional_scale() {
|
||||||
let mut connection = Connection::new(&f.display);
|
let mut connection = Connection::new(&f.display);
|
||||||
f.testwl.enable_xdg_output_manager();
|
f.testwl.enable_xdg_output_manager();
|
||||||
|
|
||||||
let output = f.testwl.last_created_output();
|
let output = f.testwl.finalize_output();
|
||||||
|
|
||||||
let window = connection.new_window(connection.root, 0, 0, 20, 20, false);
|
let window = connection.new_window(connection.root, 0, 0, 20, 20, false);
|
||||||
let surface = f.map_as_toplevel(&mut connection, window);
|
let surface = f.map_as_toplevel(&mut connection, window);
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,7 @@ struct DataSourceData {
|
||||||
struct Output {
|
struct Output {
|
||||||
name: String,
|
name: String,
|
||||||
xdg: Option<ZxdgOutputV1>,
|
xdg: Option<ZxdgOutputV1>,
|
||||||
|
global_id: Option<GlobalId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct KeyboardState {
|
struct KeyboardState {
|
||||||
|
|
@ -266,6 +267,8 @@ struct State {
|
||||||
last_surface_id: Option<SurfaceId>,
|
last_surface_id: Option<SurfaceId>,
|
||||||
created_surfaces: Vec<SurfaceId>,
|
created_surfaces: Vec<SurfaceId>,
|
||||||
last_output: Option<WlOutput>,
|
last_output: Option<WlOutput>,
|
||||||
|
last_output_global: Option<GlobalId>,
|
||||||
|
output_counter: u32,
|
||||||
callbacks: Vec<WlCallback>,
|
callbacks: Vec<WlCallback>,
|
||||||
seat: Option<WlSeat>,
|
seat: Option<WlSeat>,
|
||||||
pointer: Option<PointerState>,
|
pointer: Option<PointerState>,
|
||||||
|
|
@ -296,6 +299,8 @@ impl Default for State {
|
||||||
begin: Instant::now(),
|
begin: Instant::now(),
|
||||||
last_surface_id: None,
|
last_surface_id: None,
|
||||||
last_output: None,
|
last_output: None,
|
||||||
|
last_output_global: None,
|
||||||
|
output_counter: 0,
|
||||||
callbacks: Vec::new(),
|
callbacks: Vec::new(),
|
||||||
seat: None,
|
seat: None,
|
||||||
pointer: None,
|
pointer: None,
|
||||||
|
|
@ -572,13 +577,15 @@ impl Server {
|
||||||
&self.state.created_surfaces
|
&self.state.created_surfaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finish the initialization of an output created by `new_output`.
|
||||||
|
/// This function must be called after the globals have been dispatched in order to use the
|
||||||
|
/// output on the server side created by `new_output` (this function's return value).
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn last_created_output(&self) -> WlOutput {
|
pub fn finalize_output(&mut self) -> WlOutput {
|
||||||
self.state
|
let output_s = self.state.last_output.take().expect("No new outputs");
|
||||||
.last_output
|
let output_data = self.state.outputs.get_mut(&output_s).unwrap();
|
||||||
.as_ref()
|
output_data.global_id = self.state.last_output_global.take();
|
||||||
.expect("No outputs created!")
|
output_s
|
||||||
.clone()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_object<T: Resource + 'static>(
|
pub fn get_object<T: Resource + 'static>(
|
||||||
|
|
@ -845,7 +852,8 @@ impl Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_output(&mut self, x: i32, y: i32) {
|
pub fn new_output(&mut self, x: i32, y: i32) {
|
||||||
self.dh.create_global::<State, WlOutput, _>(4, (x, y));
|
self.state.last_output_global =
|
||||||
|
Some(self.dh.create_global::<State, WlOutput, _>(4, (x, y)));
|
||||||
self.display.flush_clients().unwrap();
|
self.display.flush_clients().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -877,6 +885,12 @@ impl Server {
|
||||||
self.display.flush_clients().unwrap();
|
self.display.flush_clients().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_output(&mut self, output: WlOutput) {
|
||||||
|
let output = self.state.outputs.remove(&output).unwrap();
|
||||||
|
self.dh.remove_global::<State>(output.global_id.unwrap());
|
||||||
|
self.display.flush_clients().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn enable_xdg_output_manager(&mut self) {
|
pub fn enable_xdg_output_manager(&mut self) {
|
||||||
self.dh
|
self.dh
|
||||||
.create_global::<State, ZxdgOutputManagerV1, _>(3, ());
|
.create_global::<State, ZxdgOutputManagerV1, _>(3, ());
|
||||||
|
|
@ -1126,13 +1140,19 @@ impl GlobalDispatch<WlOutput, (i32, i32)> for State {
|
||||||
"fake monitor".to_string(),
|
"fake monitor".to_string(),
|
||||||
wl_output::Transform::Normal,
|
wl_output::Transform::Normal,
|
||||||
);
|
);
|
||||||
let name = format!("WL-{}", state.outputs.len() + 1);
|
state.output_counter += 1;
|
||||||
|
let name = format!("WL-{}", state.output_counter);
|
||||||
output.name(name.clone());
|
output.name(name.clone());
|
||||||
output.mode(wl_output::Mode::Current, 1000, 1000, 0);
|
output.mode(wl_output::Mode::Current, 1000, 1000, 0);
|
||||||
output.done();
|
output.done();
|
||||||
state
|
state.outputs.insert(
|
||||||
.outputs
|
output.clone(),
|
||||||
.insert(output.clone(), Output { name, xdg: None });
|
Output {
|
||||||
|
name,
|
||||||
|
xdg: None,
|
||||||
|
global_id: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
state.last_output = Some(output);
|
state.last_output = Some(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue