Add XWayland support (#16)

* save dev state

* fix xwayland-satellite supervisor lifecycle, readiness and cleanup

* remove wayland scanner protocol generated files

* default xwayland enabled with path

* remove jargons

* address x11 review: park unconfirmed cleanup, never signal a reaped group, fix storm test and notify dir mode

* fix resize tests: never build Size values from negative dimensions

* add min-width/min-height window rules that replace client minimum sizes

* rename effective_size_constraints to effective_minimum_size and minor cleanups
This commit is contained in:
Samuel Onoja authored and GitHub committed 2026-10-06 19:40:22 +01:00
1 parent ed23949a95
commit a6f83689f8
42 files changed
+7162 -1333

No files matched your search

+13 -8
View File
@@ -300,16 +300,21 @@ impl Ferese {
let constraints = window
.toplevel()
.map(|top| {
with_states(top.wl_surface(), |states| {
let (minimum, maximum) = with_states(top.wl_surface(), |states| {
let mut cache = states.cached_state.get::<SurfaceCachedState>();
let state = cache.current();
SizeConstraints {
min_width: state.min_size.w.max(1) as f64,
min_height: state.min_size.h.max(1) as f64,
max_width: (state.max_size.w > 0).then_some(state.max_size.w as f64),
max_height: (state.max_size.h > 0).then_some(state.max_size.h as f64),
}
})
(
(state.min_size.w, state.min_size.h),
(state.max_size.w, state.max_size.h),
)
});
let (minimum, maximum) = self.effective_size_constraints(window, minimum, maximum);
SizeConstraints {
min_width: minimum.0.max(1) as f64,
min_height: minimum.1.max(1) as f64,
max_width: (maximum.0 > 0).then_some(maximum.0 as f64),
max_height: (maximum.1 > 0).then_some(maximum.1 as f64),
}
})
.unwrap_or_default();
let stored_size = if parent_rect.is_none() {
+9 -4
View File
@@ -574,14 +574,19 @@ impl Ferese {
let state = cached.current();
(state.min_size, state.max_size)
});
let (minimum, maximum) = self.effective_size_constraints(
window,
(minimum.w, minimum.h),
(maximum.w, maximum.h),
);
Some((
*id,
SizeConstraints {
min_width: minimum.w.max(1) as f64,
min_height: minimum.h.max(1) as f64,
max_width: (maximum.w > 0).then_some(maximum.w as f64),
max_height: (maximum.h > 0).then_some(maximum.h as f64),
min_width: minimum.0.max(1) as f64,
min_height: minimum.1.max(1) as f64,
max_width: (maximum.0 > 0).then_some(maximum.0 as f64),
max_height: (maximum.1 > 0).then_some(maximum.1 as f64),
},
))
})
@@ -463,3 +463,41 @@ fn malformed_resize_limits_disconnect_only_the_client_and_motion_survives() {
let (healthy, _wire) = window(&mut state, &mut events, 0xff112233);
assert!(state.windows.ids().contains_key(&healthy));
}
#[test]
fn window_rule_minimum_size_replaces_the_client_advertised_minimum() {
if !crate::startup_tests::private_runtime(
"state::window_lifecycle_tests::window_rule_minimum_size_replaces_the_client_advertised_minimum",
) {
return;
}
let (mut events, mut state, _) = fixture();
let (window, mut wire) = window(&mut state, &mut events, 0xff112233);
let id = state.windows.ids()[&window];
request(&mut wire, 4, 8, &[800, 600], None);
request(&mut wire, 2, 6, &[], None);
dispatch(&mut events, &mut state);
assert_eq!(state.effective_size_constraints(&window, (800, 600), (0,0)).0, (800, 600));
assert_eq!(state.window_constraints()[&id].min_width, 800.0);
assert_eq!(state.window_constraints()[&id].min_height, 600.0);
state.window_rules =
crate::config::Config::parse_source("window-rule transient=#false min-width=500 min-height=400\n")
.unwrap()
.window_rules()
.unwrap();
assert_eq!(state.effective_size_constraints(&window, (800, 600), (0,0)).0, (500, 400));
let constraints = state.window_constraints();
assert_eq!(constraints[&id].min_width, 500.0);
assert_eq!(constraints[&id].min_height, 400.0);
state.window_rules = crate::config::Config::parse_source("window-rule transient=#false min-height=400\n")
.unwrap()
.window_rules()
.unwrap();
assert_eq!(state.effective_size_constraints(&window, (800, 600), (0,0)).0, (800, 400));
state.window_rules.clear();
assert_eq!(state.effective_size_constraints(&window, (800, 600), (0,0)).0, (800, 600));
}
+38
View File
@@ -261,8 +261,42 @@ impl Ferese {
self.apply_window_rule_result(window, rule);
}
pub(crate) fn effective_size_constraints(
&self,
window: &Window,
minimum: (i32, i32),
maximum: (i32, i32),
) -> ((i32, i32), (i32, i32)) {
let Some(toplevel) = window.toplevel() else {
return (minimum, maximum);
};
let rules = &self.window_rules;
with_states(toplevel.wl_surface(), |states| {
let attributes = states
.data_map
.get::<XdgToplevelSurfaceData>()
.expect("xdg toplevel state exists")
.lock()
.expect("xdg toplevel state is not poisoned");
let rule = resolve_window_rules(
rules,
attributes.app_id.as_deref(),
attributes.title.as_deref(),
attributes.parent.is_some(),
);
(
(
rule.min_width.map_or(minimum.0, |width| width.ceil() as i32),
rule.min_height.map_or(minimum.1, |height| height.ceil() as i32),
),
maximum,
)
})
}
pub(super) fn reapply_window_rules(&mut self, old_rules: &[WindowRule]) {
let windows = self.windows.ids().keys().cloned().collect::<Vec<_>>();
let mut minimums_changed = false;
for window in windows {
let Some(toplevel) = window.toplevel() else {
continue;
@@ -278,10 +312,14 @@ impl Ferese {
});
let old = resolve_window_rules(old_rules, app_id.as_deref(), title.as_deref(), transient);
let new = resolve_window_rules(&self.window_rules, app_id.as_deref(), title.as_deref(), transient);
minimums_changed |= old.min_width != new.min_width || old.min_height != new.min_height;
if let Some(new) = crate::window_rules::live_result(old, new, transient) {
self.apply_window_rule_result(&window, new);
}
}
if minimums_changed {
self.relayout();
}
}
pub(super) fn apply_window_rule_result(&mut self, window: &Window, rule: crate::window_rules::WindowRuleResult) {