1
0
Fork 0
forked from wry/wry

config: add just-mapped window criteria

This commit is contained in:
Julian Orth 2025-05-01 18:52:55 +02:00
parent e36ccd560c
commit 5f1268cada
16 changed files with 95 additions and 4 deletions

View file

@ -116,6 +116,7 @@ pub enum WindowCriterionIpc {
Urgent,
SeatFocus(Seat),
Fullscreen,
JustMapped,
}
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]

View file

@ -1662,6 +1662,7 @@ impl ConfigClient {
WindowCriterion::Urgent => WindowCriterionIpc::Urgent,
WindowCriterion::Focus(seat) => WindowCriterionIpc::SeatFocus(seat),
WindowCriterion::Fullscreen => WindowCriterionIpc::Fullscreen,
WindowCriterion::JustMapped => WindowCriterionIpc::JustMapped,
};
let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion });
get_response!(

View file

@ -255,6 +255,11 @@ pub enum WindowCriterion<'a> {
Focus(Seat),
/// Matches if the window is fullscreen.
Fullscreen,
/// Matches if the window has/hasn't just been mapped.
///
/// This is true for one iteration of the compositor's main loop immediately after the
/// window has been mapped.
JustMapped,
}
impl WindowCriterion<'_> {

View file

@ -105,7 +105,6 @@ impl AsyncEngine {
break;
}
self.now.take();
self.iteration.fetch_add(1);
let mut phase = 0;
while phase < NUM_PHASES {
self.queues[phase].swap(&mut *stash);
@ -121,6 +120,7 @@ impl AsyncEngine {
}
}
}
self.iteration.fetch_add(1);
self.yields.swap(&mut *yield_stash);
while let Some(waker) = yield_stash.pop_front() {
waker.wake();
@ -153,7 +153,7 @@ impl AsyncEngine {
self.yields.push(waker);
}
fn iteration(&self) -> u64 {
pub fn iteration(&self) -> u64 {
self.iteration.get()
}

View file

@ -18,7 +18,9 @@ use {
criteria::{
CritMatcherIds,
clm::{ClMatcherManager, handle_cl_changes, handle_cl_leaf_events},
tlm::{TlMatcherManager, handle_tl_changes, handle_tl_leaf_events},
tlm::{
TlMatcherManager, handle_tl_changes, handle_tl_just_mapped, handle_tl_leaf_events,
},
},
damage::{DamageVisualizer, visualize_damage},
dbus::Dbus,
@ -484,6 +486,11 @@ fn start_global_event_handlers(
"tl matcher leaf events",
handle_tl_leaf_events(state.clone()),
),
eng.spawn2(
"tl matcher just mapped",
Phase::Layout,
handle_tl_just_mapped(state.clone()),
),
]
}

View file

@ -2002,6 +2002,7 @@ impl ConfigProxyHandler {
WindowCriterionIpc::Urgent => mgr.urgent(),
WindowCriterionIpc::SeatFocus(seat) => mgr.seat_focus(&*self.get_seat(*seat)?),
WindowCriterionIpc::Fullscreen => mgr.fullscreen(),
WindowCriterionIpc::JustMapped => mgr.just_mapped(),
};
let cached = Rc::new(CachedCriterion {
crit: criterion.clone(),

View file

@ -15,6 +15,7 @@ use {
tlmm_client::TlmMatchClient,
tlmm_floating::TlmMatchFloating,
tlmm_fullscreen::TlmMatchFullscreen,
tlmm_just_mapped::TlmMatchJustMapped,
tlmm_kind::TlmMatchKind,
tlmm_seat_focus::TlmMatchSeatFocus,
tlmm_string::{TlmMatchAppId, TlmMatchTitle},
@ -49,6 +50,7 @@ bitflags! {
TL_CHANGED_URGENT = 1 << 6,
TL_CHANGED_SEAT_FOCI = 1 << 7,
TL_CHANGED_FULLSCREEN = 1 << 8,
TL_CHANGED_JUST_MAPPED = 1 << 9,
}
type TlmFixedRootMatcher<T> = FixedRootMatcher<ToplevelData, T>;
@ -57,11 +59,13 @@ pub struct TlMatcherManager {
ids: Rc<CritMatcherIds>,
changes: AsyncQueue<Rc<dyn ToplevelNode>>,
leaf_events: Rc<AsyncQueue<CritLeafEvent<ToplevelData>>>,
handle_just_mapped: AsyncQueue<Rc<dyn ToplevelNode>>,
constant: TlmFixedRootMatcher<CritMatchConstant<ToplevelData>>,
floating: TlmFixedRootMatcher<TlmMatchFloating>,
visible: TlmFixedRootMatcher<TlmMatchVisible>,
urgent: TlmFixedRootMatcher<TlmMatchUrgent>,
fullscreen: TlmFixedRootMatcher<TlmMatchFullscreen>,
just_mapped: TlmFixedRootMatcher<TlmMatchJustMapped>,
matchers: Rc<RootMatchers>,
}
@ -94,6 +98,16 @@ pub async fn handle_tl_leaf_events(state: Rc<State>) {
}
}
pub async fn handle_tl_just_mapped(state: Rc<State>) {
let mgr = &state.tl_matcher_manager;
loop {
let tl = mgr.handle_just_mapped.pop().await;
let data = tl.tl_data();
data.just_mapped_scheduled.set(false);
data.property_changed(TL_CHANGED_JUST_MAPPED);
}
}
pub type TlmUpstreamNode = dyn CritUpstreamNode<ToplevelData>;
pub type TlmLeafMatcher = CritLeafMatcher<ToplevelData>;
@ -117,16 +131,19 @@ impl TlMatcherManager {
visible: bool!(TlmMatchVisible),
urgent: bool!(TlmMatchUrgent),
fullscreen: bool!(TlmMatchFullscreen),
just_mapped: bool!(TlmMatchJustMapped),
changes: Default::default(),
leaf_events: Default::default(),
ids: ids.clone(),
matchers,
handle_just_mapped: Default::default(),
}
}
pub fn clear(&self) {
self.changes.clear();
self.leaf_events.clear();
self.handle_just_mapped.clear();
}
pub fn rematch_all(&self, state: &Rc<State>) {
@ -192,6 +209,7 @@ impl TlMatcherManager {
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
fixed_conditional!(TL_CHANGED_URGENT, urgent);
fixed_conditional!(TL_CHANGED_FULLSCREEN, fullscreen);
fixed_conditional!(TL_CHANGED_JUST_MAPPED, just_mapped);
false
}
@ -263,6 +281,14 @@ impl TlMatcherManager {
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
fixed_conditional!(TL_CHANGED_URGENT, urgent);
fixed_conditional!(TL_CHANGED_FULLSCREEN, fullscreen);
fixed_conditional!(TL_CHANGED_JUST_MAPPED, just_mapped);
if changed.contains(TL_CHANGED_JUST_MAPPED)
&& data.just_mapped()
&& (self.just_mapped[false].has_downstream() || self.just_mapped[true].has_downstream())
&& !data.just_mapped_scheduled.replace(true)
{
self.handle_just_mapped.push(node);
}
}
pub fn title(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
@ -297,6 +323,10 @@ impl TlMatcherManager {
self.urgent[true].clone()
}
pub fn just_mapped(&self) -> Rc<TlmUpstreamNode> {
self.just_mapped[true].clone()
}
pub fn seat_focus(&self, seat: &WlSeatGlobal) -> Rc<TlmUpstreamNode> {
self.root(TlmMatchSeatFocus::new(seat.id()))
}

View file

@ -20,6 +20,7 @@ macro_rules! fixed_root_criterion {
pub mod tlmm_client;
pub mod tlmm_floating;
pub mod tlmm_fullscreen;
pub mod tlmm_just_mapped;
pub mod tlmm_kind;
pub mod tlmm_seat_focus;
pub mod tlmm_string;

View file

@ -0,0 +1,11 @@
use crate::{criteria::crit_graph::CritFixedRootCriterion, tree::ToplevelData};
pub struct TlmMatchJustMapped(pub bool);
fixed_root_criterion!(TlmMatchJustMapped, just_mapped);
impl CritFixedRootCriterion<ToplevelData> for TlmMatchJustMapped {
fn matches(&self, data: &ToplevelData) -> bool {
data.just_mapped()
}
}

View file

@ -103,6 +103,7 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
let data = self.tl_data();
let parent_was_none = data.parent.set(Some(parent.clone())).is_none();
if parent_was_none {
data.mapped_during_iteration.set(data.state.eng.iteration());
data.property_changed(TL_CHANGED_NEW);
}
let was_floating = data.is_floating.get();
@ -308,6 +309,7 @@ pub struct ToplevelData {
pub workspace: CloneCell<Option<Rc<WorkspaceNode>>>,
pub title: RefCell<String>,
pub parent: CloneCell<Option<Rc<dyn ContainingNode>>>,
pub mapped_during_iteration: Cell<u64>,
pub pos: Cell<Rect>,
pub desired_extents: Cell<Rect>,
pub seat_state: NodeSeatState,
@ -325,6 +327,7 @@ pub struct ToplevelData {
pub slf: Weak<dyn ToplevelNode>,
pub destroyed: CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<ToplevelData>>>,
pub changed_properties: Cell<TlMatcherChange>,
pub just_mapped_scheduled: Cell<bool>,
pub seat_foci: CopyHashMap<SeatId, ()>,
}
@ -357,6 +360,7 @@ impl ToplevelData {
workspace: Default::default(),
title: RefCell::new(title),
parent: Default::default(),
mapped_during_iteration: Cell::new(0),
pos: Default::default(),
desired_extents: Default::default(),
seat_state: Default::default(),
@ -372,6 +376,7 @@ impl ToplevelData {
slf: slf.clone(),
destroyed: Default::default(),
changed_properties: Default::default(),
just_mapped_scheduled: Cell::new(false),
seat_foci: Default::default(),
}
}
@ -696,6 +701,10 @@ impl ToplevelData {
};
(0, 0)
}
pub fn just_mapped(&self) -> bool {
self.mapped_during_iteration.get() == self.state.eng.iteration()
}
}
impl Drop for ToplevelData {

View file

@ -264,6 +264,7 @@ pub struct WindowMatch {
pub urgent: Option<bool>,
pub focused: Option<bool>,
pub fullscreen: Option<bool>,
pub just_mapped: Option<bool>,
}
#[derive(Debug, Clone)]

View file

@ -56,7 +56,7 @@ impl Parser for WindowMatchParser<'_> {
title,
title_regex,
),
(app_id, app_id_regex, floating, visible, urgent, focused, fullscreen),
(app_id, app_id_regex, floating, visible, urgent, focused, fullscreen, just_mapped),
) = ext.extract((
(
opt(str("name")),
@ -77,6 +77,7 @@ impl Parser for WindowMatchParser<'_> {
opt(bol("urgent")),
opt(bol("focused")),
opt(bol("fullscreen")),
opt(bol("just-mapped")),
),
))?;
let mut not = None;
@ -127,6 +128,7 @@ impl Parser for WindowMatchParser<'_> {
urgent: urgent.despan(),
focused: focused.despan(),
fullscreen: fullscreen.despan(),
just_mapped: just_mapped.despan(),
types,
client,
})

View file

@ -262,6 +262,7 @@ impl Rule for WindowRule {
bool!(Visible, visible);
bool!(Urgent, urgent);
bool!(Fullscreen, fullscreen);
bool!(JustMapped, just_mapped);
if let Some(value) = match_.focused {
let crit = WindowCriterion::Focus(state.persistent.seat);
let matcher = match value {

View file

@ -1815,6 +1815,10 @@
"fullscreen": {
"type": "boolean",
"description": "Matches if the window is/isn't fullscreen."
},
"just-mapped": {
"type": "boolean",
"description": "Matches if the window has/hasn't just been mapped.\n\nThis is true for one iteration of the compositor's main loop immediately after the\nwindow has been mapped.\n"
}
},
"required": []

View file

@ -4064,6 +4064,15 @@ The table has the following fields:
The value of this field should be a boolean.
- `just-mapped` (optional):
Matches if the window has/hasn't just been mapped.
This is true for one iteration of the compositor's main loop immediately after the
window has been mapped.
The value of this field should be a boolean.
<a name="types-WindowMatchExactly"></a>
### `WindowMatchExactly`

View file

@ -3503,6 +3503,14 @@ WindowMatch:
kind: boolean
required: false
description: Matches if the window is/isn't fullscreen.
just-mapped:
kind: boolean
required: false
description: |
Matches if the window has/hasn't just been mapped.
This is true for one iteration of the compositor's main loop immediately after the
window has been mapped.
WindowMatchExactly: