toml-config: add input modes
This commit is contained in:
parent
e160aa3406
commit
a57f0036a8
10 changed files with 797 additions and 46 deletions
|
|
@ -8,6 +8,7 @@ mod parsers;
|
||||||
mod spanned;
|
mod spanned;
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
|
pub use crate::config::parsers::input_mode::InputMode;
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
config::{
|
config::{
|
||||||
|
|
@ -81,6 +82,7 @@ pub enum SimpleCommand {
|
||||||
FocusTiles,
|
FocusTiles,
|
||||||
CreateMark,
|
CreateMark,
|
||||||
JumpToMark,
|
JumpToMark,
|
||||||
|
PopMode(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -167,6 +169,10 @@ pub enum Action {
|
||||||
CreateMark(u32),
|
CreateMark(u32),
|
||||||
JumpToMark(u32),
|
JumpToMark(u32),
|
||||||
CopyMark(u32, u32),
|
CopyMark(u32, u32),
|
||||||
|
SetMode {
|
||||||
|
name: String,
|
||||||
|
latch: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|
@ -502,6 +508,7 @@ pub struct Config {
|
||||||
pub show_bar: Option<bool>,
|
pub show_bar: Option<bool>,
|
||||||
pub focus_history: Option<FocusHistory>,
|
pub focus_history: Option<FocusHistory>,
|
||||||
pub middle_click_paste: Option<bool>,
|
pub middle_click_paste: Option<bool>,
|
||||||
|
pub input_modes: AHashMap<String, InputMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ mod gfx_api;
|
||||||
mod idle;
|
mod idle;
|
||||||
mod input;
|
mod input;
|
||||||
mod input_match;
|
mod input_match;
|
||||||
|
pub mod input_mode;
|
||||||
pub mod keymap;
|
pub mod keymap;
|
||||||
mod libei;
|
mod libei;
|
||||||
mod log_level;
|
mod log_level;
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,8 @@ impl ActionParser<'_> {
|
||||||
"focus-tiles" => FocusTiles,
|
"focus-tiles" => FocusTiles,
|
||||||
"create-mark" => CreateMark,
|
"create-mark" => CreateMark,
|
||||||
"jump-to-mark" => JumpToMark,
|
"jump-to-mark" => JumpToMark,
|
||||||
|
"clear-modes" => PopMode(false),
|
||||||
|
"pop-mode" => PopMode(true),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(
|
return Err(
|
||||||
ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)
|
ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)
|
||||||
|
|
@ -414,6 +416,22 @@ impl ActionParser<'_> {
|
||||||
.map_spanned_err(ActionParserError::CopyMark)?;
|
.map_spanned_err(ActionParserError::CopyMark)?;
|
||||||
Ok(Action::CopyMark(src, dst))
|
Ok(Action::CopyMark(src, dst))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_push_mode(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let (name,) = ext.extract((str("name"),))?;
|
||||||
|
Ok(Action::SetMode {
|
||||||
|
name: name.value.to_string(),
|
||||||
|
latch: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_latch_mode(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let (name,) = ext.extract((str("name"),))?;
|
||||||
|
Ok(Action::SetMode {
|
||||||
|
name: name.value.to_string(),
|
||||||
|
latch: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parser for ActionParser<'_> {
|
impl Parser for ActionParser<'_> {
|
||||||
|
|
@ -471,6 +489,8 @@ impl Parser for ActionParser<'_> {
|
||||||
"create-mark" => self.parse_create_mark(&mut ext),
|
"create-mark" => self.parse_create_mark(&mut ext),
|
||||||
"jump-to-mark" => self.parse_jump_to_mark(&mut ext),
|
"jump-to-mark" => self.parse_jump_to_mark(&mut ext),
|
||||||
"copy-mark" => self.parse_copy_mark(&mut ext),
|
"copy-mark" => self.parse_copy_mark(&mut ext),
|
||||||
|
"push-mode" => self.parse_push_mode(&mut ext),
|
||||||
|
"latch-mode" => self.parse_latch_mode(&mut ext),
|
||||||
v => {
|
v => {
|
||||||
ext.ignore_unused();
|
ext.ignore_unused();
|
||||||
return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span));
|
return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span));
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ use {
|
||||||
gfx_api::GfxApiParser,
|
gfx_api::GfxApiParser,
|
||||||
idle::IdleParser,
|
idle::IdleParser,
|
||||||
input::InputsParser,
|
input::InputsParser,
|
||||||
|
input_mode::InputModesParser,
|
||||||
keymap::KeymapParser,
|
keymap::KeymapParser,
|
||||||
libei::LibeiParser,
|
libei::LibeiParser,
|
||||||
log_level::LogLevelParser,
|
log_level::LogLevelParser,
|
||||||
|
|
@ -44,6 +45,7 @@ use {
|
||||||
toml_value::Value,
|
toml_value::Value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ahash::AHashMap,
|
||||||
indexmap::IndexMap,
|
indexmap::IndexMap,
|
||||||
std::collections::HashSet,
|
std::collections::HashSet,
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
|
|
@ -136,7 +138,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
show_bar,
|
show_bar,
|
||||||
focus_history_val,
|
focus_history_val,
|
||||||
),
|
),
|
||||||
(middle_click_paste,),
|
(middle_click_paste, input_modes_val),
|
||||||
) = ext.extract((
|
) = ext.extract((
|
||||||
(
|
(
|
||||||
opt(val("keymap")),
|
opt(val("keymap")),
|
||||||
|
|
@ -186,7 +188,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
recover(opt(bol("show-bar"))),
|
recover(opt(bol("show-bar"))),
|
||||||
opt(val("focus-history")),
|
opt(val("focus-history")),
|
||||||
),
|
),
|
||||||
(recover(opt(bol("middle-click-paste"))),),
|
(recover(opt(bol("middle-click-paste"))), opt(val("modes"))),
|
||||||
))?;
|
))?;
|
||||||
let mut keymap = None;
|
let mut keymap = None;
|
||||||
if let Some(value) = keymap_val {
|
if let Some(value) = keymap_val {
|
||||||
|
|
@ -475,6 +477,15 @@ impl Parser for ConfigParser<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut input_modes = AHashMap::new();
|
||||||
|
if let Some(value) = input_modes_val {
|
||||||
|
match value.parse(&mut InputModesParser(self.0)) {
|
||||||
|
Ok(v) => input_modes = v,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse the input modes: {}", self.0.error(e),);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(Config {
|
Ok(Config {
|
||||||
keymap,
|
keymap,
|
||||||
repeat_rate,
|
repeat_rate,
|
||||||
|
|
@ -516,6 +527,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
show_bar: show_bar.despan(),
|
show_bar: show_bar.despan(),
|
||||||
focus_history,
|
focus_history,
|
||||||
middle_click_paste: middle_click_paste.despan(),
|
middle_click_paste: middle_click_paste.despan(),
|
||||||
|
input_modes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
125
toml-config/src/config/parsers/input_mode.rs
Normal file
125
toml-config/src/config/parsers/input_mode.rs
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
Shortcut,
|
||||||
|
context::Context,
|
||||||
|
extractor::{Extractor, ExtractorError, opt, recover, str, val},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::shortcuts::{ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError},
|
||||||
|
spanned::SpannedErrorExt,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ahash::{AHashMap, AHashSet},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
std::collections::HashSet,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum InputModeParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
ExtractorError(#[from] ExtractorError),
|
||||||
|
#[error("Could not parse the shortcuts")]
|
||||||
|
ParseShortcuts(#[source] ShortcutsParserError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct InputMode {
|
||||||
|
pub parent: Option<String>,
|
||||||
|
pub shortcuts: Vec<Shortcut>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InputModesParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl Parser for InputModesParser<'_> {
|
||||||
|
type Value = AHashMap<String, InputMode>;
|
||||||
|
type Error = InputModeParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
_span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut modes = AHashMap::new();
|
||||||
|
let mut used = AHashSet::new();
|
||||||
|
for (key, value) in table.iter() {
|
||||||
|
let mode = match value.parse(&mut InputModeParser(self.0)) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(
|
||||||
|
"Could not parse input mode {}: {}",
|
||||||
|
key.value,
|
||||||
|
self.0.error(e)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
log_used(self.0, &mut used, key);
|
||||||
|
modes.insert(key.value.to_string(), mode);
|
||||||
|
}
|
||||||
|
Ok(modes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InputModeParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl Parser for InputModeParser<'_> {
|
||||||
|
type Value = InputMode;
|
||||||
|
type Error = InputModeParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (parent, shortcuts_val, complex_shortcuts_val) = ext.extract((
|
||||||
|
recover(opt(str("parent"))),
|
||||||
|
opt(val("shortcuts")),
|
||||||
|
opt(val("complex-shortcuts")),
|
||||||
|
))?;
|
||||||
|
let mut used_keys = HashSet::new();
|
||||||
|
let mut shortcuts = vec![];
|
||||||
|
if let Some(value) = shortcuts_val {
|
||||||
|
value
|
||||||
|
.parse(&mut ShortcutsParser {
|
||||||
|
cx: self.0,
|
||||||
|
used_keys: &mut used_keys,
|
||||||
|
shortcuts: &mut shortcuts,
|
||||||
|
})
|
||||||
|
.map_spanned_err(InputModeParserError::ParseShortcuts)?;
|
||||||
|
}
|
||||||
|
if let Some(value) = complex_shortcuts_val {
|
||||||
|
value
|
||||||
|
.parse(&mut ComplexShortcutsParser {
|
||||||
|
cx: self.0,
|
||||||
|
used_keys: &mut used_keys,
|
||||||
|
shortcuts: &mut shortcuts,
|
||||||
|
})
|
||||||
|
.map_spanned_err(InputModeParserError::ParseShortcuts)?;
|
||||||
|
}
|
||||||
|
Ok(InputMode {
|
||||||
|
parent: parent.despan_into(),
|
||||||
|
shortcuts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_used(cx: &Context<'_>, used: &mut AHashSet<Spanned<String>>, key: &Spanned<String>) {
|
||||||
|
if let Some(prev) = used.get(key) {
|
||||||
|
log::warn!(
|
||||||
|
"Duplicate input mode overrides previous definition: {}",
|
||||||
|
cx.error3(key.span)
|
||||||
|
);
|
||||||
|
log::info!("Previous definition here: {}", cx.error3(prev.span));
|
||||||
|
}
|
||||||
|
used.insert(key.clone());
|
||||||
|
}
|
||||||
|
|
@ -7,16 +7,18 @@
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod rules;
|
mod rules;
|
||||||
|
mod shortcuts;
|
||||||
mod toml;
|
mod toml;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
config::{
|
config::{
|
||||||
Action, ClientRule, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap,
|
Action, ClientRule, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap,
|
||||||
ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output, OutputMatch, Shortcut,
|
ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output, OutputMatch,
|
||||||
SimpleCommand, Status, Theme, WindowRule, parse_config,
|
SimpleCommand, Status, Theme, WindowRule, parse_config,
|
||||||
},
|
},
|
||||||
rules::{MatcherTemp, RuleMapper},
|
rules::{MatcherTemp, RuleMapper},
|
||||||
|
shortcuts::ModeState,
|
||||||
},
|
},
|
||||||
ahash::{AHashMap, AHashSet},
|
ahash::{AHashMap, AHashSet},
|
||||||
error_reporter::Report,
|
error_reporter::Report,
|
||||||
|
|
@ -31,7 +33,7 @@ use {
|
||||||
set_libei_socket_enabled,
|
set_libei_socket_enabled,
|
||||||
},
|
},
|
||||||
is_reload,
|
is_reload,
|
||||||
keyboard::{Keymap, ModifiedKeySym},
|
keyboard::Keymap,
|
||||||
logging::set_log_level,
|
logging::set_log_level,
|
||||||
on_devices_enumerated, on_idle, on_unload, quit, reload, set_color_management_enabled,
|
on_devices_enumerated, on_idle, on_unload, quit, reload, set_color_management_enabled,
|
||||||
set_default_workspace_capture, set_explicit_sync_enabled, set_float_above_fullscreen,
|
set_default_workspace_capture, set_explicit_sync_enabled, set_float_above_fullscreen,
|
||||||
|
|
@ -91,6 +93,20 @@ impl FnBuilder for RcFnBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ShortcutFnBuilder<'a>(&'a Rc<State>);
|
||||||
|
|
||||||
|
impl FnBuilder for ShortcutFnBuilder<'_> {
|
||||||
|
type Output = Rc<dyn Fn()>;
|
||||||
|
|
||||||
|
fn new<F: Fn() + 'static>(&self, f: F) -> Self::Output {
|
||||||
|
let state = self.0.clone();
|
||||||
|
Rc::new(move || {
|
||||||
|
state.cancel_mode_latch();
|
||||||
|
f();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Action {
|
impl Action {
|
||||||
fn into_fn(self, state: &Rc<State>) -> Box<dyn Fn()> {
|
fn into_fn(self, state: &Rc<State>) -> Box<dyn Fn()> {
|
||||||
self.into_fn_impl(&BoxFnBuilder, state)
|
self.into_fn_impl(&BoxFnBuilder, state)
|
||||||
|
|
@ -100,6 +116,10 @@ impl Action {
|
||||||
self.into_fn_impl(&RcFnBuilder, state)
|
self.into_fn_impl(&RcFnBuilder, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn into_shortcut_fn(self, state: &Rc<State>) -> Rc<dyn Fn()> {
|
||||||
|
self.into_fn_impl(&ShortcutFnBuilder(state), state)
|
||||||
|
}
|
||||||
|
|
||||||
fn into_fn_impl<B: FnBuilder>(self, b: &B, state: &Rc<State>) -> B::Output {
|
fn into_fn_impl<B: FnBuilder>(self, b: &B, state: &Rc<State>) -> B::Output {
|
||||||
macro_rules! client_action {
|
macro_rules! client_action {
|
||||||
($name:ident, $opt:expr) => {{
|
($name:ident, $opt:expr) => {{
|
||||||
|
|
@ -187,6 +207,10 @@ impl Action {
|
||||||
let persistent = state.persistent.clone();
|
let persistent = state.persistent.clone();
|
||||||
b.new(move || persistent.seat.jump_to_mark(None))
|
b.new(move || persistent.seat.jump_to_mark(None))
|
||||||
}
|
}
|
||||||
|
SimpleCommand::PopMode(pop) => {
|
||||||
|
let state = state.clone();
|
||||||
|
b.new(move || state.pop_mode(pop))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Action::Multi { actions } => {
|
Action::Multi { actions } => {
|
||||||
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
|
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
|
||||||
|
|
@ -355,6 +379,18 @@ impl Action {
|
||||||
let persistent = state.persistent.clone();
|
let persistent = state.persistent.clone();
|
||||||
b.new(move || persistent.seat.copy_mark(s, d))
|
b.new(move || persistent.seat.copy_mark(s, d))
|
||||||
}
|
}
|
||||||
|
Action::SetMode { name, latch } => {
|
||||||
|
let state = state.clone();
|
||||||
|
let new = state.get_mode_slot(&name);
|
||||||
|
b.new(move || {
|
||||||
|
let new = new.mode.borrow();
|
||||||
|
let Some(new) = new.as_ref() else {
|
||||||
|
log::warn!("Input mode {name} does not exist");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
state.set_mode(new, latch);
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -773,43 +809,6 @@ impl Drop for State {
|
||||||
type SwitchActions = Vec<(InputMatch, AHashMap<SwitchEvent, Box<dyn Fn()>>)>;
|
type SwitchActions = Vec<(InputMatch, AHashMap<SwitchEvent, Box<dyn Fn()>>)>;
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
fn unbind_all(&self) {
|
|
||||||
let mut binds = self.persistent.binds.borrow_mut();
|
|
||||||
for bind in binds.drain() {
|
|
||||||
self.persistent.seat.unbind(bind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_shortcuts(self: &Rc<Self>, shortcuts: impl IntoIterator<Item = Shortcut>) {
|
|
||||||
let mut binds = self.persistent.binds.borrow_mut();
|
|
||||||
for shortcut in shortcuts {
|
|
||||||
if let Action::SimpleCommand {
|
|
||||||
cmd: SimpleCommand::None,
|
|
||||||
} = shortcut.action
|
|
||||||
{
|
|
||||||
if shortcut.latch.is_none() {
|
|
||||||
self.persistent.seat.unbind(shortcut.keysym);
|
|
||||||
binds.remove(&shortcut.keysym);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut f = shortcut.action.into_fn(self);
|
|
||||||
if let Some(l) = shortcut.latch {
|
|
||||||
let l = l.into_rc_fn(self);
|
|
||||||
let s = self.persistent.seat;
|
|
||||||
f = Box::new(move || {
|
|
||||||
f();
|
|
||||||
let l = l.clone();
|
|
||||||
s.latch(move || l());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
self.persistent
|
|
||||||
.seat
|
|
||||||
.bind_masked(shortcut.mask, shortcut.keysym, f);
|
|
||||||
binds.insert(shortcut.keysym);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_keymap(&self, map: &ConfigKeymap) -> Option<Keymap> {
|
fn get_keymap(&self, map: &ConfigKeymap) -> Option<Keymap> {
|
||||||
let map = match map {
|
let map = match map {
|
||||||
ConfigKeymap::Named(n) => match self.keymaps.get(n) {
|
ConfigKeymap::Named(n) => match self.keymaps.get(n) {
|
||||||
|
|
@ -998,13 +997,13 @@ struct PersistentState {
|
||||||
seen_outputs: RefCell<AHashSet<OutputId>>,
|
seen_outputs: RefCell<AHashSet<OutputId>>,
|
||||||
default: Config,
|
default: Config,
|
||||||
seat: Seat,
|
seat: Seat,
|
||||||
binds: RefCell<AHashSet<ModifiedKeySym>>,
|
|
||||||
#[expect(clippy::type_complexity)]
|
#[expect(clippy::type_complexity)]
|
||||||
actions: RefCell<AHashMap<Rc<String>, Rc<dyn Fn()>>>,
|
actions: RefCell<AHashMap<Rc<String>, Rc<dyn Fn()>>>,
|
||||||
client_rules: Cell<Vec<MatcherTemp<ClientRule>>>,
|
client_rules: Cell<Vec<MatcherTemp<ClientRule>>>,
|
||||||
client_rule_mapper: RefCell<Option<RuleMapper<ClientRule>>>,
|
client_rule_mapper: RefCell<Option<RuleMapper<ClientRule>>>,
|
||||||
window_rules: Cell<Vec<MatcherTemp<WindowRule>>>,
|
window_rules: Cell<Vec<MatcherTemp<WindowRule>>>,
|
||||||
mark_names: RefCell<AHashMap<String, u32>>,
|
mark_names: RefCell<AHashMap<String, u32>>,
|
||||||
|
mode_state: ModeState,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
||||||
|
|
@ -1088,6 +1087,7 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
||||||
client: Default::default(),
|
client: Default::default(),
|
||||||
window: Default::default(),
|
window: Default::default(),
|
||||||
});
|
});
|
||||||
|
state.clear_modes_after_reload();
|
||||||
let (client_rules, client_rule_mapper) = state.create_rules(&config.client_rules);
|
let (client_rules, client_rule_mapper) = state.create_rules(&config.client_rules);
|
||||||
persistent.client_rules.set(client_rules);
|
persistent.client_rules.set(client_rules);
|
||||||
*state.persistent.client_rule_mapper.borrow_mut() = Some(client_rule_mapper);
|
*state.persistent.client_rule_mapper.borrow_mut() = Some(client_rule_mapper);
|
||||||
|
|
@ -1118,8 +1118,7 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
||||||
None => on_idle(|| ()),
|
None => on_idle(|| ()),
|
||||||
Some(a) => on_idle(a.into_fn(&state)),
|
Some(a) => on_idle(a.into_fn(&state)),
|
||||||
}
|
}
|
||||||
state.unbind_all();
|
state.init_modes(&config.shortcuts, &config.input_modes);
|
||||||
state.apply_shortcuts(config.shortcuts);
|
|
||||||
if let Some(keymap) = config.keymap {
|
if let Some(keymap) = config.keymap {
|
||||||
state.set_keymap(&keymap);
|
state.set_keymap(&keymap);
|
||||||
}
|
}
|
||||||
|
|
@ -1334,18 +1333,19 @@ pub fn configure() {
|
||||||
seen_outputs: Default::default(),
|
seen_outputs: Default::default(),
|
||||||
default: default.unwrap(),
|
default: default.unwrap(),
|
||||||
seat: default_seat(),
|
seat: default_seat(),
|
||||||
binds: Default::default(),
|
|
||||||
actions: Default::default(),
|
actions: Default::default(),
|
||||||
client_rules: Default::default(),
|
client_rules: Default::default(),
|
||||||
client_rule_mapper: Default::default(),
|
client_rule_mapper: Default::default(),
|
||||||
window_rules: Default::default(),
|
window_rules: Default::default(),
|
||||||
mark_names,
|
mark_names,
|
||||||
|
mode_state: Default::default(),
|
||||||
});
|
});
|
||||||
{
|
{
|
||||||
let p = persistent.clone();
|
let p = persistent.clone();
|
||||||
on_unload(move || {
|
on_unload(move || {
|
||||||
p.actions.borrow_mut().clear();
|
p.actions.borrow_mut().clear();
|
||||||
p.client_rule_mapper.borrow_mut().take();
|
p.client_rule_mapper.borrow_mut().take();
|
||||||
|
p.mode_state.clear();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
load_config(true, &persistent);
|
load_config(true, &persistent);
|
||||||
|
|
|
||||||
276
toml-config/src/shortcuts.rs
Normal file
276
toml-config/src/shortcuts.rs
Normal file
|
|
@ -0,0 +1,276 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
State,
|
||||||
|
config::{Action, InputMode, Shortcut, SimpleCommand},
|
||||||
|
},
|
||||||
|
ahash::{AHashMap, AHashSet},
|
||||||
|
jay_config::keyboard::{ModifiedKeySym, mods::Modifiers},
|
||||||
|
std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
collections::hash_map::Entry,
|
||||||
|
rc::Rc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ModeState {
|
||||||
|
latched: Cell<bool>,
|
||||||
|
stack: RefCell<Vec<Rc<ConvertedShortcuts>>>,
|
||||||
|
slots: RefCell<AHashMap<String, Rc<ModeSlot>>>,
|
||||||
|
diffs: RefCell<AHashMap<[*const ConvertedShortcuts; 2], Rc<Vec<ModeDiff>>>>,
|
||||||
|
current: RefCell<Rc<ConvertedShortcuts>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModeState {
|
||||||
|
pub fn clear(&self) {
|
||||||
|
self.slots.borrow_mut().clear();
|
||||||
|
self.stack.borrow_mut().clear();
|
||||||
|
self.diffs.borrow_mut().clear();
|
||||||
|
*self.current.borrow_mut() = Default::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ConvertedShortcuts = AHashMap<ModifiedKeySym, ConvertedShortcut>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ConvertedShortcut {
|
||||||
|
mask: Modifiers,
|
||||||
|
shortcut: Rc<dyn Fn()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ModeSlot {
|
||||||
|
pub mode: RefCell<Option<Rc<ConvertedShortcuts>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ModeDiff {
|
||||||
|
Bind(ModifiedKeySym, Modifiers, Rc<dyn Fn()>),
|
||||||
|
Unbind(ModifiedKeySym),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for ConvertedShortcut {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
if self.mask != other.mask {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Rc::ptr_eq(&self.shortcut, &other.shortcut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn get_mode_slot(&self, name: &str) -> Rc<ModeSlot> {
|
||||||
|
let state = &self.persistent.mode_state;
|
||||||
|
state
|
||||||
|
.slots
|
||||||
|
.borrow_mut()
|
||||||
|
.entry(name.to_string())
|
||||||
|
.or_default()
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_modes_after_reload(&self) {
|
||||||
|
let state = &self.persistent.mode_state;
|
||||||
|
state.slots.borrow_mut().clear();
|
||||||
|
state.diffs.borrow_mut().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_modes(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
shortcuts: &[Shortcut],
|
||||||
|
modes: &AHashMap<String, InputMode>,
|
||||||
|
) {
|
||||||
|
let state = &self.persistent.mode_state;
|
||||||
|
let base = self.convert_shortcuts(shortcuts);
|
||||||
|
let stack = &mut *state.stack.borrow_mut();
|
||||||
|
stack.clear();
|
||||||
|
stack.push(base.clone());
|
||||||
|
self.convert_modes(&base, modes);
|
||||||
|
self.apply_shortcuts(&base);
|
||||||
|
state.latched.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_mode(&self, new: &Rc<ConvertedShortcuts>, latch: bool) {
|
||||||
|
let state = &self.persistent.mode_state;
|
||||||
|
self.cancel_mode_latch();
|
||||||
|
self.apply_shortcuts(new);
|
||||||
|
let stack = &mut *state.stack.borrow_mut();
|
||||||
|
stack.push(new.clone());
|
||||||
|
if latch {
|
||||||
|
state.latched.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop_mode(&self, pop: bool) {
|
||||||
|
let state = &self.persistent.mode_state;
|
||||||
|
let stack = &mut *state.stack.borrow_mut();
|
||||||
|
if stack.len() < 1 + pop as usize {
|
||||||
|
log::error!("Mode stack is empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.cancel_mode_latch();
|
||||||
|
if pop {
|
||||||
|
stack.pop();
|
||||||
|
} else {
|
||||||
|
stack.truncate(1);
|
||||||
|
}
|
||||||
|
let new = stack.last().unwrap();
|
||||||
|
self.apply_shortcuts(new);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel_mode_latch(&self) {
|
||||||
|
let state = &self.persistent.mode_state;
|
||||||
|
if !state.latched.take() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let stack = &mut *state.stack.borrow_mut();
|
||||||
|
if stack.len() < 2 {
|
||||||
|
log::error!("Mode is latched but mode stack is empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let _ = stack.pop();
|
||||||
|
let new = stack.last().unwrap();
|
||||||
|
self.apply_shortcuts(new);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_modes(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
base: &ConvertedShortcuts,
|
||||||
|
modes: &AHashMap<String, InputMode>,
|
||||||
|
) {
|
||||||
|
let mut pending = AHashSet::new();
|
||||||
|
let mut out = AHashMap::new();
|
||||||
|
for (name, mode) in modes {
|
||||||
|
if !out.contains_key(name) {
|
||||||
|
self.convert_mode(&mut out, &mut pending, base, modes, name, mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_mode<'a>(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
out: &'a mut AHashMap<String, Rc<ConvertedShortcuts>>,
|
||||||
|
pending: &mut AHashSet<String>,
|
||||||
|
base: &ConvertedShortcuts,
|
||||||
|
modes: &AHashMap<String, InputMode>,
|
||||||
|
mode_name: &String,
|
||||||
|
mode: &InputMode,
|
||||||
|
) -> Option<&'a ConvertedShortcuts> {
|
||||||
|
if !pending.insert(mode_name.clone()) {
|
||||||
|
log::warn!("Detected loop while converting input mode `{mode_name}`");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut shortcuts = None;
|
||||||
|
if let Some(parent) = &mode.parent {
|
||||||
|
match out.get(parent) {
|
||||||
|
Some(c) => shortcuts = Some((**c).clone()),
|
||||||
|
None => match modes.get(parent) {
|
||||||
|
None => {
|
||||||
|
log::warn!("Input mode `{parent}` does not exist");
|
||||||
|
}
|
||||||
|
Some(p) => {
|
||||||
|
if let Some(p) = self.convert_mode(out, pending, base, modes, parent, p) {
|
||||||
|
shortcuts = Some(p.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut shortcuts = shortcuts.unwrap_or_else(|| base.clone());
|
||||||
|
self.convert_shortcuts_(&mode.shortcuts, &mut shortcuts);
|
||||||
|
let shortcuts = Rc::new(shortcuts);
|
||||||
|
*self.get_mode_slot(mode_name).mode.borrow_mut() = Some(shortcuts.clone());
|
||||||
|
let res = out.entry(mode_name.clone()).insert_entry(shortcuts);
|
||||||
|
Some(res.into_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_shortcuts<'a>(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
shortcuts: impl IntoIterator<Item = &'a Shortcut>,
|
||||||
|
) -> Rc<ConvertedShortcuts> {
|
||||||
|
let mut dst = ConvertedShortcuts::new();
|
||||||
|
self.convert_shortcuts_(shortcuts, &mut dst);
|
||||||
|
Rc::new(dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_shortcuts_<'a>(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
shortcuts: impl IntoIterator<Item = &'a Shortcut>,
|
||||||
|
dst: &mut ConvertedShortcuts,
|
||||||
|
) {
|
||||||
|
for sc in shortcuts {
|
||||||
|
match self.convert_shortcut(sc.clone()) {
|
||||||
|
None => dst.remove(&sc.keysym),
|
||||||
|
Some(cs) => dst.insert(sc.keysym, cs),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_shortcut(self: &Rc<Self>, shortcut: Shortcut) -> Option<ConvertedShortcut> {
|
||||||
|
if let Action::SimpleCommand {
|
||||||
|
cmd: SimpleCommand::None,
|
||||||
|
} = shortcut.action
|
||||||
|
&& shortcut.latch.is_none()
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut f = shortcut.action.into_shortcut_fn(self);
|
||||||
|
if let Some(l) = shortcut.latch {
|
||||||
|
let l = l.into_rc_fn(self);
|
||||||
|
let s = self.persistent.seat;
|
||||||
|
f = Rc::new(move || {
|
||||||
|
f();
|
||||||
|
let l = l.clone();
|
||||||
|
s.latch(move || l());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(ConvertedShortcut {
|
||||||
|
mask: shortcut.mask,
|
||||||
|
shortcut: f,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_shortcuts(&self, new: &Rc<ConvertedShortcuts>) {
|
||||||
|
let state = &self.persistent.mode_state;
|
||||||
|
let current = &mut *state.current.borrow_mut();
|
||||||
|
let diffs = self.get_or_create_mode_diffs(current, new);
|
||||||
|
let seat = &self.persistent.seat;
|
||||||
|
for diff in &*diffs {
|
||||||
|
match diff {
|
||||||
|
ModeDiff::Bind(key, mask, f) => {
|
||||||
|
let f = f.clone();
|
||||||
|
seat.bind_masked(*mask, *key, move || f());
|
||||||
|
}
|
||||||
|
ModeDiff::Unbind(key) => {
|
||||||
|
seat.unbind(*key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*current = new.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_or_create_mode_diffs(
|
||||||
|
&self,
|
||||||
|
old: &Rc<ConvertedShortcuts>,
|
||||||
|
new: &Rc<ConvertedShortcuts>,
|
||||||
|
) -> Rc<Vec<ModeDiff>> {
|
||||||
|
let state = &self.persistent.mode_state;
|
||||||
|
let diffs = &mut *state.diffs.borrow_mut();
|
||||||
|
match diffs.entry([Rc::as_ptr(old), Rc::as_ptr(new)]) {
|
||||||
|
Entry::Occupied(o) => o.get().clone(),
|
||||||
|
Entry::Vacant(v) => {
|
||||||
|
let mut diffs = vec![];
|
||||||
|
for (key, sc) in new.iter() {
|
||||||
|
if old.get(key) != Some(sc) {
|
||||||
|
diffs.push(ModeDiff::Bind(*key, sc.mask, sc.shortcut.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for key in old.keys() {
|
||||||
|
if !new.contains_key(key) {
|
||||||
|
diffs.push(ModeDiff::Unbind(*key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.insert(Rc::new(diffs)).clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -533,6 +533,40 @@
|
||||||
"src",
|
"src",
|
||||||
"dst"
|
"dst"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Pushes an input mode on top of the input-mode stack. The mode can be popped\nwith the `pop-mode` action.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-x = { type = \"push-mode\", name = \"navigation\" }\n ```\n",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "push-mode"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the mode."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Temporarily pushes an input mode on top of the input-mode stack. The new mode\nwill automatically be popped when the next shortcut is invoked.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-x = { type = \"latch-mode\", name = \"navigation\" }\n ```\n",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "latch-mode"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the mode."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -949,6 +983,14 @@
|
||||||
"middle-click-paste": {
|
"middle-click-paste": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Configures whether middle-click pasting is enabled.\n\nChanging this has no effect on running applications.\n\nThe default is `true`.\n"
|
"description": "Configures whether middle-click pasting is enabled.\n\nChanging this has no effect on running applications.\n\nThe default is `true`.\n"
|
||||||
|
},
|
||||||
|
"modes": {
|
||||||
|
"description": "Configures the input modes.\n\nModes can be used to define shortcuts that are only active when the mode is\nactive.\n\n- Example\n\n ```toml\n [modes.\"navigation\".shortcuts]\n w = \"focus-up\"\n a = \"focus-left\"\n s = \"focus-down\"\n d = \"focus-right\"\n r = \"focus-above\"\n f = \"focus-below\"\n q = \"focus-prev\"\n e = \"focus-next\"\n ```\n\nModes can be activated with the `push-mode` and `latch-mode` actions.\n",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"description": "",
|
||||||
|
"$ref": "#/$defs/InputMode"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
|
|
@ -1420,6 +1462,33 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"InputMode": {
|
||||||
|
"description": "Defines an input mode.\n\nModes can be used to define shortcuts that are only active when the mode is active.\n\n- Example\n\n ```toml\n [modes.\"navigation\".shortcuts]\n w = \"focus-up\"\n a = \"focus-left\"\n s = \"focus-down\"\n d = \"focus-right\"\n r = \"focus-above\"\n f = \"focus-below\"\n q = \"focus-prev\"\n e = \"focus-next\"\n ```\n",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"parent": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The parent of this input mode.\n\nThis mode inherits all shortcuts from this parent. If this field is not set, then\nit inherits the shortcuts from the top-level shortcuts.\n\nNote that you can disable a shortcut by explicitly assigning it the action `none`.\n"
|
||||||
|
},
|
||||||
|
"shortcuts": {
|
||||||
|
"description": "The shortcuts of this mode.\n\nSee the same field in the top-level `Config` object for a description.\n",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"description": "",
|
||||||
|
"$ref": "#/$defs/Action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"complex-shortcuts": {
|
||||||
|
"description": "The complex shortcuts of this mode.\n\nSee the same field in the top-level `Config` object for a description.\n",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"description": "",
|
||||||
|
"$ref": "#/$defs/ComplexShortcut"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
},
|
||||||
"Keymap": {
|
"Keymap": {
|
||||||
"description": "A keymap.\n",
|
"description": "A keymap.\n",
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
|
|
@ -1692,7 +1761,9 @@
|
||||||
"focus-above",
|
"focus-above",
|
||||||
"focus-tiles",
|
"focus-tiles",
|
||||||
"create-mark",
|
"create-mark",
|
||||||
"jump-to-mark"
|
"jump-to-mark",
|
||||||
|
"clear-modes",
|
||||||
|
"pop-mode"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Status": {
|
"Status": {
|
||||||
|
|
|
||||||
|
|
@ -756,6 +756,46 @@ This table is a tagged union. The variant is determined by the `type` field. It
|
||||||
|
|
||||||
The value of this field should be a [MarkId](#types-MarkId).
|
The value of this field should be a [MarkId](#types-MarkId).
|
||||||
|
|
||||||
|
- `push-mode`:
|
||||||
|
|
||||||
|
Pushes an input mode on top of the input-mode stack. The mode can be popped
|
||||||
|
with the `pop-mode` action.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[shortcuts]
|
||||||
|
alt-x = { type = "push-mode", name = "navigation" }
|
||||||
|
```
|
||||||
|
|
||||||
|
The table has the following fields:
|
||||||
|
|
||||||
|
- `name` (required):
|
||||||
|
|
||||||
|
The name of the mode.
|
||||||
|
|
||||||
|
The value of this field should be a string.
|
||||||
|
|
||||||
|
- `latch-mode`:
|
||||||
|
|
||||||
|
Temporarily pushes an input mode on top of the input-mode stack. The new mode
|
||||||
|
will automatically be popped when the next shortcut is invoked.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[shortcuts]
|
||||||
|
alt-x = { type = "latch-mode", name = "navigation" }
|
||||||
|
```
|
||||||
|
|
||||||
|
The table has the following fields:
|
||||||
|
|
||||||
|
- `name` (required):
|
||||||
|
|
||||||
|
The name of the mode.
|
||||||
|
|
||||||
|
The value of this field should be a string.
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Brightness"></a>
|
<a name="types-Brightness"></a>
|
||||||
### `Brightness`
|
### `Brightness`
|
||||||
|
|
@ -1911,6 +1951,31 @@ The table has the following fields:
|
||||||
|
|
||||||
The value of this field should be a boolean.
|
The value of this field should be a boolean.
|
||||||
|
|
||||||
|
- `modes` (optional):
|
||||||
|
|
||||||
|
Configures the input modes.
|
||||||
|
|
||||||
|
Modes can be used to define shortcuts that are only active when the mode is
|
||||||
|
active.
|
||||||
|
|
||||||
|
- Example
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[modes."navigation".shortcuts]
|
||||||
|
w = "focus-up"
|
||||||
|
a = "focus-left"
|
||||||
|
s = "focus-down"
|
||||||
|
d = "focus-right"
|
||||||
|
r = "focus-above"
|
||||||
|
f = "focus-below"
|
||||||
|
q = "focus-prev"
|
||||||
|
e = "focus-next"
|
||||||
|
```
|
||||||
|
|
||||||
|
Modes can be activated with the `push-mode` and `latch-mode` actions.
|
||||||
|
|
||||||
|
The value of this field should be a table whose values are [InputModes](#types-InputMode).
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Connector"></a>
|
<a name="types-Connector"></a>
|
||||||
### `Connector`
|
### `Connector`
|
||||||
|
|
@ -3025,6 +3090,59 @@ The table has the following fields:
|
||||||
The value of this field should be a boolean.
|
The value of this field should be a boolean.
|
||||||
|
|
||||||
|
|
||||||
|
<a name="types-InputMode"></a>
|
||||||
|
### `InputMode`
|
||||||
|
|
||||||
|
Defines an input mode.
|
||||||
|
|
||||||
|
Modes can be used to define shortcuts that are only active when the mode is active.
|
||||||
|
|
||||||
|
- Example
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[modes."navigation".shortcuts]
|
||||||
|
w = "focus-up"
|
||||||
|
a = "focus-left"
|
||||||
|
s = "focus-down"
|
||||||
|
d = "focus-right"
|
||||||
|
r = "focus-above"
|
||||||
|
f = "focus-below"
|
||||||
|
q = "focus-prev"
|
||||||
|
e = "focus-next"
|
||||||
|
```
|
||||||
|
|
||||||
|
Values of this type should be tables.
|
||||||
|
|
||||||
|
The table has the following fields:
|
||||||
|
|
||||||
|
- `parent` (optional):
|
||||||
|
|
||||||
|
The parent of this input mode.
|
||||||
|
|
||||||
|
This mode inherits all shortcuts from this parent. If this field is not set, then
|
||||||
|
it inherits the shortcuts from the top-level shortcuts.
|
||||||
|
|
||||||
|
Note that you can disable a shortcut by explicitly assigning it the action `none`.
|
||||||
|
|
||||||
|
The value of this field should be a string.
|
||||||
|
|
||||||
|
- `shortcuts` (optional):
|
||||||
|
|
||||||
|
The shortcuts of this mode.
|
||||||
|
|
||||||
|
See the same field in the top-level `Config` object for a description.
|
||||||
|
|
||||||
|
The value of this field should be a table whose values are [Actions](#types-Action).
|
||||||
|
|
||||||
|
- `complex-shortcuts` (optional):
|
||||||
|
|
||||||
|
The complex shortcuts of this mode.
|
||||||
|
|
||||||
|
See the same field in the top-level `Config` object for a description.
|
||||||
|
|
||||||
|
The value of this field should be a table whose values are [ComplexShortcuts](#types-ComplexShortcut).
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Keymap"></a>
|
<a name="types-Keymap"></a>
|
||||||
### `Keymap`
|
### `Keymap`
|
||||||
|
|
||||||
|
|
@ -3830,6 +3948,14 @@ The string should have one of the following values:
|
||||||
|
|
||||||
The next pressed key identifies the mark to jump to.
|
The next pressed key identifies the mark to jump to.
|
||||||
|
|
||||||
|
- `clear-modes`:
|
||||||
|
|
||||||
|
Disables all previously set input modes, clearing the input-mode stack.
|
||||||
|
|
||||||
|
- `pop-mode`:
|
||||||
|
|
||||||
|
Pops the topmost mode from the input-mode stack.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Status"></a>
|
<a name="types-Status"></a>
|
||||||
|
|
|
||||||
|
|
@ -675,6 +675,38 @@ Action:
|
||||||
description: The destination id to copy to.
|
description: The destination id to copy to.
|
||||||
required: true
|
required: true
|
||||||
ref: MarkId
|
ref: MarkId
|
||||||
|
push-mode:
|
||||||
|
description: |
|
||||||
|
Pushes an input mode on top of the input-mode stack. The mode can be popped
|
||||||
|
with the `pop-mode` action.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[shortcuts]
|
||||||
|
alt-x = { type = "push-mode", name = "navigation" }
|
||||||
|
```
|
||||||
|
fields:
|
||||||
|
name:
|
||||||
|
description: The name of the mode.
|
||||||
|
required: true
|
||||||
|
kind: string
|
||||||
|
latch-mode:
|
||||||
|
description: |
|
||||||
|
Temporarily pushes an input mode on top of the input-mode stack. The new mode
|
||||||
|
will automatically be popped when the next shortcut is invoked.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[shortcuts]
|
||||||
|
alt-x = { type = "latch-mode", name = "navigation" }
|
||||||
|
```
|
||||||
|
fields:
|
||||||
|
name:
|
||||||
|
description: The name of the mode.
|
||||||
|
required: true
|
||||||
|
kind: string
|
||||||
|
|
||||||
|
|
||||||
Exec:
|
Exec:
|
||||||
|
|
@ -953,6 +985,10 @@ SimpleActionName:
|
||||||
Interactively jumps to a mark.
|
Interactively jumps to a mark.
|
||||||
|
|
||||||
The next pressed key identifies the mark to jump to.
|
The next pressed key identifies the mark to jump to.
|
||||||
|
- value: clear-modes
|
||||||
|
description: Disables all previously set input modes, clearing the input-mode stack.
|
||||||
|
- value: pop-mode
|
||||||
|
description: Pops the topmost mode from the input-mode stack.
|
||||||
|
|
||||||
|
|
||||||
Color:
|
Color:
|
||||||
|
|
@ -2742,6 +2778,32 @@ Config:
|
||||||
Changing this has no effect on running applications.
|
Changing this has no effect on running applications.
|
||||||
|
|
||||||
The default is `true`.
|
The default is `true`.
|
||||||
|
modes:
|
||||||
|
kind: map
|
||||||
|
values:
|
||||||
|
ref: InputMode
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
Configures the input modes.
|
||||||
|
|
||||||
|
Modes can be used to define shortcuts that are only active when the mode is
|
||||||
|
active.
|
||||||
|
|
||||||
|
- Example
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[modes."navigation".shortcuts]
|
||||||
|
w = "focus-up"
|
||||||
|
a = "focus-left"
|
||||||
|
s = "focus-down"
|
||||||
|
d = "focus-right"
|
||||||
|
r = "focus-above"
|
||||||
|
f = "focus-below"
|
||||||
|
q = "focus-prev"
|
||||||
|
e = "focus-next"
|
||||||
|
```
|
||||||
|
|
||||||
|
Modes can be activated with the `push-mode` and `latch-mode` actions.
|
||||||
|
|
||||||
|
|
||||||
Idle:
|
Idle:
|
||||||
|
|
@ -3893,3 +3955,54 @@ MarkId:
|
||||||
Identifies a mark with an arbitrary string.
|
Identifies a mark with an arbitrary string.
|
||||||
kind: string
|
kind: string
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
|
|
||||||
|
InputMode:
|
||||||
|
kind: table
|
||||||
|
description: |
|
||||||
|
Defines an input mode.
|
||||||
|
|
||||||
|
Modes can be used to define shortcuts that are only active when the mode is active.
|
||||||
|
|
||||||
|
- Example
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[modes."navigation".shortcuts]
|
||||||
|
w = "focus-up"
|
||||||
|
a = "focus-left"
|
||||||
|
s = "focus-down"
|
||||||
|
d = "focus-right"
|
||||||
|
r = "focus-above"
|
||||||
|
f = "focus-below"
|
||||||
|
q = "focus-prev"
|
||||||
|
e = "focus-next"
|
||||||
|
```
|
||||||
|
fields:
|
||||||
|
parent:
|
||||||
|
kind: string
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
The parent of this input mode.
|
||||||
|
|
||||||
|
This mode inherits all shortcuts from this parent. If this field is not set, then
|
||||||
|
it inherits the shortcuts from the top-level shortcuts.
|
||||||
|
|
||||||
|
Note that you can disable a shortcut by explicitly assigning it the action `none`.
|
||||||
|
shortcuts:
|
||||||
|
kind: map
|
||||||
|
values:
|
||||||
|
ref: Action
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
The shortcuts of this mode.
|
||||||
|
|
||||||
|
See the same field in the top-level `Config` object for a description.
|
||||||
|
complex-shortcuts:
|
||||||
|
kind: map
|
||||||
|
values:
|
||||||
|
ref: ComplexShortcut
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
The complex shortcuts of this mode.
|
||||||
|
|
||||||
|
See the same field in the top-level `Config` object for a description.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue