wayland: Fix window bounds restoration (#12581)

Fixes multiple issues that prevented window bounds restoration to not
work on Wayland.

Note: Since the display uuid depends on the `wl_output.name` field, this
only works properly on KDE 5.26+ or Gnome 44+ ([kwin
commit](330a02d862),
[mutter](7e838b1115)).

Release Notes:

- N/A
This commit is contained in:
apricotbucket28 2024-06-03 13:27:01 -03:00 committed by GitHub
parent ed86b86dc7
commit 344e5e1cf2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 82 additions and 62 deletions

View file

@ -712,7 +712,7 @@ impl Size<Length> {
/// assert_eq!(bounds.origin, origin); /// assert_eq!(bounds.origin, origin);
/// assert_eq!(bounds.size, size); /// assert_eq!(bounds.size, size);
/// ``` /// ```
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)] #[derive(Refineable, Clone, Default, Debug, Eq, PartialEq, Hash)]
#[refineable(Debug)] #[refineable(Debug)]
#[repr(C)] #[repr(C)]
pub struct Bounds<T: Clone + Default + Debug> { pub struct Bounds<T: Clone + Default + Debug> {

View file

@ -139,8 +139,9 @@ impl Globals {
} }
} }
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
pub struct InProgressOutput { pub struct InProgressOutput {
name: Option<String>,
scale: Option<i32>, scale: Option<i32>,
position: Option<Point<DevicePixels>>, position: Option<Point<DevicePixels>>,
size: Option<Size<DevicePixels>>, size: Option<Size<DevicePixels>>,
@ -151,6 +152,7 @@ impl InProgressOutput {
if let Some((position, size)) = self.position.zip(self.size) { if let Some((position, size)) = self.position.zip(self.size) {
let scale = self.scale.unwrap_or(1); let scale = self.scale.unwrap_or(1);
Some(Output { Some(Output {
name: self.name.clone(),
scale, scale,
bounds: Bounds::new(position, size), bounds: Bounds::new(position, size),
}) })
@ -160,22 +162,13 @@ impl InProgressOutput {
} }
} }
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Output { pub struct Output {
pub name: Option<String>,
pub scale: i32, pub scale: i32,
pub bounds: Bounds<DevicePixels>, pub bounds: Bounds<DevicePixels>,
} }
impl Hash for Output {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_i32(self.scale);
state.write_i32(self.bounds.origin.x.0);
state.write_i32(self.bounds.origin.y.0);
state.write_i32(self.bounds.size.width.0);
state.write_i32(self.bounds.size.height.0);
}
}
pub(crate) struct WaylandClientState { pub(crate) struct WaylandClientState {
serial_tracker: SerialTracker, serial_tracker: SerialTracker,
globals: Globals, globals: Globals,
@ -337,7 +330,6 @@ impl Drop for WaylandClient {
} }
const WL_DATA_DEVICE_MANAGER_VERSION: u32 = 3; const WL_DATA_DEVICE_MANAGER_VERSION: u32 = 3;
const WL_OUTPUT_VERSION: u32 = 2;
fn wl_seat_version(version: u32) -> u32 { fn wl_seat_version(version: u32) -> u32 {
// We rely on the wl_pointer.frame event // We rely on the wl_pointer.frame event
@ -354,6 +346,20 @@ fn wl_seat_version(version: u32) -> u32 {
version.clamp(WL_SEAT_MIN_VERSION, WL_SEAT_MAX_VERSION) version.clamp(WL_SEAT_MIN_VERSION, WL_SEAT_MAX_VERSION)
} }
fn wl_output_version(version: u32) -> u32 {
const WL_OUTPUT_MIN_VERSION: u32 = 2;
const WL_OUTPUT_MAX_VERSION: u32 = 4;
if version < WL_OUTPUT_MIN_VERSION {
panic!(
"wl_output below required version: {} < {}",
version, WL_OUTPUT_MIN_VERSION
);
}
version.clamp(WL_OUTPUT_MIN_VERSION, WL_OUTPUT_MAX_VERSION)
}
impl WaylandClient { impl WaylandClient {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
let conn = Connection::connect_to_env().unwrap(); let conn = Connection::connect_to_env().unwrap();
@ -378,7 +384,7 @@ impl WaylandClient {
"wl_output" => { "wl_output" => {
let output = globals.registry().bind::<wl_output::WlOutput, _, _>( let output = globals.registry().bind::<wl_output::WlOutput, _, _>(
global.name, global.name,
WL_OUTPUT_VERSION, wl_output_version(global.version),
&qh, &qh,
(), (),
); );
@ -517,6 +523,7 @@ impl LinuxClient for WaylandClient {
.map(|(id, output)| { .map(|(id, output)| {
Rc::new(WaylandDisplay { Rc::new(WaylandDisplay {
id: id.clone(), id: id.clone(),
name: output.name.clone(),
bounds: output.bounds, bounds: output.bounds,
}) as Rc<dyn PlatformDisplay> }) as Rc<dyn PlatformDisplay>
}) })
@ -532,6 +539,7 @@ impl LinuxClient for WaylandClient {
(object_id.protocol_id() == id.0).then(|| { (object_id.protocol_id() == id.0).then(|| {
Rc::new(WaylandDisplay { Rc::new(WaylandDisplay {
id: object_id.clone(), id: object_id.clone(),
name: output.name.clone(),
bounds: output.bounds, bounds: output.bounds,
}) as Rc<dyn PlatformDisplay> }) as Rc<dyn PlatformDisplay>
}) })
@ -716,8 +724,12 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
); );
} }
"wl_output" => { "wl_output" => {
let output = let output = registry.bind::<wl_output::WlOutput, _, _>(
registry.bind::<wl_output::WlOutput, _, _>(name, WL_OUTPUT_VERSION, qh, ()); name,
wl_output_version(version),
qh,
(),
);
state state
.in_progress_outputs .in_progress_outputs
@ -821,6 +833,9 @@ impl Dispatch<wl_output::WlOutput, ()> for WaylandClientStatePtr {
}; };
match event { match event {
wl_output::Event::Name { name } => {
in_progress_output.name = Some(name);
}
wl_output::Event::Scale { factor } => { wl_output::Event::Scale { factor } => {
in_progress_output.scale = Some(factor); in_progress_output.scale = Some(factor);
} }

View file

@ -12,6 +12,7 @@ use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay};
pub(crate) struct WaylandDisplay { pub(crate) struct WaylandDisplay {
/// The ID of the wl_output object /// The ID of the wl_output object
pub id: ObjectId, pub id: ObjectId,
pub name: Option<String>,
pub bounds: Bounds<DevicePixels>, pub bounds: Bounds<DevicePixels>,
} }
@ -27,7 +28,11 @@ impl PlatformDisplay for WaylandDisplay {
} }
fn uuid(&self) -> anyhow::Result<Uuid> { fn uuid(&self) -> anyhow::Result<Uuid> {
Err(anyhow::anyhow!("Display UUID is not supported on Wayland")) if let Some(name) = &self.name {
Ok(Uuid::new_v5(&Uuid::NAMESPACE_DNS, name.as_bytes()))
} else {
Err(anyhow::anyhow!("Wayland display does not have a name"))
}
} }
fn bounds(&self) -> Bounds<DevicePixels> { fn bounds(&self) -> Bounds<DevicePixels> {

View file

@ -81,8 +81,8 @@ pub struct WaylandWindowState {
input_handler: Option<PlatformInputHandler>, input_handler: Option<PlatformInputHandler>,
decoration_state: WaylandDecorationState, decoration_state: WaylandDecorationState,
fullscreen: bool, fullscreen: bool,
restore_bounds: Bounds<DevicePixels>,
maximized: bool, maximized: bool,
windowed_bounds: Bounds<DevicePixels>,
client: WaylandClientStatePtr, client: WaylandClientStatePtr,
handle: AnyWindowHandle, handle: AnyWindowHandle,
active: bool, active: bool,
@ -158,8 +158,8 @@ impl WaylandWindowState {
input_handler: None, input_handler: None,
decoration_state: WaylandDecorationState::Client, decoration_state: WaylandDecorationState::Client,
fullscreen: false, fullscreen: false,
restore_bounds: Bounds::default(),
maximized: false, maximized: false,
windowed_bounds: options.bounds,
client, client,
appearance, appearance,
handle, handle,
@ -230,6 +230,7 @@ impl WaylandWindow {
.wm_base .wm_base
.get_xdg_surface(&surface, &globals.qh, surface.id()); .get_xdg_surface(&surface, &globals.qh, surface.id());
let toplevel = xdg_surface.get_toplevel(&globals.qh, surface.id()); let toplevel = xdg_surface.get_toplevel(&globals.qh, surface.id());
toplevel.set_min_size(200, 200);
if let Some(fractional_scale_manager) = globals.fractional_scale_manager.as_ref() { if let Some(fractional_scale_manager) = globals.fractional_scale_manager.as_ref() {
fractional_scale_manager.get_fractional_scale(&surface, &globals.qh, surface.id()); fractional_scale_manager.get_fractional_scale(&surface, &globals.qh, surface.id());
@ -348,23 +349,32 @@ impl WaylandWindowStatePtr {
pub fn handle_toplevel_event(&self, event: xdg_toplevel::Event) -> bool { pub fn handle_toplevel_event(&self, event: xdg_toplevel::Event) -> bool {
match event { match event {
xdg_toplevel::Event::Configure { xdg_toplevel::Event::Configure {
width, mut width,
height, mut height,
states, states,
} => { } => {
let width = NonZeroU32::new(width as u32);
let height = NonZeroU32::new(height as u32);
let fullscreen = states.contains(&(xdg_toplevel::State::Fullscreen as u8)); let fullscreen = states.contains(&(xdg_toplevel::State::Fullscreen as u8));
let maximized = states.contains(&(xdg_toplevel::State::Maximized as u8)); let maximized = states.contains(&(xdg_toplevel::State::Maximized as u8));
let mut state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
state.maximized = maximized; let got_unmaximized = state.maximized && !maximized;
state.fullscreen = fullscreen; state.fullscreen = fullscreen;
if fullscreen || maximized { state.maximized = maximized;
state.restore_bounds = state.bounds.map(|p| DevicePixels(p as i32));
if got_unmaximized {
width = state.windowed_bounds.size.width.0;
height = state.windowed_bounds.size.height.0;
} else if width != 0 && height != 0 && !fullscreen && !maximized {
state.windowed_bounds = Bounds {
origin: Point::default(),
size: size(width.into(), height.into()),
};
} }
let width = NonZeroU32::new(width as u32);
let height = NonZeroU32::new(height as u32);
drop(state); drop(state);
self.resize(width, height); self.resize(width, height);
self.set_fullscreen(fullscreen);
false false
} }
@ -393,49 +403,44 @@ impl WaylandWindowStatePtr {
) { ) {
let mut state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
// We use `WpFractionalScale` instead to set the scale if it's available
if state.globals.fractional_scale_manager.is_some() {
return;
}
match event { match event {
wl_surface::Event::Enter { output } => { wl_surface::Event::Enter { output } => {
// We use `PreferredBufferScale` instead to set the scale if it's available
if state.surface.version() >= wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
return;
}
let id = output.id(); let id = output.id();
let Some(output) = outputs.get(&id) else { let Some(output) = outputs.get(&id) else {
return; return;
}; };
state.outputs.insert(id, *output); state.outputs.insert(id, output.clone());
let scale = primary_output_scale(&mut state); let scale = primary_output_scale(&mut state);
state.surface.set_buffer_scale(scale); // We use `PreferredBufferScale` instead to set the scale if it's available
drop(state); if state.surface.version() < wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
self.rescale(scale as f32); state.surface.set_buffer_scale(scale);
drop(state);
self.rescale(scale as f32);
}
} }
wl_surface::Event::Leave { output } => { wl_surface::Event::Leave { output } => {
// We use `PreferredBufferScale` instead to set the scale if it's available
if state.surface.version() >= wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
return;
}
state.outputs.remove(&output.id()); state.outputs.remove(&output.id());
let scale = primary_output_scale(&mut state); let scale = primary_output_scale(&mut state);
state.surface.set_buffer_scale(scale); // We use `PreferredBufferScale` instead to set the scale if it's available
drop(state); if state.surface.version() < wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
self.rescale(scale as f32); state.surface.set_buffer_scale(scale);
drop(state);
self.rescale(scale as f32);
}
} }
wl_surface::Event::PreferredBufferScale { factor } => { wl_surface::Event::PreferredBufferScale { factor } => {
state.surface.set_buffer_scale(factor); // We use `WpFractionalScale` instead to set the scale if it's available
drop(state); if state.globals.fractional_scale_manager.is_none() {
self.rescale(factor as f32); state.surface.set_buffer_scale(factor);
drop(state);
self.rescale(factor as f32);
}
} }
_ => {} _ => {}
} }
@ -537,11 +542,6 @@ impl WaylandWindowStatePtr {
self.set_size_and_scale(None, None, Some(scale)); self.set_size_and_scale(None, None, Some(scale));
} }
pub fn set_fullscreen(&self, fullscreen: bool) {
let mut state = self.state.borrow_mut();
state.fullscreen = fullscreen;
}
/// Notifies the window of the state of the decorations. /// Notifies the window of the state of the decorations.
/// ///
/// # Note /// # Note
@ -602,10 +602,10 @@ fn primary_output_scale(state: &mut RefMut<WaylandWindowState>) -> i32 {
for (id, output) in state.outputs.iter() { for (id, output) in state.outputs.iter() {
if let Some((_, output_data)) = &current_output { if let Some((_, output_data)) = &current_output {
if output.scale > output_data.scale { if output.scale > output_data.scale {
current_output = Some((id.clone(), *output)); current_output = Some((id.clone(), output.clone()));
} }
} else { } else {
current_output = Some((id.clone(), *output)); current_output = Some((id.clone(), output.clone()));
} }
scale = scale.max(output.scale); scale = scale.max(output.scale);
} }
@ -636,9 +636,9 @@ impl PlatformWindow for WaylandWindow {
fn window_bounds(&self) -> WindowBounds { fn window_bounds(&self) -> WindowBounds {
let state = self.borrow(); let state = self.borrow();
if state.fullscreen { if state.fullscreen {
WindowBounds::Fullscreen(state.restore_bounds) WindowBounds::Fullscreen(state.windowed_bounds)
} else if state.maximized { } else if state.maximized {
WindowBounds::Maximized(state.restore_bounds) WindowBounds::Maximized(state.windowed_bounds)
} else { } else {
WindowBounds::Windowed(state.bounds.map(|p| DevicePixels(p as i32))) WindowBounds::Windowed(state.bounds.map(|p| DevicePixels(p as i32)))
} }
@ -664,6 +664,7 @@ impl PlatformWindow for WaylandWindow {
self.borrow().display.as_ref().map(|(id, display)| { self.borrow().display.as_ref().map(|(id, display)| {
Rc::new(WaylandDisplay { Rc::new(WaylandDisplay {
id: id.clone(), id: id.clone(),
name: display.name.clone(),
bounds: display.bounds, bounds: display.bounds,
}) as Rc<dyn PlatformDisplay> }) as Rc<dyn PlatformDisplay>
}) })
@ -779,7 +780,6 @@ impl PlatformWindow for WaylandWindow {
fn toggle_fullscreen(&self) { fn toggle_fullscreen(&self) {
let mut state = self.borrow_mut(); let mut state = self.borrow_mut();
state.restore_bounds = state.bounds.map(|p| DevicePixels(p as i32));
if !state.fullscreen { if !state.fullscreen {
state.toplevel.set_fullscreen(None); state.toplevel.set_fullscreen(None);
} else { } else {