config: add auto-focus window rule
This commit is contained in:
parent
51e752992f
commit
b1ca98b488
12 changed files with 114 additions and 10 deletions
|
|
@ -1701,6 +1701,13 @@ impl ConfigClient {
|
||||||
handler.cb = cb.clone();
|
handler.cb = cb.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_window_matcher_auto_focus(&self, matcher: WindowMatcher, auto_focus: bool) {
|
||||||
|
self.send(&ClientMessage::SetWindowMatcherAutoFocus {
|
||||||
|
matcher,
|
||||||
|
auto_focus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_window_matcher_latch_handler(
|
pub fn set_window_matcher_latch_handler(
|
||||||
&self,
|
&self,
|
||||||
matcher: WindowMatcher,
|
matcher: WindowMatcher,
|
||||||
|
|
|
||||||
|
|
@ -698,6 +698,10 @@ pub enum ClientMessage<'a> {
|
||||||
EnableWindowMatcherEvents {
|
EnableWindowMatcherEvents {
|
||||||
matcher: WindowMatcher,
|
matcher: WindowMatcher,
|
||||||
},
|
},
|
||||||
|
SetWindowMatcherAutoFocus {
|
||||||
|
matcher: WindowMatcher,
|
||||||
|
auto_focus: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,16 @@ impl WindowCriterion<'_> {
|
||||||
pub fn bind<F: FnMut(MatchedWindow) + 'static>(self, cb: F) {
|
pub fn bind<F: FnMut(MatchedWindow) + 'static>(self, cb: F) {
|
||||||
self.to_matcher().bind(cb);
|
self.to_matcher().bind(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets whether newly mapped windows that match this criterion get the keyboard focus.
|
||||||
|
///
|
||||||
|
/// If a window matches any criterion for which this is false, the window will not be
|
||||||
|
/// automatically focused.
|
||||||
|
///
|
||||||
|
/// This leaks the matcher.
|
||||||
|
pub fn set_auto_focus(self, auto_focus: bool) {
|
||||||
|
self.to_matcher().set_auto_focus(auto_focus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowMatcher {
|
impl WindowMatcher {
|
||||||
|
|
@ -312,6 +322,14 @@ impl WindowMatcher {
|
||||||
pub fn bind<F: FnMut(MatchedWindow) + 'static>(self, cb: F) {
|
pub fn bind<F: FnMut(MatchedWindow) + 'static>(self, cb: F) {
|
||||||
get!().set_window_matcher_handler(self, cb);
|
get!().set_window_matcher_handler(self, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets whether newly mapped windows that match this matcher get the keyboard focus.
|
||||||
|
///
|
||||||
|
/// If a window matches any matcher for which this is false, the window will not be
|
||||||
|
/// automatically focused.
|
||||||
|
pub fn set_auto_focus(self, auto_focus: bool) {
|
||||||
|
get!().set_window_matcher_auto_focus(self, auto_focus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MatchedWindow {
|
impl MatchedWindow {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use {
|
||||||
config::handler::ConfigProxyHandler,
|
config::handler::ConfigProxyHandler,
|
||||||
ifs::wl_seat::SeatId,
|
ifs::wl_seat::SeatId,
|
||||||
state::State,
|
state::State,
|
||||||
|
tree::ToplevelData,
|
||||||
utils::{
|
utils::{
|
||||||
clonecell::CloneCell, numcell::NumCell, ptr_ext::PtrExt,
|
clonecell::CloneCell, numcell::NumCell, ptr_ext::PtrExt,
|
||||||
toplevel_identifier::ToplevelIdentifier, unlink_on_drop::UnlinkOnDrop, xrd::xrd,
|
toplevel_identifier::ToplevelIdentifier, unlink_on_drop::UnlinkOnDrop, xrd::xrd,
|
||||||
|
|
@ -161,6 +162,13 @@ impl ConfigProxy {
|
||||||
handler.windows_to_tl_id.remove(&win);
|
handler.windows_to_tl_id.remove(&win);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn auto_focus(&self, data: &ToplevelData) -> bool {
|
||||||
|
let Some(handler) = self.handler.get() else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
handler.auto_focus(data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for ConfigProxy {
|
impl Drop for ConfigProxy {
|
||||||
|
|
@ -224,6 +232,7 @@ impl ConfigProxy {
|
||||||
window_matcher_cache: Default::default(),
|
window_matcher_cache: Default::default(),
|
||||||
window_matcher_leafs: Default::default(),
|
window_matcher_leafs: Default::default(),
|
||||||
window_matcher_std_kinds: state.tl_matcher_manager.kind(window::CLIENT_WINDOW),
|
window_matcher_std_kinds: state.tl_matcher_manager.kind(window::CLIENT_WINDOW),
|
||||||
|
window_matcher_no_auto_focus: Default::default(),
|
||||||
});
|
});
|
||||||
let init_msg = bincode_ops()
|
let init_msg = bincode_ops()
|
||||||
.serialize(&InitMessage::V1(V1InitMessage {}))
|
.serialize(&InitMessage::V1(V1InitMessage {}))
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,8 @@ pub(super) struct ConfigProxyHandler {
|
||||||
pub window_matcher_cache: CriterionCache<WindowCriterionIpc, ToplevelData>,
|
pub window_matcher_cache: CriterionCache<WindowCriterionIpc, ToplevelData>,
|
||||||
pub window_matcher_leafs: CopyHashMap<WindowMatcher, Rc<TlmLeafMatcher>>,
|
pub window_matcher_leafs: CopyHashMap<WindowMatcher, Rc<TlmLeafMatcher>>,
|
||||||
pub window_matcher_std_kinds: Rc<TlmUpstreamNode>,
|
pub window_matcher_std_kinds: Rc<TlmUpstreamNode>,
|
||||||
|
pub window_matcher_no_auto_focus:
|
||||||
|
CopyHashMap<WindowMatcher, Rc<CachedCriterion<WindowCriterionIpc, ToplevelData>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Pollable {
|
pub struct Pollable {
|
||||||
|
|
@ -2027,6 +2029,7 @@ impl ConfigProxyHandler {
|
||||||
fn handle_destroy_window_matcher(&self, matcher: WindowMatcher) {
|
fn handle_destroy_window_matcher(&self, matcher: WindowMatcher) {
|
||||||
self.window_matchers.remove(&matcher);
|
self.window_matchers.remove(&matcher);
|
||||||
self.window_matcher_leafs.remove(&matcher);
|
self.window_matcher_leafs.remove(&matcher);
|
||||||
|
self.window_matcher_no_auto_focus.remove(&matcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_enable_window_matcher_events(
|
fn handle_enable_window_matcher_events(
|
||||||
|
|
@ -2056,6 +2059,20 @@ impl ConfigProxyHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_set_window_matcher_auto_focus(
|
||||||
|
&self,
|
||||||
|
matcher: WindowMatcher,
|
||||||
|
auto_focus: bool,
|
||||||
|
) -> Result<(), CphError> {
|
||||||
|
if auto_focus {
|
||||||
|
self.window_matcher_no_auto_focus.remove(&matcher);
|
||||||
|
} else {
|
||||||
|
let m = self.get_window_matcher(matcher)?;
|
||||||
|
self.window_matcher_no_auto_focus.set(matcher, m);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn spaces_change(&self) {
|
fn spaces_change(&self) {
|
||||||
struct V;
|
struct V;
|
||||||
impl NodeVisitorBase for V {
|
impl NodeVisitorBase for V {
|
||||||
|
|
@ -2861,9 +2878,24 @@ impl ConfigProxyHandler {
|
||||||
ClientMessage::EnableWindowMatcherEvents { matcher } => self
|
ClientMessage::EnableWindowMatcherEvents { matcher } => self
|
||||||
.handle_enable_window_matcher_events(matcher)
|
.handle_enable_window_matcher_events(matcher)
|
||||||
.wrn("enable_window_matcher_events")?,
|
.wrn("enable_window_matcher_events")?,
|
||||||
|
ClientMessage::SetWindowMatcherAutoFocus {
|
||||||
|
matcher,
|
||||||
|
auto_focus,
|
||||||
|
} => self
|
||||||
|
.handle_set_window_matcher_auto_focus(matcher, auto_focus)
|
||||||
|
.wrn("set_window_matcher_auto_focus")?,
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn auto_focus(&self, data: &ToplevelData) -> bool {
|
||||||
|
for matcher in self.window_matcher_no_auto_focus.lock().values() {
|
||||||
|
if matcher.node.pull(data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
||||||
23
src/state.rs
23
src/state.rs
|
|
@ -665,11 +665,7 @@ impl State {
|
||||||
pub fn map_tiled(self: &Rc<Self>, node: Rc<dyn ToplevelNode>) {
|
pub fn map_tiled(self: &Rc<Self>, node: Rc<dyn ToplevelNode>) {
|
||||||
let seat = self.seat_queue.last();
|
let seat = self.seat_queue.last();
|
||||||
self.do_map_tiled(seat.as_deref(), node.clone());
|
self.do_map_tiled(seat.as_deref(), node.clone());
|
||||||
if node.node_visible() {
|
self.focus_after_map(node, seat.as_deref());
|
||||||
if let Some(seat) = seat {
|
|
||||||
node.node_do_focus(&seat, Direction::Unspecified);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_map_tiled(self: &Rc<Self>, seat: Option<&Rc<WlSeatGlobal>>, node: Rc<dyn ToplevelNode>) {
|
fn do_map_tiled(self: &Rc<Self>, seat: Option<&Rc<WlSeatGlobal>>, node: Rc<dyn ToplevelNode>) {
|
||||||
|
|
@ -739,11 +735,22 @@ impl State {
|
||||||
Rect::new_sized(x1, y1, width, height).unwrap()
|
Rect::new_sized(x1, y1, width, height).unwrap()
|
||||||
};
|
};
|
||||||
FloatNode::new(self, workspace, position, node.clone());
|
FloatNode::new(self, workspace, position, node.clone());
|
||||||
if node.node_visible() {
|
self.focus_after_map(node, self.seat_queue.last().as_deref());
|
||||||
if let Some(seat) = self.seat_queue.last() {
|
}
|
||||||
node.node_do_focus(&seat, Direction::Unspecified);
|
|
||||||
|
fn focus_after_map(&self, node: Rc<dyn ToplevelNode>, seat: Option<&Rc<WlSeatGlobal>>) {
|
||||||
|
if !node.node_visible() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(seat) = seat else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(config) = self.config.get() {
|
||||||
|
if !config.auto_focus(node.tl_data()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
node.node_do_focus(&seat, Direction::Unspecified);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_workspace(&self, seat: &Rc<WlSeatGlobal>, name: &str) {
|
pub fn show_workspace(&self, seat: &Rc<WlSeatGlobal>, name: &str) {
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,7 @@ pub struct WindowRule {
|
||||||
pub match_: WindowMatch,
|
pub match_: WindowMatch,
|
||||||
pub action: Option<Action>,
|
pub action: Option<Action>,
|
||||||
pub latch: Option<Action>,
|
pub latch: Option<Action>,
|
||||||
|
pub auto_focus: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use {
|
||||||
config::{
|
config::{
|
||||||
WindowMatch, WindowRule,
|
WindowMatch, WindowRule,
|
||||||
context::Context,
|
context::Context,
|
||||||
extractor::{Extractor, ExtractorError, opt, str, val},
|
extractor::{Extractor, ExtractorError, bol, opt, recover, str, val},
|
||||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
parsers::{
|
parsers::{
|
||||||
action::{ActionParser, ActionParserError},
|
action::{ActionParser, ActionParserError},
|
||||||
|
|
@ -47,11 +47,12 @@ impl Parser for WindowRuleParser<'_> {
|
||||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
) -> ParseResult<Self> {
|
) -> ParseResult<Self> {
|
||||||
let mut ext = Extractor::new(self.0, span, table);
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
let (name, match_val, action_val, latch_val) = ext.extract((
|
let (name, match_val, action_val, latch_val, auto_focus) = ext.extract((
|
||||||
opt(str("name")),
|
opt(str("name")),
|
||||||
opt(val("match")),
|
opt(val("match")),
|
||||||
opt(val("action")),
|
opt(val("action")),
|
||||||
opt(val("latch")),
|
opt(val("latch")),
|
||||||
|
recover(opt(bol("auto-focus"))),
|
||||||
))?;
|
))?;
|
||||||
let mut action = None;
|
let mut action = None;
|
||||||
if let Some(value) = action_val {
|
if let Some(value) = action_val {
|
||||||
|
|
@ -78,6 +79,7 @@ impl Parser for WindowRuleParser<'_> {
|
||||||
match_,
|
match_,
|
||||||
action,
|
action,
|
||||||
latch,
|
latch,
|
||||||
|
auto_focus: auto_focus.despan(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -333,6 +333,9 @@ impl Rule for WindowRule {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(auto_focus) = self.auto_focus {
|
||||||
|
matcher.set_auto_focus(auto_focus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_matcher(m: Self::Matcher) -> Self::Criterion<'static> {
|
fn gen_matcher(m: Self::Matcher) -> Self::Criterion<'static> {
|
||||||
|
|
|
||||||
|
|
@ -1904,6 +1904,10 @@
|
||||||
"latch": {
|
"latch": {
|
||||||
"description": "An action to execute when a window no longer matches the criteria.",
|
"description": "An action to execute when a window no longer matches the criteria.",
|
||||||
"$ref": "#/$defs/Action"
|
"$ref": "#/$defs/Action"
|
||||||
|
},
|
||||||
|
"auto-focus": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether newly mapped windows that match this rule get the keyboard focus.\n\nIf a window matches any rule for which this is false, the window will not be\nautomatically focused.\n"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
|
|
|
||||||
|
|
@ -4203,6 +4203,15 @@ The table has the following fields:
|
||||||
|
|
||||||
The value of this field should be a [Action](#types-Action).
|
The value of this field should be a [Action](#types-Action).
|
||||||
|
|
||||||
|
- `auto-focus` (optional):
|
||||||
|
|
||||||
|
Whether newly mapped windows that match this rule get the keyboard focus.
|
||||||
|
|
||||||
|
If a window matches any rule for which this is false, the window will not be
|
||||||
|
automatically focused.
|
||||||
|
|
||||||
|
The value of this field should be a boolean.
|
||||||
|
|
||||||
|
|
||||||
<a name="types-WindowTypeMask"></a>
|
<a name="types-WindowTypeMask"></a>
|
||||||
### `WindowTypeMask`
|
### `WindowTypeMask`
|
||||||
|
|
|
||||||
|
|
@ -3368,6 +3368,14 @@ WindowRule:
|
||||||
ref: Action
|
ref: Action
|
||||||
required: false
|
required: false
|
||||||
description: An action to execute when a window no longer matches the criteria.
|
description: An action to execute when a window no longer matches the criteria.
|
||||||
|
auto-focus:
|
||||||
|
kind: boolean
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
Whether newly mapped windows that match this rule get the keyboard focus.
|
||||||
|
|
||||||
|
If a window matches any rule for which this is false, the window will not be
|
||||||
|
automatically focused.
|
||||||
|
|
||||||
|
|
||||||
WindowMatch:
|
WindowMatch:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue