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 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,
|
||||
|
|
@ -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 {
|
||||
fn into_fn(self, state: &Rc<State>) -> Box<dyn Fn()> {
|
||||
self.into_fn_impl(&BoxFnBuilder, state)
|
||||
|
|
@ -100,6 +116,10 @@ impl Action {
|
|||
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 {
|
||||
macro_rules! client_action {
|
||||
($name:ident, $opt:expr) => {{
|
||||
|
|
@ -187,6 +207,10 @@ impl Action {
|
|||
let persistent = state.persistent.clone();
|
||||
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();
|
||||
|
|
@ -355,6 +379,18 @@ impl Action {
|
|||
let persistent = state.persistent.clone();
|
||||
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()>>)>;
|
||||
|
||||
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) {
|
||||
|
|
@ -998,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>) {
|
||||
|
|
@ -1088,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);
|
||||
|
|
@ -1118,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);
|
||||
}
|
||||
|
|
@ -1334,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue