use halley_config::{
ExpandedPlacementStrategy, InitialWindowClusterParticipation, InitialWindowOverlapPolicy,
InitialWindowSpawnPlacement, WindowRule,
};
use halley_core::field::NodeId;
use smithay::reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface};
use smithay::wayland::compositor::with_states;
use smithay::wayland::shell::xdg::{SurfaceCachedState, ToplevelSurface, XdgToplevelSurfaceData};
use crate::compositor::root::Halley;
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct ResolvedInitialWindowRule {
pub(crate) overlap_policy: InitialWindowOverlapPolicy,
pub(crate) spawn_placement: InitialWindowSpawnPlacement,
pub(crate) cluster_participation: InitialWindowClusterParticipation,
pub(crate) initial_size: Option<(i32, i32)>,
pub(crate) opacity: f32,
}
impl Default for ResolvedInitialWindowRule {
fn default() -> Self {
Self {
overlap_policy: InitialWindowOverlapPolicy::None,
spawn_placement: InitialWindowSpawnPlacement::Adjacent,
cluster_participation: InitialWindowClusterParticipation::Layout,
initial_size: None,
opacity: 1.0,
}
}
}
fn default_expanded_spawn_placement(st: &Halley) -> InitialWindowSpawnPlacement {
match st.runtime.tuning.placement.expanded.strategy {
ExpandedPlacementStrategy::Center => InitialWindowSpawnPlacement::Center,
ExpandedPlacementStrategy::FindEmpty => InitialWindowSpawnPlacement::Adjacent,
}
}
fn default_initial_window_rule(st: &Halley) -> ResolvedInitialWindowRule {
ResolvedInitialWindowRule {
overlap_policy: InitialWindowOverlapPolicy::None,
spawn_placement: default_expanded_spawn_placement(st),
cluster_participation: InitialWindowClusterParticipation::Layout,
initial_size: None,
opacity: 1.0,
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct InitialWindowIntent {
pub(crate) app_id: Option<String>,
pub(crate) title: Option<String>,
pub(crate) parent_node: Option<NodeId>,
pub(crate) rule: ResolvedInitialWindowRule,
pub(crate) builtin_rule: Option<BuiltinInitialWindowRule>,
pub(crate) matched_rule: bool,
pub(crate) is_transient: bool,
pub(crate) prefer_app_intent: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum BuiltinInitialWindowRule {
Dialog,
PortalDialog,
PictureInPicture,
}
impl InitialWindowIntent {
pub(crate) fn bypassed(&self) -> Self {
Self {
app_id: self.app_id.clone(),
title: self.title.clone(),
parent_node: self.parent_node,
rule: ResolvedInitialWindowRule::default(),
builtin_rule: None,
matched_rule: false,
is_transient: self.is_transient,
prefer_app_intent: false,
}
}
pub(crate) fn applied_rule_for_node(
&self,
) -> crate::compositor::spawn::state::AppliedInitialWindowRule {
let effective_spawn_placement = self.effective_spawn_placement();
let effective_overlap_policy = self.effective_overlap_policy();
crate::compositor::spawn::state::AppliedInitialWindowRule {
overlap_policy: effective_overlap_policy,
spawn_placement: self.rule.spawn_placement,
cluster_participation: self.rule.cluster_participation,
opacity: self.rule.opacity,
parent_node: self.parent_node,
suppress_reveal_pan: !matches!(
effective_spawn_placement,
InitialWindowSpawnPlacement::Adjacent
) || effective_overlap_policy != InitialWindowOverlapPolicy::None
|| self.rule.cluster_participation == InitialWindowClusterParticipation::Float,
builtin_rule: self.builtin_rule,
}
}
pub(crate) fn effective_overlap_policy(&self) -> InitialWindowOverlapPolicy {
match (self.rule.overlap_policy, self.parent_node) {
(InitialWindowOverlapPolicy::ParentOnly, None) => InitialWindowOverlapPolicy::None,
(policy, _) => policy,
}
}
pub(crate) fn effective_spawn_placement(&self) -> InitialWindowSpawnPlacement {
match self.rule.spawn_placement {
InitialWindowSpawnPlacement::Default => InitialWindowSpawnPlacement::Center,
InitialWindowSpawnPlacement::App if self.parent_node.is_some() => {
InitialWindowSpawnPlacement::Center
}
InitialWindowSpawnPlacement::App => InitialWindowSpawnPlacement::Adjacent,
placement => placement,
}
}
}
pub(crate) fn resolve_initial_window_intent(
st: &Halley,
toplevel: &ToplevelSurface,
) -> InitialWindowIntent {
resolve_initial_window_intent_for_surface(st, toplevel.wl_surface())
}
pub(crate) fn resolve_initial_window_intent_for_surface(
st: &Halley,
surface: &WlSurface,
) -> InitialWindowIntent {
let root = surface_tree_root(surface);
let is_dialog = surface_is_xdg_dialog(&root);
let default_float = is_dialog || surface_is_fixed_height(&root);
resolve_initial_window_intent_from_identity(
st,
surface_app_id(&root),
surface_title(&root),
parent_node_for_surface(st, &root),
default_float,
is_dialog,
)
}
fn resolve_initial_window_intent_from_identity(
st: &Halley,
app_id: Option<String>,
title: Option<String>,
parent_node: Option<NodeId>,
default_float: bool,
is_dialog: bool,
) -> InitialWindowIntent {
let (rule, builtin_rule) =
matching_window_rule_with_source(st, app_id.as_deref(), title.as_deref(), default_float);
InitialWindowIntent {
app_id,
title,
parent_node,
rule: rule.unwrap_or_else(|| default_initial_window_rule(st)),
builtin_rule,
matched_rule: rule.is_some(),
is_transient: parent_node.is_some() || is_dialog,
prefer_app_intent: rule
.is_some_and(|rule| matches!(rule.spawn_placement, InitialWindowSpawnPlacement::App)),
}
}
pub(crate) fn needs_deferred_rule_recheck(st: &Halley, intent: &InitialWindowIntent) -> bool {
if intent.matched_rule {
return false;
}
let missing_title = intent.title.is_none()
&& st
.runtime
.tuning
.window_rules
.iter()
.any(|rule| !rule.titles.is_empty());
let missing_app_id = intent.app_id.is_none()
&& st
.runtime
.tuning
.window_rules
.iter()
.any(|rule| !rule.app_ids.is_empty());
missing_title
|| missing_app_id
|| builtin_window_rule_may_match_later(intent.app_id.as_deref(), intent.title.as_deref())
}
fn rule_match(st: &Halley, rule: &WindowRule) -> ResolvedInitialWindowRule {
ResolvedInitialWindowRule {
overlap_policy: InitialWindowOverlapPolicy::None,
spawn_placement: match rule.spawn_placement {
InitialWindowSpawnPlacement::Default => default_expanded_spawn_placement(st),
placement => placement,
},
cluster_participation: rule.cluster_participation,
initial_size: rule
.initial_size
.map(|(width, height)| (width.max(96) as i32, height.max(72) as i32)),
opacity: rule.opacity.unwrap_or(1.0),
}
}
fn builtin_float_center_overlap_rule() -> ResolvedInitialWindowRule {
ResolvedInitialWindowRule {
overlap_policy: InitialWindowOverlapPolicy::All,
spawn_placement: InitialWindowSpawnPlacement::Center,
cluster_participation: InitialWindowClusterParticipation::Float,
initial_size: None,
opacity: 1.0,
}
}
fn portal_dialog_title_matches(title: &str) -> bool {
[
"File Upload",
"Open File",
"Save File",
"Save Image",
"Choose",
]
.into_iter()
.any(|prefix| title.starts_with(prefix))
}
fn matching_user_window_rule<'a>(
st: &'a Halley,
app_id: Option<&str>,
title: Option<&str>,
) -> Option<&'a WindowRule> {
st.runtime.tuning.window_rules.iter().find(|rule| {
let app_matches = if rule.app_ids.is_empty() {
true
} else {
app_id.is_some_and(|app_id| {
rule.app_ids
.iter()
.any(|candidate: &halley_config::WindowRulePattern| candidate.matches(app_id))
})
};
let title_matches = if rule.titles.is_empty() {
true
} else {
title.is_some_and(|title| {
rule.titles
.iter()
.any(|candidate: &halley_config::WindowRulePattern| candidate.matches(title))
})
};
app_matches && title_matches
})
}
pub(crate) fn user_window_rule_opacity_for_identity(
st: &Halley,
app_id: Option<&str>,
title: Option<&str>,
) -> f32 {
matching_user_window_rule(st, app_id, title)
.and_then(|rule| rule.opacity)
.unwrap_or(1.0)
.clamp(0.0, 1.0)
}
fn matching_builtin_window_rule(
app_id: Option<&str>,
title: Option<&str>,
default_float: bool,
) -> Option<(BuiltinInitialWindowRule, ResolvedInitialWindowRule)> {
if default_float {
return Some((
BuiltinInitialWindowRule::Dialog,
builtin_float_center_overlap_rule(),
));
}
if app_id == Some("xdg-desktop-portal-gtk") && title.is_some_and(portal_dialog_title_matches) {
return Some((
BuiltinInitialWindowRule::PortalDialog,
builtin_float_center_overlap_rule(),
));
}
if title == Some("Picture-in-Picture") {
return Some((
BuiltinInitialWindowRule::PictureInPicture,
builtin_float_center_overlap_rule(),
));
}
None
}
fn matching_window_rule_with_source(
st: &Halley,
app_id: Option<&str>,
title: Option<&str>,
default_float: bool,
) -> (
Option<ResolvedInitialWindowRule>,
Option<BuiltinInitialWindowRule>,
) {
if let Some(rule) =
matching_user_window_rule(st, app_id, title).map(|rule| rule_match(st, rule))
{
return (Some(rule), None);
}
matching_builtin_window_rule(app_id, title, default_float)
.map(|(builtin, rule)| (Some(rule), Some(builtin)))
.unwrap_or((None, None))
}
#[cfg(test)]
fn matching_window_rule(
st: &Halley,
app_id: Option<&str>,
title: Option<&str>,
default_float: bool,
) -> Option<ResolvedInitialWindowRule> {
matching_window_rule_with_source(st, app_id, title, default_float).0
}
fn builtin_window_rule_may_match_later(app_id: Option<&str>, title: Option<&str>) -> bool {
if app_id == Some("xdg-desktop-portal-gtk") && title.is_none() {
return true;
}
if app_id.is_none() && title.is_some_and(portal_dialog_title_matches) {
return true;
}
false
}
fn parent_node_for_surface(st: &Halley, surface: &WlSurface) -> Option<NodeId> {
let parent_surface = with_states(surface, |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.and_then(|data| {
data.lock()
.expect("xdg toplevel surface data")
.parent
.clone()
})
})?;
st.model.surface_to_node.get(&parent_surface.id()).copied()
}
fn fixed_height_size_requests_float(min_h: i32, max_h: i32) -> bool {
min_h > 0 && min_h == max_h
}
fn surface_is_xdg_dialog(surface: &WlSurface) -> bool {
with_states(surface, |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.is_some_and(|data| {
let guard = data.lock().expect("xdg toplevel surface data");
guard.modal || guard.parent.is_some()
})
})
}
fn surface_is_fixed_height(surface: &WlSurface) -> bool {
with_states(surface, |states| {
let mut cached = states.cached_state.get::<SurfaceCachedState>();
let current = cached.current();
fixed_height_size_requests_float(current.min_size.h, current.max_size.h)
})
}
fn surface_tree_root(surface: &WlSurface) -> WlSurface {
let mut root = surface.clone();
while let Some(parent) = smithay::wayland::compositor::get_parent(&root) {
root = parent;
}
root
}
fn surface_app_id(surface: &WlSurface) -> Option<String> {
with_states(surface, |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.and_then(|data| {
data.lock()
.expect("xdg toplevel surface data")
.app_id
.clone()
.filter(|value| !value.trim().is_empty())
})
})
}
fn surface_title(surface: &WlSurface) -> Option<String> {
with_states(surface, |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.and_then(|data| {
data.lock()
.expect("xdg toplevel surface data")
.title
.clone()
.filter(|value| !value.trim().is_empty())
})
})
}
#[cfg(test)]
mod tests {
use super::*;
use halley_config::{RuntimeTuning, WindowRulePattern};
use smithay::reexports::wayland_server::Display;
#[test]
fn first_matching_rule_wins() {
let dh = Display::<Halley>::new().expect("display").handle();
let mut tuning = RuntimeTuning::default();
tuning.window_rules = vec![
WindowRule {
app_ids: vec![WindowRulePattern::Exact("firefox".to_string())],
titles: Vec::new(),
overlap_policy: InitialWindowOverlapPolicy::All,
spawn_placement: InitialWindowSpawnPlacement::Center,
cluster_participation: InitialWindowClusterParticipation::Float,
initial_size: None,
opacity: None,
},
WindowRule {
app_ids: vec![WindowRulePattern::Exact("firefox".to_string())],
titles: Vec::new(),
overlap_policy: InitialWindowOverlapPolicy::None,
spawn_placement: InitialWindowSpawnPlacement::Adjacent,
cluster_participation: InitialWindowClusterParticipation::Layout,
initial_size: None,
opacity: None,
},
];
let state = Halley::new_for_test(&dh, tuning);
let matched = matching_window_rule(&state, Some("firefox"), None, false).expect("match");
assert_eq!(matched.overlap_policy, InitialWindowOverlapPolicy::None);
assert_eq!(matched.spawn_placement, InitialWindowSpawnPlacement::Center);
}
#[test]
fn title_match_works_without_app_id() {
let dh = Display::<Halley>::new().expect("display").handle();
let mut tuning = RuntimeTuning::default();
tuning.window_rules = vec![WindowRule {
app_ids: Vec::new(),
titles: vec![WindowRulePattern::Exact("Picture-in-Picture".to_string())],
overlap_policy: InitialWindowOverlapPolicy::All,
spawn_placement: InitialWindowSpawnPlacement::Center,
cluster_participation: InitialWindowClusterParticipation::Float,
initial_size: None,
opacity: None,
}];
let state = Halley::new_for_test(&dh, tuning);
let matched =
matching_window_rule(&state, None, Some("Picture-in-Picture"), false).expect("match");
assert_eq!(matched.spawn_placement, InitialWindowSpawnPlacement::Center);
}
#[test]
fn user_rule_overrides_builtin_pip() {
let dh = Display::<Halley>::new().expect("display").handle();
let mut tuning = RuntimeTuning::default();
tuning.window_rules = vec![WindowRule {
app_ids: Vec::new(),
titles: vec![WindowRulePattern::Exact("Picture-in-Picture".to_string())],
overlap_policy: InitialWindowOverlapPolicy::None,
spawn_placement: InitialWindowSpawnPlacement::Adjacent,
cluster_participation: InitialWindowClusterParticipation::Layout,
initial_size: None,
opacity: None,
}];
let state = Halley::new_for_test(&dh, tuning);
let matched =
matching_window_rule(&state, None, Some("Picture-in-Picture"), false).expect("match");
assert_eq!(matched.overlap_policy, InitialWindowOverlapPolicy::None);
assert_eq!(
matched.spawn_placement,
InitialWindowSpawnPlacement::Adjacent
);
assert_eq!(
matched.cluster_participation,
InitialWindowClusterParticipation::Layout
);
}
#[test]
fn builtin_portal_dialog_matches() {
let dh = Display::<Halley>::new().expect("display").handle();
let state = Halley::new_for_test(&dh, RuntimeTuning::default());
let matched = matching_window_rule(
&state,
Some("xdg-desktop-portal-gtk"),
Some("Open File"),
false,
)
.expect("match");
assert_eq!(matched.overlap_policy, InitialWindowOverlapPolicy::All);
assert_eq!(matched.spawn_placement, InitialWindowSpawnPlacement::Center);
assert_eq!(
matched.cluster_participation,
InitialWindowClusterParticipation::Float
);
}
#[test]
fn builtin_portal_save_image_dialog_matches() {
let dh = Display::<Halley>::new().expect("display").handle();
let state = Halley::new_for_test(&dh, RuntimeTuning::default());
let matched = matching_window_rule(
&state,
Some("xdg-desktop-portal-gtk"),
Some("Save Image - cow - Google Search"),
false,
)
.expect("match");
assert_eq!(matched.overlap_policy, InitialWindowOverlapPolicy::All);
assert_eq!(matched.spawn_placement, InitialWindowSpawnPlacement::Center);
assert_eq!(
matched.cluster_participation,
InitialWindowClusterParticipation::Float
);
}
#[test]
fn builtin_pip_matches() {
let dh = Display::<Halley>::new().expect("display").handle();
let state = Halley::new_for_test(&dh, RuntimeTuning::default());
let matched =
matching_window_rule(&state, None, Some("Picture-in-Picture"), false).expect("match");
assert_eq!(matched.overlap_policy, InitialWindowOverlapPolicy::All);
assert_eq!(matched.spawn_placement, InitialWindowSpawnPlacement::Center);
assert_eq!(
matched.cluster_participation,
InitialWindowClusterParticipation::Float
);
}
#[test]
fn builtin_default_float_matches() {
let dh = Display::<Halley>::new().expect("display").handle();
let state = Halley::new_for_test(&dh, RuntimeTuning::default());
let matched = matching_window_rule(&state, Some("steam"), Some("Sign in to Steam"), true)
.expect("default floating rule");
assert_eq!(matched.overlap_policy, InitialWindowOverlapPolicy::All);
assert_eq!(matched.spawn_placement, InitialWindowSpawnPlacement::Center);
assert_eq!(
matched.cluster_participation,
InitialWindowClusterParticipation::Float
);
}
#[test]
fn steam_windows_have_no_builtin_rule_without_default_float() {
let dh = Display::<Halley>::new().expect("display").handle();
let state = Halley::new_for_test(&dh, RuntimeTuning::default());
assert!(matching_window_rule(&state, Some("steam"), Some("Steam"), false).is_none());
assert!(
matching_window_rule(&state, Some("steam"), Some("Sign in to Steam"), false).is_none()
);
}
#[test]
fn app_id_and_title_match_as_and_condition() {
let dh = Display::<Halley>::new().expect("display").handle();
let mut tuning = RuntimeTuning::default();
tuning.window_rules = vec![WindowRule {
app_ids: vec![WindowRulePattern::Exact("firefox".to_string())],
titles: vec![WindowRulePattern::Exact("Picture-in-Picture".to_string())],
overlap_policy: InitialWindowOverlapPolicy::All,
spawn_placement: InitialWindowSpawnPlacement::Center,
cluster_participation: InitialWindowClusterParticipation::Float,
initial_size: None,
opacity: None,
}];
let state = Halley::new_for_test(&dh, tuning);
assert!(
matching_user_window_rule(&state, Some("firefox"), Some("Picture-in-Picture"))
.is_some()
);
assert!(matching_user_window_rule(&state, Some("firefox"), Some("Other")).is_none());
assert!(matching_user_window_rule(&state, None, Some("Picture-in-Picture")).is_none());
}
#[test]
fn regex_title_match_works() {
let dh = Display::<Halley>::new().expect("display").handle();
let mut tuning = RuntimeTuning::default();
tuning.window_rules = vec![WindowRule {
app_ids: Vec::new(),
titles: vec![WindowRulePattern::Regex(
regex::Regex::new("File Upload.*").expect("regex"),
)],
overlap_policy: InitialWindowOverlapPolicy::All,
spawn_placement: InitialWindowSpawnPlacement::Center,
cluster_participation: InitialWindowClusterParticipation::Float,
initial_size: None,
opacity: None,
}];
let state = Halley::new_for_test(&dh, tuning);
assert!(matching_window_rule(&state, None, Some("File Upload - Firefox"), false).is_some());
}
#[test]
fn user_rule_initial_size_is_resolved_and_clamped() {
let dh = Display::<Halley>::new().expect("display").handle();
let mut tuning = RuntimeTuning::default();
tuning.window_rules = vec![WindowRule {
app_ids: vec![WindowRulePattern::Exact("pavucontrol".to_string())],
titles: Vec::new(),
initial_size: Some((40, 50)),
opacity: None,
overlap_policy: InitialWindowOverlapPolicy::None,
spawn_placement: InitialWindowSpawnPlacement::Center,
cluster_participation: InitialWindowClusterParticipation::Float,
}];
let state = Halley::new_for_test(&dh, tuning);
let matched =
matching_window_rule(&state, Some("pavucontrol"), None, false).expect("size rule");
assert_eq!(matched.initial_size, Some((96, 72)));
}
#[test]
fn user_rule_opacity_is_resolved() {
let dh = Display::<Halley>::new().expect("display").handle();
let mut tuning = RuntimeTuning::default();
tuning.window_rules = vec![WindowRule {
app_ids: vec![WindowRulePattern::Exact("kitty".to_string())],
titles: Vec::new(),
initial_size: None,
opacity: Some(0.72),
overlap_policy: InitialWindowOverlapPolicy::None,
spawn_placement: InitialWindowSpawnPlacement::Adjacent,
cluster_participation: InitialWindowClusterParticipation::Layout,
}];
let state = Halley::new_for_test(&dh, tuning);
let matched =
matching_window_rule(&state, Some("kitty"), None, false).expect("opacity rule");
assert_eq!(matched.opacity, 0.72);
}
#[test]
fn builtin_xdg_dialogs_float_center_and_overlap() {
let dh = Display::<Halley>::new().expect("display").handle();
let state = Halley::new_for_test(&dh, RuntimeTuning::default());
let matched = matching_window_rule(&state, Some("steam"), Some("Properties"), true)
.expect("dialog rule");
assert_eq!(matched.overlap_policy, InitialWindowOverlapPolicy::All);
assert_eq!(matched.spawn_placement, InitialWindowSpawnPlacement::Center);
assert_eq!(
matched.cluster_participation,
InitialWindowClusterParticipation::Float
);
}
#[test]
fn user_rule_overrides_builtin_xdg_dialog_behavior() {
let dh = Display::<Halley>::new().expect("display").handle();
let mut tuning = RuntimeTuning::default();
tuning.window_rules = vec![WindowRule {
app_ids: vec![WindowRulePattern::Exact("steam".to_string())],
titles: Vec::new(),
overlap_policy: InitialWindowOverlapPolicy::None,
spawn_placement: InitialWindowSpawnPlacement::Adjacent,
cluster_participation: InitialWindowClusterParticipation::Layout,
initial_size: None,
opacity: None,
}];
let state = Halley::new_for_test(&dh, tuning);
let matched = matching_window_rule(&state, Some("steam"), Some("Properties"), true)
.expect("user rule");
assert_eq!(matched.overlap_policy, InitialWindowOverlapPolicy::None);
assert_eq!(
matched.spawn_placement,
InitialWindowSpawnPlacement::Adjacent
);
assert_eq!(
matched.cluster_participation,
InitialWindowClusterParticipation::Layout
);
}
#[test]
fn user_rule_overrides_builtin_default_float_behavior() {
let dh = Display::<Halley>::new().expect("display").handle();
let mut tuning = RuntimeTuning::default();
tuning.window_rules = vec![WindowRule {
app_ids: vec![WindowRulePattern::Exact("steam".to_string())],
titles: Vec::new(),
overlap_policy: InitialWindowOverlapPolicy::None,
spawn_placement: InitialWindowSpawnPlacement::Adjacent,
cluster_participation: InitialWindowClusterParticipation::Layout,
initial_size: None,
opacity: None,
}];
let state = Halley::new_for_test(&dh, tuning);
let matched = matching_window_rule(&state, Some("steam"), Some("Sign in to Steam"), true)
.expect("user rule");
assert_eq!(matched.overlap_policy, InitialWindowOverlapPolicy::None);
assert_eq!(
matched.spawn_placement,
InitialWindowSpawnPlacement::Adjacent
);
assert_eq!(
matched.cluster_participation,
InitialWindowClusterParticipation::Layout
);
}
#[test]
fn deferred_recheck_considers_builtin_title_rules() {
let dh = Display::<Halley>::new().expect("display").handle();
let state = Halley::new_for_test(&dh, RuntimeTuning::default());
let intent = InitialWindowIntent {
app_id: Some("xdg-desktop-portal-gtk".to_string()),
title: None,
parent_node: None,
rule: ResolvedInitialWindowRule::default(),
builtin_rule: None,
matched_rule: false,
is_transient: false,
prefer_app_intent: false,
};
assert!(needs_deferred_rule_recheck(&state, &intent));
}
#[test]
fn deferred_recheck_considers_late_user_window_rules() {
let dh = Display::<Halley>::new().expect("display").handle();
let mut tuning = RuntimeTuning::default();
tuning.window_rules = vec![WindowRule {
app_ids: vec![WindowRulePattern::Exact("firefox".to_string())],
titles: vec![WindowRulePattern::Regex(
regex::Regex::new("File Upload.*").expect("regex"),
)],
overlap_policy: InitialWindowOverlapPolicy::All,
spawn_placement: InitialWindowSpawnPlacement::Center,
cluster_participation: InitialWindowClusterParticipation::Float,
initial_size: None,
opacity: None,
}];
let state = Halley::new_for_test(&dh, tuning);
let intent = InitialWindowIntent {
app_id: Some("firefox".to_string()),
title: None,
parent_node: None,
rule: ResolvedInitialWindowRule::default(),
builtin_rule: None,
matched_rule: false,
is_transient: false,
prefer_app_intent: false,
};
assert!(needs_deferred_rule_recheck(&state, &intent));
}
#[test]
fn steam_without_title_is_not_deferred_without_builtin_rule() {
let dh = Display::<Halley>::new().expect("display").handle();
let state = Halley::new_for_test(&dh, RuntimeTuning::default());
let intent = InitialWindowIntent {
app_id: Some("steam".to_string()),
title: None,
parent_node: None,
rule: ResolvedInitialWindowRule::default(),
builtin_rule: None,
matched_rule: false,
is_transient: false,
prefer_app_intent: false,
};
assert!(!needs_deferred_rule_recheck(&state, &intent));
}
#[test]
fn fixed_height_size_requests_default_float() {
assert!(fixed_height_size_requests_float(480, 480));
assert!(!fixed_height_size_requests_float(0, 480));
assert!(!fixed_height_size_requests_float(480, 720));
}
#[test]
fn steam_game_without_title_is_not_deferred() {
let dh = Display::<Halley>::new().expect("display").handle();
let state = Halley::new_for_test(&dh, RuntimeTuning::default());
let intent = InitialWindowIntent {
app_id: Some("steam_app_264710".to_string()),
title: None,
parent_node: None,
rule: ResolvedInitialWindowRule::default(),
builtin_rule: None,
matched_rule: false,
is_transient: false,
prefer_app_intent: false,
};
assert!(!needs_deferred_rule_recheck(&state, &intent));
}
#[test]
fn generic_window_without_title_is_not_deferred() {
let dh = Display::<Halley>::new().expect("display").handle();
let state = Halley::new_for_test(&dh, RuntimeTuning::default());
let intent = InitialWindowIntent {
app_id: Some("some-game".to_string()),
title: None,
parent_node: None,
rule: ResolvedInitialWindowRule::default(),
builtin_rule: None,
matched_rule: false,
is_transient: false,
prefer_app_intent: false,
};
assert!(!needs_deferred_rule_recheck(&state, &intent));
}
#[test]
fn steam_with_title_is_not_deferred() {
let dh = Display::<Halley>::new().expect("display").handle();
let state = Halley::new_for_test(&dh, RuntimeTuning::default());
let intent = InitialWindowIntent {
app_id: Some("steam".to_string()),
title: Some("Subnautica 2".to_string()),
parent_node: None,
rule: ResolvedInitialWindowRule::default(),
builtin_rule: None,
matched_rule: false,
is_transient: false,
prefer_app_intent: false,
};
assert!(!needs_deferred_rule_recheck(&state, &intent));
}
#[test]
fn float_and_overlap_rules_suppress_reveal_pan() {
let adjacent = InitialWindowIntent {
app_id: Some("firefox".to_string()),
title: None,
parent_node: None,
rule: ResolvedInitialWindowRule {
overlap_policy: InitialWindowOverlapPolicy::All,
spawn_placement: InitialWindowSpawnPlacement::Adjacent,
cluster_participation: InitialWindowClusterParticipation::Float,
initial_size: None,
opacity: 1.0,
},
builtin_rule: None,
matched_rule: true,
is_transient: false,
prefer_app_intent: false,
};
let center = InitialWindowIntent {
rule: ResolvedInitialWindowRule {
spawn_placement: InitialWindowSpawnPlacement::Center,
..adjacent.rule
},
..adjacent.clone()
};
let layout_adjacent = InitialWindowIntent {
rule: ResolvedInitialWindowRule {
overlap_policy: InitialWindowOverlapPolicy::None,
spawn_placement: InitialWindowSpawnPlacement::Adjacent,
cluster_participation: InitialWindowClusterParticipation::Layout,
initial_size: None,
opacity: 1.0,
},
..adjacent.clone()
};
assert!(adjacent.applied_rule_for_node().suppress_reveal_pan);
assert!(center.applied_rule_for_node().suppress_reveal_pan);
assert!(!layout_adjacent.applied_rule_for_node().suppress_reveal_pan);
}
}