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:
parent
ed86b86dc7
commit
344e5e1cf2
4 changed files with 82 additions and 62 deletions
|
@ -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> {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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)) = ¤t_output {
|
if let Some((_, output_data)) = ¤t_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 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue