Merge pull request #529 from mahkoh/jorth/modes
toml-config: add input modes
This commit is contained in:
commit
25addc9bc2
10 changed files with 862 additions and 100 deletions
|
|
@ -8,6 +8,7 @@ mod parsers;
|
|||
mod spanned;
|
||||
mod value;
|
||||
|
||||
pub use crate::config::parsers::input_mode::InputMode;
|
||||
use {
|
||||
crate::{
|
||||
config::{
|
||||
|
|
@ -81,6 +82,7 @@ pub enum SimpleCommand {
|
|||
FocusTiles,
|
||||
CreateMark,
|
||||
JumpToMark,
|
||||
PopMode(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -167,6 +169,10 @@ pub enum Action {
|
|||
CreateMark(u32),
|
||||
JumpToMark(u32),
|
||||
CopyMark(u32, u32),
|
||||
SetMode {
|
||||
name: String,
|
||||
latch: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
|
@ -502,6 +508,7 @@ pub struct Config {
|
|||
pub show_bar: Option<bool>,
|
||||
pub focus_history: Option<FocusHistory>,
|
||||
pub middle_click_paste: Option<bool>,
|
||||
pub input_modes: AHashMap<String, InputMode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ mod gfx_api;
|
|||
mod idle;
|
||||
mod input;
|
||||
mod input_match;
|
||||
pub mod input_mode;
|
||||
pub mod keymap;
|
||||
mod libei;
|
||||
mod log_level;
|
||||
|
|
|
|||
|
|
@ -151,6 +151,8 @@ impl ActionParser<'_> {
|
|||
"focus-tiles" => FocusTiles,
|
||||
"create-mark" => CreateMark,
|
||||
"jump-to-mark" => JumpToMark,
|
||||
"clear-modes" => PopMode(false),
|
||||
"pop-mode" => PopMode(true),
|
||||
_ => {
|
||||
return Err(
|
||||
ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)
|
||||
|
|
@ -414,6 +416,22 @@ impl ActionParser<'_> {
|
|||
.map_spanned_err(ActionParserError::CopyMark)?;
|
||||
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<'_> {
|
||||
|
|
@ -471,6 +489,8 @@ impl Parser for ActionParser<'_> {
|
|||
"create-mark" => self.parse_create_mark(&mut ext),
|
||||
"jump-to-mark" => self.parse_jump_to_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 => {
|
||||
ext.ignore_unused();
|
||||
return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span));
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ use {
|
|||
gfx_api::GfxApiParser,
|
||||
idle::IdleParser,
|
||||
input::InputsParser,
|
||||
input_mode::InputModesParser,
|
||||
keymap::KeymapParser,
|
||||
libei::LibeiParser,
|
||||
log_level::LogLevelParser,
|
||||
|
|
@ -44,6 +45,7 @@ use {
|
|||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
indexmap::IndexMap,
|
||||
std::collections::HashSet,
|
||||
thiserror::Error,
|
||||
|
|
@ -136,7 +138,7 @@ impl Parser for ConfigParser<'_> {
|
|||
show_bar,
|
||||
focus_history_val,
|
||||
),
|
||||
(middle_click_paste,),
|
||||
(middle_click_paste, input_modes_val),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(val("keymap")),
|
||||
|
|
@ -186,7 +188,7 @@ impl Parser for ConfigParser<'_> {
|
|||
recover(opt(bol("show-bar"))),
|
||||
opt(val("focus-history")),
|
||||
),
|
||||
(recover(opt(bol("middle-click-paste"))),),
|
||||
(recover(opt(bol("middle-click-paste"))), opt(val("modes"))),
|
||||
))?;
|
||||
let mut keymap = None;
|
||||
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 {
|
||||
keymap,
|
||||
repeat_rate,
|
||||
|
|
@ -516,6 +527,7 @@ impl Parser for ConfigParser<'_> {
|
|||
show_bar: show_bar.despan(),
|
||||
focus_history,
|
||||
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 rules;
|
||||
mod shortcuts;
|
||||
mod toml;
|
||||
|
||||
use {
|
||||
crate::{
|
||||
config::{
|
||||
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,
|
||||
},
|
||||
rules::{MatcherTemp, RuleMapper},
|
||||
shortcuts::ModeState,
|
||||
},
|
||||
ahash::{AHashMap, AHashSet},
|
||||
error_reporter::Report,
|
||||
|
|
@ -31,7 +33,7 @@ use {
|
|||
set_libei_socket_enabled,
|
||||
},
|
||||
is_reload,
|
||||
keyboard::{Keymap, ModifiedKeySym},
|
||||
keyboard::Keymap,
|
||||
logging::set_log_level,
|
||||
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,
|
||||
|
|
@ -65,35 +67,64 @@ fn default_seat() -> Seat {
|
|||
}
|
||||
|
||||
trait FnBuilder: Sized {
|
||||
fn new<F: Fn() + 'static>(f: F) -> Self;
|
||||
type Output;
|
||||
|
||||
#[expect(clippy::wrong_self_convention)]
|
||||
fn new<F: Fn() + 'static>(&self, f: F) -> Self::Output;
|
||||
}
|
||||
|
||||
impl FnBuilder for Box<dyn Fn()> {
|
||||
fn new<F: Fn() + 'static>(f: F) -> Self {
|
||||
struct BoxFnBuilder;
|
||||
|
||||
impl FnBuilder for BoxFnBuilder {
|
||||
type Output = Box<dyn Fn()>;
|
||||
|
||||
fn new<F: Fn() + 'static>(&self, f: F) -> Self::Output {
|
||||
Box::new(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FnBuilder for Rc<dyn Fn()> {
|
||||
fn new<F: Fn() + 'static>(f: F) -> Self {
|
||||
struct RcFnBuilder;
|
||||
|
||||
impl FnBuilder for RcFnBuilder {
|
||||
type Output = Rc<dyn Fn()>;
|
||||
|
||||
fn new<F: Fn() + 'static>(&self, f: F) -> Self::Output {
|
||||
Rc::new(f)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
fn into_fn(self, state: &Rc<State>) -> Box<dyn Fn()> {
|
||||
self.into_fn_impl(state)
|
||||
self.into_fn_impl(&BoxFnBuilder, state)
|
||||
}
|
||||
|
||||
fn into_rc_fn(self, state: &Rc<State>) -> Rc<dyn Fn()> {
|
||||
self.into_fn_impl(state)
|
||||
self.into_fn_impl(&RcFnBuilder, state)
|
||||
}
|
||||
|
||||
fn into_fn_impl<B: FnBuilder>(self, state: &Rc<State>) -> B {
|
||||
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 {
|
||||
macro_rules! client_action {
|
||||
($name:ident, $opt:expr) => {{
|
||||
let state = state.clone();
|
||||
B::new(move || {
|
||||
b.new(move || {
|
||||
if let Some($name) = state.client.get() {
|
||||
$opt
|
||||
}
|
||||
|
|
@ -104,7 +135,7 @@ impl Action {
|
|||
macro_rules! window_or_seat {
|
||||
($name:ident, $expr:expr) => {{
|
||||
let state = state.clone();
|
||||
B::new(move || {
|
||||
b.new(move || {
|
||||
if let Some($name) = state.window.get() {
|
||||
if let Some($name) = $name {
|
||||
$expr;
|
||||
|
|
@ -118,7 +149,7 @@ impl Action {
|
|||
}
|
||||
match self {
|
||||
Action::SimpleCommand { cmd } => match cmd {
|
||||
SimpleCommand::Focus(dir) => B::new(move || s.focus(dir)),
|
||||
SimpleCommand::Focus(dir) => b.new(move || s.focus(dir)),
|
||||
SimpleCommand::Move(dir) => window_or_seat!(s, s.move_(dir)),
|
||||
SimpleCommand::Split(axis) => window_or_seat!(s, s.create_split(axis)),
|
||||
SimpleCommand::ToggleSplit => window_or_seat!(s, s.toggle_split()),
|
||||
|
|
@ -127,75 +158,79 @@ impl Action {
|
|||
SimpleCommand::SetMono(b) => window_or_seat!(s, s.set_mono(b)),
|
||||
SimpleCommand::ToggleFullscreen => window_or_seat!(s, s.toggle_fullscreen()),
|
||||
SimpleCommand::SetFullscreen(b) => window_or_seat!(s, s.set_fullscreen(b)),
|
||||
SimpleCommand::FocusParent => B::new(move || s.focus_parent()),
|
||||
SimpleCommand::FocusParent => b.new(move || s.focus_parent()),
|
||||
SimpleCommand::Close => window_or_seat!(s, s.close()),
|
||||
SimpleCommand::DisablePointerConstraint => {
|
||||
B::new(move || s.disable_pointer_constraint())
|
||||
b.new(move || s.disable_pointer_constraint())
|
||||
}
|
||||
SimpleCommand::ToggleFloating => window_or_seat!(s, s.toggle_floating()),
|
||||
SimpleCommand::SetFloating(b) => window_or_seat!(s, s.set_floating(b)),
|
||||
SimpleCommand::Quit => B::new(quit),
|
||||
SimpleCommand::Quit => b.new(quit),
|
||||
SimpleCommand::ReloadConfigToml => {
|
||||
let persistent = state.persistent.clone();
|
||||
B::new(move || load_config(false, &persistent))
|
||||
b.new(move || load_config(false, &persistent))
|
||||
}
|
||||
SimpleCommand::ReloadConfigSo => B::new(reload),
|
||||
SimpleCommand::None => B::new(|| ()),
|
||||
SimpleCommand::Forward(bool) => B::new(move || s.set_forward(bool)),
|
||||
SimpleCommand::ReloadConfigSo => b.new(reload),
|
||||
SimpleCommand::None => b.new(|| ()),
|
||||
SimpleCommand::Forward(bool) => b.new(move || s.set_forward(bool)),
|
||||
SimpleCommand::EnableWindowManagement(bool) => {
|
||||
B::new(move || s.set_window_management_enabled(bool))
|
||||
b.new(move || s.set_window_management_enabled(bool))
|
||||
}
|
||||
SimpleCommand::SetFloatAboveFullscreen(bool) => {
|
||||
B::new(move || set_float_above_fullscreen(bool))
|
||||
b.new(move || set_float_above_fullscreen(bool))
|
||||
}
|
||||
SimpleCommand::ToggleFloatAboveFullscreen => B::new(toggle_float_above_fullscreen),
|
||||
SimpleCommand::ToggleFloatAboveFullscreen => b.new(toggle_float_above_fullscreen),
|
||||
SimpleCommand::SetFloatPinned(pinned) => {
|
||||
window_or_seat!(s, s.set_float_pinned(pinned))
|
||||
}
|
||||
SimpleCommand::ToggleFloatPinned => window_or_seat!(s, s.toggle_float_pinned()),
|
||||
SimpleCommand::KillClient => client_action!(c, c.kill()),
|
||||
SimpleCommand::ShowBar(show) => B::new(move || set_show_bar(show)),
|
||||
SimpleCommand::ToggleBar => B::new(toggle_show_bar),
|
||||
SimpleCommand::ShowBar(show) => b.new(move || set_show_bar(show)),
|
||||
SimpleCommand::ToggleBar => b.new(toggle_show_bar),
|
||||
SimpleCommand::FocusHistory(timeline) => {
|
||||
let persistent = state.persistent.clone();
|
||||
B::new(move || persistent.seat.focus_history(timeline))
|
||||
b.new(move || persistent.seat.focus_history(timeline))
|
||||
}
|
||||
SimpleCommand::FocusLayerRel(direction) => {
|
||||
let persistent = state.persistent.clone();
|
||||
B::new(move || persistent.seat.focus_layer_rel(direction))
|
||||
b.new(move || persistent.seat.focus_layer_rel(direction))
|
||||
}
|
||||
SimpleCommand::FocusTiles => {
|
||||
let persistent = state.persistent.clone();
|
||||
B::new(move || persistent.seat.focus_tiles())
|
||||
b.new(move || persistent.seat.focus_tiles())
|
||||
}
|
||||
SimpleCommand::CreateMark => {
|
||||
let persistent = state.persistent.clone();
|
||||
B::new(move || persistent.seat.create_mark(None))
|
||||
b.new(move || persistent.seat.create_mark(None))
|
||||
}
|
||||
SimpleCommand::JumpToMark => {
|
||||
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 } => {
|
||||
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
|
||||
B::new(move || {
|
||||
b.new(move || {
|
||||
for action in &actions {
|
||||
action();
|
||||
}
|
||||
})
|
||||
}
|
||||
Action::Exec { exec } => B::new(move || create_command(&exec).spawn()),
|
||||
Action::SwitchToVt { num } => B::new(move || switch_to_vt(num)),
|
||||
Action::Exec { exec } => b.new(move || create_command(&exec).spawn()),
|
||||
Action::SwitchToVt { num } => b.new(move || switch_to_vt(num)),
|
||||
Action::ShowWorkspace { name } => {
|
||||
let workspace = get_workspace(&name);
|
||||
B::new(move || s.show_workspace(workspace))
|
||||
b.new(move || s.show_workspace(workspace))
|
||||
}
|
||||
Action::MoveToWorkspace { name } => {
|
||||
let workspace = get_workspace(&name);
|
||||
window_or_seat!(s, s.set_workspace(workspace))
|
||||
}
|
||||
Action::ConfigureConnector { con } => B::new(move || {
|
||||
Action::ConfigureConnector { con } => b.new(move || {
|
||||
for c in connectors() {
|
||||
if con.match_.matches(c) {
|
||||
con.apply(c);
|
||||
|
|
@ -204,7 +239,7 @@ impl Action {
|
|||
}),
|
||||
Action::ConfigureInput { input } => {
|
||||
let state = state.clone();
|
||||
B::new(move || {
|
||||
b.new(move || {
|
||||
for c in input_devices() {
|
||||
if input.match_.matches(c, &state) {
|
||||
input.apply(c, &state);
|
||||
|
|
@ -214,7 +249,7 @@ impl Action {
|
|||
}
|
||||
Action::ConfigureOutput { out } => {
|
||||
let state = state.clone();
|
||||
B::new(move || {
|
||||
b.new(move || {
|
||||
for c in connectors() {
|
||||
if out.match_.matches(c, &state) {
|
||||
out.apply(c);
|
||||
|
|
@ -222,36 +257,36 @@ impl Action {
|
|||
}
|
||||
})
|
||||
}
|
||||
Action::SetEnv { env } => B::new(move || {
|
||||
Action::SetEnv { env } => b.new(move || {
|
||||
for (k, v) in &env {
|
||||
set_env(k, v);
|
||||
}
|
||||
}),
|
||||
Action::UnsetEnv { env } => B::new(move || {
|
||||
Action::UnsetEnv { env } => b.new(move || {
|
||||
for k in &env {
|
||||
unset_env(k);
|
||||
}
|
||||
}),
|
||||
Action::SetKeymap { map } => {
|
||||
let state = state.clone();
|
||||
B::new(move || state.set_keymap(&map))
|
||||
b.new(move || state.set_keymap(&map))
|
||||
}
|
||||
Action::SetStatus { status } => {
|
||||
let state = state.clone();
|
||||
B::new(move || state.set_status(&status))
|
||||
b.new(move || state.set_status(&status))
|
||||
}
|
||||
Action::SetTheme { theme } => {
|
||||
let state = state.clone();
|
||||
B::new(move || state.apply_theme(&theme))
|
||||
b.new(move || state.apply_theme(&theme))
|
||||
}
|
||||
Action::SetLogLevel { level } => B::new(move || set_log_level(level)),
|
||||
Action::SetGfxApi { api } => B::new(move || set_gfx_api(api)),
|
||||
Action::SetLogLevel { level } => b.new(move || set_log_level(level)),
|
||||
Action::SetGfxApi { api } => b.new(move || set_gfx_api(api)),
|
||||
Action::ConfigureDirectScanout { enabled } => {
|
||||
B::new(move || set_direct_scanout_enabled(enabled))
|
||||
b.new(move || set_direct_scanout_enabled(enabled))
|
||||
}
|
||||
Action::ConfigureDrmDevice { dev } => {
|
||||
let state = state.clone();
|
||||
B::new(move || {
|
||||
b.new(move || {
|
||||
for d in drm_devices() {
|
||||
if dev.match_.matches(d, &state) {
|
||||
dev.apply(d);
|
||||
|
|
@ -261,7 +296,7 @@ impl Action {
|
|||
}
|
||||
Action::SetRenderDevice { dev } => {
|
||||
let state = state.clone();
|
||||
B::new(move || {
|
||||
b.new(move || {
|
||||
for d in drm_devices() {
|
||||
if dev.matches(d, &state) {
|
||||
d.make_render_device();
|
||||
|
|
@ -269,7 +304,7 @@ impl Action {
|
|||
}
|
||||
})
|
||||
}
|
||||
Action::ConfigureIdle { idle, grace_period } => B::new(move || {
|
||||
Action::ConfigureIdle { idle, grace_period } => b.new(move || {
|
||||
if let Some(idle) = idle {
|
||||
set_idle(Some(idle))
|
||||
}
|
||||
|
|
@ -279,7 +314,7 @@ impl Action {
|
|||
}),
|
||||
Action::MoveToOutput { output, workspace } => {
|
||||
let state = state.clone();
|
||||
B::new(move || {
|
||||
b.new(move || {
|
||||
let output = 'get_output: {
|
||||
for connector in connectors() {
|
||||
if connector.connected() && output.matches(connector, &state) {
|
||||
|
|
@ -295,13 +330,13 @@ impl Action {
|
|||
})
|
||||
}
|
||||
Action::SetRepeatRate { rate } => {
|
||||
B::new(move || s.set_repeat_rate(rate.rate, rate.delay))
|
||||
b.new(move || s.set_repeat_rate(rate.rate, rate.delay))
|
||||
}
|
||||
Action::DefineAction { name, action } => {
|
||||
let state = state.clone();
|
||||
let action = action.into_rc_fn(&state);
|
||||
let name = Rc::new(name);
|
||||
B::new(move || {
|
||||
b.new(move || {
|
||||
state
|
||||
.persistent
|
||||
.actions
|
||||
|
|
@ -311,13 +346,13 @@ impl Action {
|
|||
}
|
||||
Action::UndefineAction { name } => {
|
||||
let state = state.clone();
|
||||
B::new(move || {
|
||||
b.new(move || {
|
||||
state.persistent.actions.borrow_mut().remove(&name);
|
||||
})
|
||||
}
|
||||
Action::NamedAction { name } => {
|
||||
let state = state.clone();
|
||||
B::new(move || {
|
||||
b.new(move || {
|
||||
let depth = state.action_depth.get();
|
||||
if depth >= state.action_depth_max {
|
||||
log::error!("Maximum action depth reached");
|
||||
|
|
@ -334,15 +369,27 @@ impl Action {
|
|||
}
|
||||
Action::CreateMark(m) => {
|
||||
let persistent = state.persistent.clone();
|
||||
B::new(move || persistent.seat.create_mark(Some(m)))
|
||||
b.new(move || persistent.seat.create_mark(Some(m)))
|
||||
}
|
||||
Action::JumpToMark(m) => {
|
||||
let persistent = state.persistent.clone();
|
||||
B::new(move || persistent.seat.jump_to_mark(Some(m)))
|
||||
b.new(move || persistent.seat.jump_to_mark(Some(m)))
|
||||
}
|
||||
Action::CopyMark(s, d) => {
|
||||
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);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -762,43 +809,6 @@ impl Drop for State {
|
|||
type SwitchActions = Vec<(InputMatch, AHashMap<SwitchEvent, Box<dyn Fn()>>)>;
|
||||
|
||||
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> {
|
||||
let map = match map {
|
||||
ConfigKeymap::Named(n) => match self.keymaps.get(n) {
|
||||
|
|
@ -987,13 +997,13 @@ struct PersistentState {
|
|||
seen_outputs: RefCell<AHashSet<OutputId>>,
|
||||
default: Config,
|
||||
seat: Seat,
|
||||
binds: RefCell<AHashSet<ModifiedKeySym>>,
|
||||
#[expect(clippy::type_complexity)]
|
||||
actions: RefCell<AHashMap<Rc<String>, Rc<dyn Fn()>>>,
|
||||
client_rules: Cell<Vec<MatcherTemp<ClientRule>>>,
|
||||
client_rule_mapper: RefCell<Option<RuleMapper<ClientRule>>>,
|
||||
window_rules: Cell<Vec<MatcherTemp<WindowRule>>>,
|
||||
mark_names: RefCell<AHashMap<String, u32>>,
|
||||
mode_state: ModeState,
|
||||
}
|
||||
|
||||
fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
||||
|
|
@ -1077,6 +1087,7 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
|||
client: Default::default(),
|
||||
window: Default::default(),
|
||||
});
|
||||
state.clear_modes_after_reload();
|
||||
let (client_rules, client_rule_mapper) = state.create_rules(&config.client_rules);
|
||||
persistent.client_rules.set(client_rules);
|
||||
*state.persistent.client_rule_mapper.borrow_mut() = Some(client_rule_mapper);
|
||||
|
|
@ -1107,8 +1118,7 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
|||
None => on_idle(|| ()),
|
||||
Some(a) => on_idle(a.into_fn(&state)),
|
||||
}
|
||||
state.unbind_all();
|
||||
state.apply_shortcuts(config.shortcuts);
|
||||
state.init_modes(&config.shortcuts, &config.input_modes);
|
||||
if let Some(keymap) = config.keymap {
|
||||
state.set_keymap(&keymap);
|
||||
}
|
||||
|
|
@ -1323,18 +1333,19 @@ pub fn configure() {
|
|||
seen_outputs: Default::default(),
|
||||
default: default.unwrap(),
|
||||
seat: default_seat(),
|
||||
binds: Default::default(),
|
||||
actions: Default::default(),
|
||||
client_rules: Default::default(),
|
||||
client_rule_mapper: Default::default(),
|
||||
window_rules: Default::default(),
|
||||
mark_names,
|
||||
mode_state: Default::default(),
|
||||
});
|
||||
{
|
||||
let p = persistent.clone();
|
||||
on_unload(move || {
|
||||
p.actions.borrow_mut().clear();
|
||||
p.client_rule_mapper.borrow_mut().take();
|
||||
p.mode_state.clear();
|
||||
});
|
||||
}
|
||||
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",
|
||||
"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": {
|
||||
"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"
|
||||
},
|
||||
"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": []
|
||||
|
|
@ -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": {
|
||||
"description": "A keymap.\n",
|
||||
"anyOf": [
|
||||
|
|
@ -1692,7 +1761,9 @@
|
|||
"focus-above",
|
||||
"focus-tiles",
|
||||
"create-mark",
|
||||
"jump-to-mark"
|
||||
"jump-to-mark",
|
||||
"clear-modes",
|
||||
"pop-mode"
|
||||
]
|
||||
},
|
||||
"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).
|
||||
|
||||
- `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>
|
||||
### `Brightness`
|
||||
|
|
@ -1911,6 +1951,31 @@ The table has the following fields:
|
|||
|
||||
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>
|
||||
### `Connector`
|
||||
|
|
@ -3025,6 +3090,59 @@ The table has the following fields:
|
|||
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>
|
||||
### `Keymap`
|
||||
|
||||
|
|
@ -3830,6 +3948,14 @@ The string should have one of the following values:
|
|||
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -675,6 +675,38 @@ Action:
|
|||
description: The destination id to copy to.
|
||||
required: true
|
||||
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:
|
||||
|
|
@ -953,6 +985,10 @@ SimpleActionName:
|
|||
Interactively jumps to a mark.
|
||||
|
||||
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:
|
||||
|
|
@ -2742,6 +2778,32 @@ Config:
|
|||
Changing this has no effect on running applications.
|
||||
|
||||
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:
|
||||
|
|
@ -3893,3 +3955,54 @@ MarkId:
|
|||
Identifies a mark with an arbitrary string.
|
||||
kind: string
|
||||
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