Commit 754ca2132943
tarball6 files changed+170-42
@@ -61,6 +61,9 @@ pub struct Window {
6161
pub tag: TagId,6262
pub floating: bool,6363
pub float_rect: Option<Rect>,64
+ /// `float_rect` is the layout's default, not a size the window or the65
+ /// user chose.66
+ pub float_default: bool,6467
pub fullscreen: bool,6568
pub sticky: bool,6669
pub scratchpad: Option<usize>,@@ -325,6 +328,7 @@ impl Layout {
325328
tag,326329
floating: floating.is_some(),327330
float_rect: floating,331
+ float_default: false,328332
fullscreen: false,329333
sticky: false,330334
scratchpad: None,@@ -364,6 +368,7 @@ impl Layout {
364368
if win.float_rect.is_none() {365369
let rect = centred(self.outputs[o].area, 0.6);366370
self.windows[w].float_rect = Some(rect);371
+ self.windows[w].float_default = true;367372
}368373
self.outputs[o].floating.push(w);369374
return;@@ -629,6 +634,7 @@ impl Layout {
629634
pub fn set_float_rect(&mut self, w: WindowId, rect: Rect) {630635
if let Some(win) = self.windows.get_mut(w) {631636
win.float_rect = Some(rect);637
+ win.float_default = false;632638
}633639
}634640
@@ -247,6 +247,7 @@ impl State {
247247
248248
/// Pushes the layout's target geometry into the space and to clients.249249
pub fn apply_layout(&mut self) {250
+ self.size_new_floats();250251
let now = now_ns();251252
let mut shown = Vec::new();252253
let mut tracks = Vec::new();@@ -364,9 +365,9 @@ impl State {
364365
365366
let mut mapped = Vec::new();366367
for (id, rect, kind, _) in shown {368
+ let free = kind == Kind::Floating && self.wm.get(id).is_some_and(|s| s.natural_size);367369
if let Some(window) = self.wm.window(id).cloned()368
- && !self.wm.get(id).is_some_and(|s| s.natural_size)369
- && configure(&window, rect, kind, self.wm.layout.maximized(id))370
+ && (free || configure(&window, rect, kind, self.wm.layout.maximized(id)))370371
{371372
let loc = (rect.x.round() as i32, rect.y.round() as i32);372373
let loc = Point::from(loc) - window.geometry().loc;@@ -9,7 +9,9 @@ use smithay::desktop::Window;
99
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};1010
use smithay::reexports::wayland_server::Resource;1111
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;12
-use smithay::utils::{Logical, Point, Rectangle};12
+use smithay::utils::{Logical, Point, Rectangle, Serial};13
+use smithay::wayland::compositor::with_states;14
+use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;1315
use std::time::Duration;1416
1517
impl State {@@ -108,7 +110,11 @@ impl State {
108110
let Some(w) = self.wm.get_mut(id) else {109111
return;110112
};111
- let natural = std::mem::take(&mut w.natural_size);113
+ let natural = w.natural_size && w.natural_after.is_none_or(|s| acked(&w.window, s));114
+ if natural {115
+ w.natural_size = false;116
+ w.natural_after = None;117
+ }112118
let reclassify = std::mem::take(&mut w.reclassify);113119
let size = w.window.geometry().size;114120
let sized = natural || (reclassify && self.float_if_hinted(id));@@ -159,6 +165,74 @@ impl State {
159165
true160166
}161167
168
+ /// Gives floats that landed on the layout's default rect their own size,169
+ /// as when a tiled window is toggled. The client is told to pick one, and170
+ /// its next commit after acking says which.171
+ pub fn size_new_floats(&mut self) {172
+ let stale: Vec<WindowId> = self173
+ .wm174
+ .iter()175
+ .filter(|(id, s)| {176
+ s.natural_size && !self.wm.layout.window(*id).is_some_and(|w| w.floating)177
+ })178
+ .map(|(id, _)| id)179
+ .collect();180
+ for id in stale {181
+ if let Some(s) = self.wm.get_mut(id) {182
+ s.natural_size = false;183
+ s.natural_after = None;184
+ }185
+ }186
+ let defaults: Vec<WindowId> = self187
+ .wm188
+ .layout189
+ .windows()190
+ .filter(|(_, w)| w.floating && w.float_default && w.scratchpad.is_none())191
+ .map(|(id, _)| id)192
+ .collect();193
+ for id in defaults {194
+ self.ask_natural_size(id);195
+ }196
+ }197
+198
+ fn ask_natural_size(&mut self, id: WindowId) {199
+ let Some(window) = self200
+ .wm201
+ .get(id)202
+ .filter(|s| !s.natural_size)203
+ .map(|s| s.window.clone())204
+ else {205
+ return;206
+ };207
+ let geometry = window.geometry().size;208
+ let current = (geometry.w > 0 && geometry.h > 0).then_some(geometry);209
+ let size = match (window.x11_surface(), window.toplevel()) {210
+ (Some(x), _) => crate::xwayland::fixed_size(x),211
+ (_, Some(t)) if crate::xdg::fixed_size(t).is_some() => current,212
+ (_, Some(t)) => {213
+ let serial = crate::wm::configure_free(t);214
+ if let Some(w) = self.wm.get_mut(id)215
+ && serial.is_some()216
+ {217
+ w.natural_size = true;218
+ w.natural_after = serial;219
+ return;220
+ }221
+ current222
+ }223
+ _ => None,224
+ };225
+ match size {226
+ Some(size) => self.float_at_natural_size(id, f64::from(size.w), f64::from(size.h)),227
+ None => {228
+ let rect = self.wm.layout.window(id).and_then(|w| w.float_rect);229
+ if let Some(rect) = rect {230
+ self.wm.layout.set_float_rect(id, rect);231
+ }232
+ }233
+ }234
+ }235
+162236
/// Centres a window over its parent, or else its output.163237
fn float_at_natural_size(&mut self, id: WindowId, w: f64, h: f64) {164238
let parent = self@@ -322,3 +396,17 @@ impl State {
322396
self.wm.id_of(surface)323397
}324398
}399
+400
+/// Whether the client has acknowledged the configure with `serial` or a later401
+/// one.402
+fn acked(window: &Window, serial: Serial) -> bool {403
+ let Some(t) = window.toplevel() else {404
+ return true;405
+ };406
+ with_states(t.wl_surface(), |s| {407
+ let data = s.data_map.get::<XdgToplevelSurfaceData>()?.lock().ok()?;408
+ let last = data.last_acked.as_ref()?;409
+ Some(last.serial.is_no_older_than(&serial))410
+ })411
+ .unwrap_or(false)412
+}@@ -10,11 +10,11 @@ use smithay::backend::renderer::element::solid::SolidColorBuffer;
1010
use smithay::desktop::{PopupManager, Space, Window};1111
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState;1212
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;13
-use smithay::utils::{Logical, Rectangle, Transform};13
+use smithay::utils::{Logical, Rectangle, Serial, Transform};1414
use smithay::wayland::compositor::with_states;1515
use smithay::wayland::foreign_toplevel_list::ForeignToplevelHandle;1616
use smithay::wayland::seat::WaylandFocus;17
-use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;17
+use smithay::wayland::shell::xdg::{ToplevelState, ToplevelSurface, XdgToplevelSurfaceData};1818
use std::collections::HashMap;1919
2020
pub struct Wm {@@ -58,6 +58,9 @@ pub struct WindowState {
5858
/// Configured without a size until its first buffer shows the one it5959
/// picked.6060
pub natural_size: bool,61
+ /// The configure that gave it free choice of size, whose ack the buffer62
+ /// has to follow to show that choice.63
+ pub natural_after: Option<Serial>,6164
/// Tiled by default, but its size hints are only reliable at its first6265
/// buffer and may turn it into a float.6366
pub reclassify: bool,@@ -117,6 +120,7 @@ impl Wm {
117120
placed: None,118121
awaiting_buffer: true,119122
natural_size: false,123
+ natural_after: None,120124
reclassify: false,121125
placed_by_rule,122126
list_handle: None,@@ -231,29 +235,7 @@ pub fn configure(window: &Window, rect: Rect, kind: Kind, maximized: bool) -> bo
231235
if let Some(t) = window.toplevel() {232236
t.with_pending_state(|s| {233237
s.size = Some((w, h).into());234
- let tiled = [235
- XdgState::TiledLeft,236
- XdgState::TiledRight,237
- XdgState::TiledTop,238
- XdgState::TiledBottom,239
- ];240
- for st in tiled {241
- if kind == Kind::Tiled {242
- s.states.set(st);243
- } else {244
- s.states.unset(st);245
- }246
- }247
- if kind == Kind::Fullscreen {248
- s.states.set(XdgState::Fullscreen);249
- } else {250
- s.states.unset(XdgState::Fullscreen);251
- }252
- if maximized && kind == Kind::Tiled {253
- s.states.set(XdgState::Maximized);254
- } else {255
- s.states.unset(XdgState::Maximized);256
- }238
+ set_states(s, kind, maximized);257239
});258240
if t.is_initial_configure_sent() {259241
t.send_pending_configure();@@ -262,6 +244,44 @@ pub fn configure(window: &Window, rect: Rect, kind: Kind, maximized: bool) -> bo
262244
true263245
}264246
247
+/// Lets a floating toplevel pick its own size, returning the serial of the248
+/// configure that says so. None if it was sent nothing.249
+pub fn configure_free(t: &ToplevelSurface) -> Option<Serial> {250
+ t.with_pending_state(|s| {251
+ s.size = None;252
+ set_states(s, Kind::Floating, false);253
+ });254
+ t.is_initial_configure_sent()255
+ .then(|| t.send_pending_configure())256
+ .flatten()257
+}258
+259
+fn set_states(s: &mut ToplevelState, kind: Kind, maximized: bool) {260
+ let tiled = [261
+ XdgState::TiledLeft,262
+ XdgState::TiledRight,263
+ XdgState::TiledTop,264
+ XdgState::TiledBottom,265
+ ];266
+ for st in tiled {267
+ if kind == Kind::Tiled {268
+ s.states.set(st);269
+ } else {270
+ s.states.unset(st);271
+ }272
+ }273
+ if kind == Kind::Fullscreen {274
+ s.states.set(XdgState::Fullscreen);275
+ } else {276
+ s.states.unset(XdgState::Fullscreen);277
+ }278
+ if maximized && kind == Kind::Tiled {279
+ s.states.set(XdgState::Maximized);280
+ } else {281
+ s.states.unset(XdgState::Maximized);282
+ }283
+}284
+265285
/// A window's surface, for xdg toplevels and X11 windows alike.266286
pub fn root(window: &Window) -> Option<WlSurface> {267287
window.wl_surface().map(|s| s.into_owned())@@ -4,7 +4,7 @@ use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_to
44
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;55
use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;66
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;7
-use smithay::utils::Serial;7
+use smithay::utils::{Logical, Serial, Size};88
use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler;99
use smithay::wayland::compositor::with_states;1010
use smithay::wayland::shell::xdg::{@@ -14,15 +14,24 @@ use smithay::wayland::shell::xdg::{
1414
1515
/// Dialogs, and windows that cap their size and so could never fill a tile.1616
pub fn floats(toplevel: &ToplevelSurface) -> bool {17
- let max = with_states(toplevel.wl_surface(), |s| {18
- s.cached_state19
- .get::<SurfaceCachedState>()20
- .current()21
- .max_size22
- });17
+ let (_, max) = size_hints(toplevel);2318
toplevel.parent().is_some() || (max.w > 0 && max.h > 0)2419
}2520
21
+/// The size of a window that asks for exactly one.22
+pub fn fixed_size(toplevel: &ToplevelSurface) -> Option<Size<i32, Logical>> {23
+ let (min, max) = size_hints(toplevel);24
+ (min == max && min.w > 0 && min.h > 0).then_some(min)25
+}26
+27
+fn size_hints(toplevel: &ToplevelSurface) -> (Size<i32, Logical>, Size<i32, Logical>) {28
+ with_states(toplevel.wl_surface(), |s| {29
+ let mut cached = s.cached_state.get::<SurfaceCachedState>();30
+ let current = cached.current();31
+ (current.min_size, current.max_size)32
+ })33
+}34
+2635
impl XdgShellHandler for State {2736
fn xdg_shell_state(&mut self) -> &mut XdgShellState {2837
&mut self.protocols.xdg_shell_state@@ -9,7 +9,7 @@ use smithay::reexports::calloop::generic::Generic;
99
use smithay::reexports::calloop::{Interest, Mode, PostAction, RegistrationToken};1010
use smithay::reexports::rustix::io::{FdFlags, fcntl_getfd, fcntl_setfd};1111
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;12
-use smithay::utils::{Logical, Rectangle};12
+use smithay::utils::{Logical, Rectangle, Size};1313
use smithay::wayland::xwayland_shell::{XWaylandShellHandler, XWaylandShellState};1414
use smithay::xwayland::xwm::{Reorder, ResizeEdge, WmWindowProperty, WmWindowType, XwmId};1515
use smithay::xwayland::{X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler};@@ -358,11 +358,15 @@ fn floats(window: &X11Surface) -> bool {
358358
| WmWindowType::Menu359359
)360360
);361
- let fixed = matches!(362
- (window.min_size(), window.max_size()),363
- (Some(min), Some(max)) if min == max && min.w > 0364
- );365
- kind || fixed || window.is_transient_for().is_some()361
+ kind || fixed_size(window).is_some() || window.is_transient_for().is_some()362
+}363
+364
+/// The size of a window that asks for exactly one.365
+pub fn fixed_size(window: &X11Surface) -> Option<Size<i32, Logical>> {366
+ match (window.min_size(), window.max_size()) {367
+ (Some(min), Some(max)) if min == max && min.w > 0 && min.h > 0 => Some(min),368
+ _ => None,369
+ }366370
}367371
368372
impl State {