@@ -367,7 +367,7 @@ impl WmState { |
| 367 | 367 | self.apply_layout(); |
| 368 | 368 | std::thread::sleep(std::time::Duration::from_millis(50)); |
| 369 | 369 | self.apply_layout(); |
| 370 | | - self.fix_oversized_windows(); |
| 370 | + self.fix_oversized_windows_aggressive(); |
| 371 | 371 | self.update_borders(); |
| 372 | 372 | self.install_observers_for_all(); |
| 373 | 373 | |
@@ -562,7 +562,23 @@ impl WmState { |
| 562 | 562 | /// After layout, check for windows that overflow their tiles. |
| 563 | 563 | /// Try swapping the oversized window into the largest available tile. |
| 564 | 564 | /// If it still doesn't fit, auto-float it. |
| 565 | + /// |
| 566 | + /// `aggressive` is set during initial discovery: if the BSP layout would |
| 567 | + /// leave any window unable to fit its tile, collapse the entire workspace |
| 568 | + /// into a single root-level stack instead of producing the hybrid |
| 569 | + /// "few-tiles-plus-corner-stack" shape that arises from the staircase BSP |
| 570 | + /// shape on cold start with many windows. Normal runtime overflow keeps |
| 571 | + /// the original Phase 2 behavior — small subtree stacks are fine when the |
| 572 | + /// user has already arranged the layout. |
| 565 | 573 | pub fn fix_oversized_windows(&mut self) { |
| 574 | + self.fix_oversized_windows_with(false); |
| 575 | + } |
| 576 | + |
| 577 | + pub fn fix_oversized_windows_aggressive(&mut self) { |
| 578 | + self.fix_oversized_windows_with(true); |
| 579 | + } |
| 580 | + |
| 581 | + fn fix_oversized_windows_with(&mut self, aggressive: bool) { |
| 566 | 582 | // Give apps time to settle to their actual minimum size after apply_layout. |
| 567 | 583 | // Some apps (e.g. Messages) handle AX resize asynchronously — they ack the |
| 568 | 584 | // request but snap to their minimum size later. |
@@ -663,6 +679,10 @@ impl WmState { |
| 663 | 679 | } |
| 664 | 680 | |
| 665 | 681 | // Phase 2: Replace the smallest conflicting subtree with a stack. |
| 682 | + // In aggressive mode (cold start), collapse the entire workspace |
| 683 | + // tree into one root-level stack on the first oversized window, |
| 684 | + // instead of leaving a hybrid layout where some windows got their |
| 685 | + // own tile and the rest piled into a corner subtree. |
| 666 | 686 | loop { |
| 667 | 687 | let geometries = self.workspace_focus_geometries(ws_idx, screen_rect); |
| 668 | 688 | if geometries.is_empty() { |
@@ -692,6 +712,26 @@ impl WmState { |
| 692 | 712 | None => break, |
| 693 | 713 | }; |
| 694 | 714 | |
| 715 | + if aggressive { |
| 716 | + if self |
| 717 | + .workspaces |
| 718 | + .get_mut(ws_idx) |
| 719 | + .tree |
| 720 | + .collapse_to_root_stack(oversized_wid) |
| 721 | + { |
| 722 | + tracing::info!( |
| 723 | + id = oversized_wid, |
| 724 | + min_w, |
| 725 | + min_h, |
| 726 | + ws = ws_idx + 1, |
| 727 | + "cold-start overflow: collapsing workspace into root stack" |
| 728 | + ); |
| 729 | + self.apply_layout(); |
| 730 | + std::thread::sleep(std::time::Duration::from_millis(50)); |
| 731 | + } |
| 732 | + break; |
| 733 | + } |
| 734 | + |
| 695 | 735 | if self |
| 696 | 736 | .workspaces |
| 697 | 737 | .get_mut(ws_idx) |