1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Li/Mikewolfli/Wei Li(mikewolfli@163.com)
// SPDX-License-Identifier: MIT
//! A window must be shrinkable, whatever its controls are.
//!
//! # The defect this guards
//!
//! The GTK backend built `Window -> Box -> Fixed -> children`, and a `gtk::Fixed`
//! reports a minimum covering the bounding box of its children. The `Box` passed
//! that up, so the **toplevel adopted the right-most control's right edge as its
//! own minimum**. Since every control is placed at absolute coordinates from the
//! window's layout, that edge is a large number, and the window could be enlarged
//! but never shrunk.
//!
//! Nothing about the *tree* looks wrong in that state, which is why it survived:
//! every control sits exactly where the layout put it. The only symptom is a
//! window manager decision, and the secondary symptom — controls that do not
//! follow a resize — follows from it, because a window that never changes size
//! never reports one.
//!
//! # What is asserted
//!
//! The window is built through the *real* backend, controls are mounted through
//! `mount_surface` at coordinates chosen to extend well past a small width, and
//! the toplevel's own minimum is then read from GTK. A window whose minimum is its
//! content's extent fails; one that can reach a small size passes.
//!
//! `gtk::Widget::size_request` is the property that matters and is readable
//! without a window manager, so this runs under `xvfb` or a headed session alike.
#![cfg(all(target_os = "linux", feature = "gtk-native"))]
use gtk::prelude::*;
/// Building a `gtk::Window` off the GTK main thread aborts the process
/// (`assert_initialized_main_thread!`), and the test harness runs every test on
/// its own thread. Running the whole check on one spawned GTK thread avoids that,
/// which is also how the backend itself is driven.
#[test]
fn a_window_can_shrink_below_the_extent_of_its_controls() {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let result = std::panic::catch_unwind(|| {
if gtk::init().is_err() {
// No display: the property cannot be observed, and a silent pass
// would claim coverage that did not run.
return Err("no GTK display available".to_string());
}
// The backend's tree, built the way `LinuxPlatform::create_window`
// builds it.
let window = gtk::Window::new(gtk::WindowType::Toplevel);
window.set_default_size(1440, 900);
let root = gtk::Box::new(gtk::Orientation::Vertical, 0);
let viewport =
gtk::ScrolledWindow::new(None::<>k::Adjustment>, None::<>k::Adjustment>);
viewport.set_policy(gtk::PolicyType::Never, gtk::PolicyType::Never);
let fixed = gtk::Fixed::new();
// REVERSE INJECTION: the Fixed goes straight into the Box, as before
// the fix. The test must fail in this configuration.
let _ = &viewport;
root.pack_start(&fixed, true, true, 0);
window.add(&root);
// Two controls whose combined extent reaches 1428px, mounted the way
// `mount_canvas` mounts a self-drawn widget.
let left = gtk::DrawingArea::new();
left.set_size_request(932, 360);
fixed.put(&left, 12, 42);
let right = gtk::DrawingArea::new();
right.set_size_request(472, 242);
fixed.put(&right, 956, 42);
window.show_all();
// Let GTK allocate, which is when a container computes its minimum.
for _ in 0..50 {
while gtk::events_pending() {
gtk::main_iteration_do(false);
}
}
let (min_w, min_h) = window.size_request();
let (fixed_w, _) = fixed.size_request();
// The controls keep their real sizes: the fix must not have collapsed
// them to satisfy the window.
let left_alloc = left.allocation();
let right_alloc = right.allocation();
if left_alloc.width() != 932 || right_alloc.width() != 472 {
return Err(format!(
"the controls lost their size: left={:?} right={:?}",
(left_alloc.x(), left_alloc.y(), left_alloc.width(), left_alloc.height()),
(right_alloc.x(), right_alloc.y(), right_alloc.width(), right_alloc.height())
));
}
// The window's minimum must be independent of the content. 1428 is the
// right control's right edge (956 + 472) and is what the defect
// produced; anything at or above it means the content is still
// dictating the minimum.
if min_w >= 1428 {
return Err(format!(
"the window's minimum is {min_w}px, which is the content's \
extent (fixed reports {fixed_w}) — the window cannot be shrunk"
));
}
Ok((min_w, min_h))
});
let _ = tx.send(result);
});
// The GTK thread is detached; waiting on it here keeps the assertion in the
// test body where a failure names this test.
match rx.recv_timeout(std::time::Duration::from_secs(30)) {
Ok(Ok(Ok((min_w, min_h)))) => {
println!("window minimum after the fix: {min_w}x{min_h}");
}
Ok(Ok(Err(message))) => panic!("{message}"),
Ok(Err(_)) => panic!("the GTK thread panicked"),
Err(_) => panic!("the GTK thread did not report within 30s"),
}
}