config: add keyboard-focus window criteria
This commit is contained in:
parent
eb172e9d8c
commit
91c948b219
15 changed files with 95 additions and 4 deletions
|
|
@ -6,6 +6,7 @@ pub(crate) mod string_error;
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
client::ClientMatcher,
|
client::ClientMatcher,
|
||||||
|
input::Seat,
|
||||||
video::Mode,
|
video::Mode,
|
||||||
window::{WindowMatcher, WindowType},
|
window::{WindowMatcher, WindowType},
|
||||||
},
|
},
|
||||||
|
|
@ -113,6 +114,7 @@ pub enum WindowCriterionIpc {
|
||||||
Floating,
|
Floating,
|
||||||
Visible,
|
Visible,
|
||||||
Urgent,
|
Urgent,
|
||||||
|
SeatFocus(Seat),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
|
||||||
|
|
|
||||||
|
|
@ -1660,6 +1660,7 @@ impl ConfigClient {
|
||||||
WindowCriterion::Floating => WindowCriterionIpc::Floating,
|
WindowCriterion::Floating => WindowCriterionIpc::Floating,
|
||||||
WindowCriterion::Visible => WindowCriterionIpc::Visible,
|
WindowCriterion::Visible => WindowCriterionIpc::Visible,
|
||||||
WindowCriterion::Urgent => WindowCriterionIpc::Urgent,
|
WindowCriterion::Urgent => WindowCriterionIpc::Urgent,
|
||||||
|
WindowCriterion::Focus(seat) => WindowCriterionIpc::SeatFocus(seat),
|
||||||
};
|
};
|
||||||
let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion });
|
let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion });
|
||||||
get_response!(
|
get_response!(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use {
|
||||||
crate::{
|
crate::{
|
||||||
Axis, Direction, Workspace,
|
Axis, Direction, Workspace,
|
||||||
client::{Client, ClientCriterion},
|
client::{Client, ClientCriterion},
|
||||||
|
input::Seat,
|
||||||
},
|
},
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
std::ops::Deref,
|
std::ops::Deref,
|
||||||
|
|
@ -250,6 +251,8 @@ pub enum WindowCriterion<'a> {
|
||||||
Visible,
|
Visible,
|
||||||
/// Matches if the window has the urgency flag set.
|
/// Matches if the window has the urgency flag set.
|
||||||
Urgent,
|
Urgent,
|
||||||
|
/// Matches if the window has the keyboard focus of the seat.
|
||||||
|
Focus(Seat),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowCriterion<'_> {
|
impl WindowCriterion<'_> {
|
||||||
|
|
|
||||||
|
|
@ -2000,6 +2000,7 @@ impl ConfigProxyHandler {
|
||||||
WindowCriterionIpc::Floating => mgr.floating(),
|
WindowCriterionIpc::Floating => mgr.floating(),
|
||||||
WindowCriterionIpc::Visible => mgr.visible(),
|
WindowCriterionIpc::Visible => mgr.visible(),
|
||||||
WindowCriterionIpc::Urgent => mgr.urgent(),
|
WindowCriterionIpc::Urgent => mgr.urgent(),
|
||||||
|
WindowCriterionIpc::SeatFocus(seat) => mgr.seat_focus(&*self.get_seat(*seat)?),
|
||||||
};
|
};
|
||||||
let cached = Rc::new(CachedCriterion {
|
let cached = Rc::new(CachedCriterion {
|
||||||
crit: criterion.clone(),
|
crit: criterion.clone(),
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,13 @@ use {
|
||||||
tlmm_client::TlmMatchClient,
|
tlmm_client::TlmMatchClient,
|
||||||
tlmm_floating::TlmMatchFloating,
|
tlmm_floating::TlmMatchFloating,
|
||||||
tlmm_kind::TlmMatchKind,
|
tlmm_kind::TlmMatchKind,
|
||||||
|
tlmm_seat_focus::TlmMatchSeatFocus,
|
||||||
tlmm_string::{TlmMatchAppId, TlmMatchTitle},
|
tlmm_string::{TlmMatchAppId, TlmMatchTitle},
|
||||||
tlmm_urgent::TlmMatchUrgent,
|
tlmm_urgent::TlmMatchUrgent,
|
||||||
tlmm_visible::TlmMatchVisible,
|
tlmm_visible::TlmMatchVisible,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ifs::wl_seat::WlSeatGlobal,
|
||||||
state::State,
|
state::State,
|
||||||
tree::{NodeId, ToplevelData, ToplevelNode},
|
tree::{NodeId, ToplevelData, ToplevelNode},
|
||||||
utils::{
|
utils::{
|
||||||
|
|
@ -44,6 +46,7 @@ bitflags! {
|
||||||
TL_CHANGED_FLOATING = 1 << 4,
|
TL_CHANGED_FLOATING = 1 << 4,
|
||||||
TL_CHANGED_VISIBLE = 1 << 5,
|
TL_CHANGED_VISIBLE = 1 << 5,
|
||||||
TL_CHANGED_URGENT = 1 << 6,
|
TL_CHANGED_URGENT = 1 << 6,
|
||||||
|
TL_CHANGED_SEAT_FOCI = 1 << 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
type TlmFixedRootMatcher<T> = FixedRootMatcher<ToplevelData, T>;
|
type TlmFixedRootMatcher<T> = FixedRootMatcher<ToplevelData, T>;
|
||||||
|
|
@ -67,6 +70,7 @@ pub struct RootMatchers {
|
||||||
clients: CopyHashMap<CritMatcherId, Weak<TlmMatchClient>>,
|
clients: CopyHashMap<CritMatcherId, Weak<TlmMatchClient>>,
|
||||||
title: TlmRootMatcherMap<TlmMatchTitle>,
|
title: TlmRootMatcherMap<TlmMatchTitle>,
|
||||||
app_id: TlmRootMatcherMap<TlmMatchAppId>,
|
app_id: TlmRootMatcherMap<TlmMatchAppId>,
|
||||||
|
seat_foci: TlmRootMatcherMap<TlmMatchSeatFocus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_tl_changes(state: Rc<State>) {
|
pub async fn handle_tl_changes(state: Rc<State>) {
|
||||||
|
|
@ -129,6 +133,10 @@ impl TlMatcherManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_seat_foci(&self) -> bool {
|
||||||
|
self.matchers.seat_foci.is_not_empty()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn has_no_interest(&self, data: &ToplevelData, change: TlMatcherChange) -> bool {
|
pub fn has_no_interest(&self, data: &ToplevelData, change: TlMatcherChange) -> bool {
|
||||||
!self.has_interest(data, change)
|
!self.has_interest(data, change)
|
||||||
}
|
}
|
||||||
|
|
@ -175,6 +183,7 @@ impl TlMatcherManager {
|
||||||
}
|
}
|
||||||
conditional!(TL_CHANGED_TITLE, title);
|
conditional!(TL_CHANGED_TITLE, title);
|
||||||
conditional!(TL_CHANGED_APP_ID, app_id);
|
conditional!(TL_CHANGED_APP_ID, app_id);
|
||||||
|
conditional!(TL_CHANGED_SEAT_FOCI, seat_foci);
|
||||||
fixed_conditional!(TL_CHANGED_FLOATING, floating);
|
fixed_conditional!(TL_CHANGED_FLOATING, floating);
|
||||||
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
|
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
|
||||||
fixed_conditional!(TL_CHANGED_URGENT, urgent);
|
fixed_conditional!(TL_CHANGED_URGENT, urgent);
|
||||||
|
|
@ -244,6 +253,7 @@ impl TlMatcherManager {
|
||||||
}
|
}
|
||||||
conditional!(TL_CHANGED_TITLE, title);
|
conditional!(TL_CHANGED_TITLE, title);
|
||||||
conditional!(TL_CHANGED_APP_ID, app_id);
|
conditional!(TL_CHANGED_APP_ID, app_id);
|
||||||
|
conditional!(TL_CHANGED_SEAT_FOCI, seat_foci);
|
||||||
fixed_conditional!(TL_CHANGED_FLOATING, floating);
|
fixed_conditional!(TL_CHANGED_FLOATING, floating);
|
||||||
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
|
fixed_conditional!(TL_CHANGED_VISIBLE, visible);
|
||||||
fixed_conditional!(TL_CHANGED_URGENT, urgent);
|
fixed_conditional!(TL_CHANGED_URGENT, urgent);
|
||||||
|
|
@ -276,6 +286,10 @@ impl TlMatcherManager {
|
||||||
pub fn urgent(&self) -> Rc<TlmUpstreamNode> {
|
pub fn urgent(&self) -> Rc<TlmUpstreamNode> {
|
||||||
self.urgent[true].clone()
|
self.urgent[true].clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn seat_focus(&self, seat: &WlSeatGlobal) -> Rc<TlmUpstreamNode> {
|
||||||
|
self.root(TlmMatchSeatFocus::new(seat.id()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CritTarget for ToplevelData {
|
impl CritTarget for ToplevelData {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ macro_rules! fixed_root_criterion {
|
||||||
pub mod tlmm_client;
|
pub mod tlmm_client;
|
||||||
pub mod tlmm_floating;
|
pub mod tlmm_floating;
|
||||||
pub mod tlmm_kind;
|
pub mod tlmm_kind;
|
||||||
|
pub mod tlmm_seat_focus;
|
||||||
pub mod tlmm_string;
|
pub mod tlmm_string;
|
||||||
pub mod tlmm_urgent;
|
pub mod tlmm_urgent;
|
||||||
pub mod tlmm_visible;
|
pub mod tlmm_visible;
|
||||||
|
|
|
||||||
28
src/criteria/tlm/tlm_matchers/tlmm_seat_focus.rs
Normal file
28
src/criteria/tlm/tlm_matchers/tlmm_seat_focus.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
use crate::{
|
||||||
|
criteria::{
|
||||||
|
crit_graph::CritRootCriterion,
|
||||||
|
tlm::{RootMatchers, TlmRootMatcherMap},
|
||||||
|
},
|
||||||
|
ifs::wl_seat::SeatId,
|
||||||
|
tree::ToplevelData,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TlmMatchSeatFocus {
|
||||||
|
id: SeatId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TlmMatchSeatFocus {
|
||||||
|
pub fn new(id: SeatId) -> TlmMatchSeatFocus {
|
||||||
|
Self { id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CritRootCriterion<ToplevelData> for TlmMatchSeatFocus {
|
||||||
|
fn matches(&self, data: &ToplevelData) -> bool {
|
||||||
|
data.seat_foci.contains(&self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nodes(roots: &RootMatchers) -> Option<&TlmRootMatcherMap<Self>> {
|
||||||
|
Some(&roots.seat_foci)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
ifs::wl_seat::WlSeatGlobal, tree::Node, utils::clonecell::CloneCell,
|
criteria::tlm::TL_CHANGED_SEAT_FOCI, ifs::wl_seat::WlSeatGlobal, tree::Node,
|
||||||
xwayland::XWaylandEvent,
|
utils::clonecell::CloneCell, xwayland::XWaylandEvent,
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
@ -61,6 +61,18 @@ impl KbOwner for DefaultKbOwner {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_kb_node(&self, seat: &Rc<WlSeatGlobal>, node: Rc<dyn Node>, serial: u64) {
|
fn set_kb_node(&self, seat: &Rc<WlSeatGlobal>, node: Rc<dyn Node>, serial: u64) {
|
||||||
|
macro_rules! notify_matcher {
|
||||||
|
($node:expr, $data:ident, $block:expr) => {
|
||||||
|
if let Some(tl) = $node.clone().node_toplevel() {
|
||||||
|
let $data = tl.tl_data();
|
||||||
|
$block;
|
||||||
|
if seat.state.tl_matcher_manager.has_seat_foci() {
|
||||||
|
$data.property_changed(TL_CHANGED_SEAT_FOCI);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let old = seat.keyboard_node.get();
|
let old = seat.keyboard_node.get();
|
||||||
if old.node_id() == node.node_id() {
|
if old.node_id() == node.node_id() {
|
||||||
return;
|
return;
|
||||||
|
|
@ -70,6 +82,7 @@ impl KbOwner for DefaultKbOwner {
|
||||||
seat.state.xwayland.queue.push(XWaylandEvent::ActivateRoot);
|
seat.state.xwayland.queue.push(XWaylandEvent::ActivateRoot);
|
||||||
}
|
}
|
||||||
old.node_on_unfocus(seat);
|
old.node_on_unfocus(seat);
|
||||||
|
notify_matcher!(old, data, data.seat_foci.remove(&seat.id));
|
||||||
if old.node_seat_state().unfocus(seat) {
|
if old.node_seat_state().unfocus(seat) {
|
||||||
old.node_active_changed(false);
|
old.node_active_changed(false);
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +92,7 @@ impl KbOwner for DefaultKbOwner {
|
||||||
}
|
}
|
||||||
// log::info!("focus {}", node.node_id());
|
// log::info!("focus {}", node.node_id());
|
||||||
node.clone().node_on_focus(seat);
|
node.clone().node_on_focus(seat);
|
||||||
|
notify_matcher!(node, data, data.seat_foci.set(seat.id, ()));
|
||||||
seat.keyboard_node_serial.set(serial);
|
seat.keyboard_node_serial.set(serial);
|
||||||
seat.keyboard_node.set(node.clone());
|
seat.keyboard_node.set(node.clone());
|
||||||
seat.tablet_on_keyboard_node_change();
|
seat.tablet_on_keyboard_node_change();
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ use {
|
||||||
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
|
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
|
||||||
jay_screencast::JayScreencast,
|
jay_screencast::JayScreencast,
|
||||||
jay_toplevel::JayToplevel,
|
jay_toplevel::JayToplevel,
|
||||||
wl_seat::{NodeSeatState, collect_kb_foci, collect_kb_foci2},
|
wl_seat::{NodeSeatState, SeatId, collect_kb_foci, collect_kb_foci2},
|
||||||
wl_surface::WlSurface,
|
wl_surface::WlSurface,
|
||||||
},
|
},
|
||||||
rect::Rect,
|
rect::Rect,
|
||||||
|
|
@ -324,6 +324,7 @@ pub struct ToplevelData {
|
||||||
pub slf: Weak<dyn ToplevelNode>,
|
pub slf: Weak<dyn ToplevelNode>,
|
||||||
pub destroyed: CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<ToplevelData>>>,
|
pub destroyed: CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<ToplevelData>>>,
|
||||||
pub changed_properties: Cell<TlMatcherChange>,
|
pub changed_properties: Cell<TlMatcherChange>,
|
||||||
|
pub seat_foci: CopyHashMap<SeatId, ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelData {
|
impl ToplevelData {
|
||||||
|
|
@ -370,6 +371,7 @@ impl ToplevelData {
|
||||||
slf: slf.clone(),
|
slf: slf.clone(),
|
||||||
destroyed: Default::default(),
|
destroyed: Default::default(),
|
||||||
changed_properties: Default::default(),
|
changed_properties: Default::default(),
|
||||||
|
seat_foci: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -262,6 +262,7 @@ pub struct WindowMatch {
|
||||||
pub floating: Option<bool>,
|
pub floating: Option<bool>,
|
||||||
pub visible: Option<bool>,
|
pub visible: Option<bool>,
|
||||||
pub urgent: Option<bool>,
|
pub urgent: Option<bool>,
|
||||||
|
pub focused: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ impl Parser for WindowMatchParser<'_> {
|
||||||
title,
|
title,
|
||||||
title_regex,
|
title_regex,
|
||||||
),
|
),
|
||||||
(app_id, app_id_regex, floating, visible, urgent),
|
(app_id, app_id_regex, floating, visible, urgent, focused),
|
||||||
) = ext.extract((
|
) = ext.extract((
|
||||||
(
|
(
|
||||||
opt(str("name")),
|
opt(str("name")),
|
||||||
|
|
@ -75,6 +75,7 @@ impl Parser for WindowMatchParser<'_> {
|
||||||
opt(bol("floating")),
|
opt(bol("floating")),
|
||||||
opt(bol("visible")),
|
opt(bol("visible")),
|
||||||
opt(bol("urgent")),
|
opt(bol("urgent")),
|
||||||
|
opt(bol("focused")),
|
||||||
),
|
),
|
||||||
))?;
|
))?;
|
||||||
let mut not = None;
|
let mut not = None;
|
||||||
|
|
@ -123,6 +124,7 @@ impl Parser for WindowMatchParser<'_> {
|
||||||
floating: floating.despan(),
|
floating: floating.despan(),
|
||||||
visible: visible.despan(),
|
visible: visible.despan(),
|
||||||
urgent: urgent.despan(),
|
urgent: urgent.despan(),
|
||||||
|
focused: focused.despan(),
|
||||||
types,
|
types,
|
||||||
client,
|
client,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -261,6 +261,14 @@ impl Rule for WindowRule {
|
||||||
bool!(Floating, floating);
|
bool!(Floating, floating);
|
||||||
bool!(Visible, visible);
|
bool!(Visible, visible);
|
||||||
bool!(Urgent, urgent);
|
bool!(Urgent, urgent);
|
||||||
|
if let Some(value) = match_.focused {
|
||||||
|
let crit = WindowCriterion::Focus(state.persistent.seat);
|
||||||
|
let matcher = match value {
|
||||||
|
false => m(WindowCriterion::Not(&crit)),
|
||||||
|
true => m(crit),
|
||||||
|
};
|
||||||
|
all.push(matcher);
|
||||||
|
}
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1807,6 +1807,10 @@
|
||||||
"urgent": {
|
"urgent": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Matches if the window has/hasn't the urgency flag set."
|
"description": "Matches if the window has/hasn't the urgency flag set."
|
||||||
|
},
|
||||||
|
"focused": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Matches if the window has/hasn't the keyboard focus."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
|
|
|
||||||
|
|
@ -4052,6 +4052,12 @@ The table has the following fields:
|
||||||
|
|
||||||
The value of this field should be a boolean.
|
The value of this field should be a boolean.
|
||||||
|
|
||||||
|
- `focused` (optional):
|
||||||
|
|
||||||
|
Matches if the window has/hasn't the keyboard focus.
|
||||||
|
|
||||||
|
The value of this field should be a boolean.
|
||||||
|
|
||||||
|
|
||||||
<a name="types-WindowMatchExactly"></a>
|
<a name="types-WindowMatchExactly"></a>
|
||||||
### `WindowMatchExactly`
|
### `WindowMatchExactly`
|
||||||
|
|
|
||||||
|
|
@ -3495,6 +3495,10 @@ WindowMatch:
|
||||||
kind: boolean
|
kind: boolean
|
||||||
required: false
|
required: false
|
||||||
description: Matches if the window has/hasn't the urgency flag set.
|
description: Matches if the window has/hasn't the urgency flag set.
|
||||||
|
focused:
|
||||||
|
kind: boolean
|
||||||
|
required: false
|
||||||
|
description: Matches if the window has/hasn't the keyboard focus.
|
||||||
|
|
||||||
|
|
||||||
WindowMatchExactly:
|
WindowMatchExactly:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue