Skip to content

Commit

Permalink
config: add support for mod masks in shortcuts
Browse files Browse the repository at this point in the history
  • Loading branch information
mahkoh committed Apr 16, 2024
1 parent 27f30f8 commit 6ca044a
Show file tree
Hide file tree
Showing 15 changed files with 497 additions and 92 deletions.
54 changes: 39 additions & 15 deletions jay-config/src/_private/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use {
},
exec::Command,
input::{acceleration::AccelProfile, capability::Capability, InputDevice, Seat},
keyboard::Keymap,
keyboard::{
mods::{Modifiers, RELEASE},
Keymap,
},
logging::LogLevel,
tasks::{JoinHandle, JoinSlot},
theme::{colors::Colorable, sized::Resizable, Color},
Expand Down Expand Up @@ -64,12 +67,17 @@ fn ignore_panic(name: &str, f: impl FnOnce()) {
}
}

struct KeyHandler {
mask: Modifiers,
cb: Callback,
}

pub(crate) struct Client {
configure: extern "C" fn(),
srv_data: *const u8,
srv_unref: unsafe extern "C" fn(data: *const u8),
srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize),
key_handlers: RefCell<HashMap<(Seat, ModifiedKeySym), Callback>>,
key_handlers: RefCell<HashMap<(Seat, ModifiedKeySym), KeyHandler>>,
timer_handlers: RefCell<HashMap<Timer, Callback>>,
response: RefCell<Vec<Response>>,
on_new_seat: RefCell<Option<Callback<Seat>>>,
Expand Down Expand Up @@ -915,33 +923,45 @@ impl Client {
keymap
}

pub fn bind<T: Into<ModifiedKeySym>, F: FnMut() + 'static>(
pub fn bind_masked<F: FnMut() + 'static>(
&self,
seat: Seat,
mod_sym: T,
mut mod_mask: Modifiers,
mod_sym: ModifiedKeySym,
mut f: F,
) {
let mod_sym = mod_sym.into();
mod_mask |= mod_sym.mods | RELEASE;
let register = {
let mut kh = self.key_handlers.borrow_mut();
let f = cb(move |_| f());
let cb = cb(move |_| f());
match kh.entry((seat, mod_sym)) {
Entry::Occupied(mut o) => {
*o.get_mut() = f;
false
let o = o.get_mut();
o.cb = cb;
mem::replace(&mut o.mask, mod_mask) != mod_mask
}
Entry::Vacant(v) => {
v.insert(f);
v.insert(KeyHandler { mask: mod_mask, cb });
true
}
}
};
if register {
self.send(&ClientMessage::AddShortcut {
seat,
mods: mod_sym.mods,
sym: mod_sym.sym,
});
let msg = if !mod_mask.0 == 0 {
ClientMessage::AddShortcut {
seat,
mods: mod_sym.mods,
sym: mod_sym.sym,
}
} else {
ClientMessage::AddShortcut2 {
seat,
mods: mod_sym.mods,
mod_mask,
sym: mod_sym.sym,
}
};
self.send(&msg);
}
}

Expand Down Expand Up @@ -1104,7 +1124,11 @@ impl Client {
}
ServerMessage::InvokeShortcut { seat, mods, sym } => {
let ms = ModifiedKeySym { mods, sym };
let handler = self.key_handlers.borrow_mut().get(&(seat, ms)).cloned();
let handler = self
.key_handlers
.borrow_mut()
.get(&(seat, ms))
.map(|k| k.cb.clone());
if let Some(handler) = handler {
run_cb("shortcut", &handler, ());
}
Expand Down
6 changes: 6 additions & 0 deletions jay-config/src/_private/ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,12 @@ pub enum ClientMessage<'a> {
seat: Seat,
forward: bool,
},
AddShortcut2 {
seat: Seat,
mods: Modifiers,
mod_mask: Modifiers,
sym: KeySym,
},
}

#[derive(Serialize, Deserialize, Debug)]
Expand Down
31 changes: 28 additions & 3 deletions jay-config/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub mod capability;
use {
crate::{
input::{acceleration::AccelProfile, capability::Capability},
keyboard::Keymap,
keyboard::{mods::Modifiers, Keymap},
Axis, Direction, ModifiedKeySym, Workspace,
_private::{ipc::WorkspaceSource, DEFAULT_SEAT_NAME},
video::Connector,
Expand Down Expand Up @@ -188,12 +188,37 @@ impl Seat {
/// CapsLock and NumLock are ignored during modifier evaluation. Therefore, bindings
/// containing these modifiers will never be invoked.
pub fn bind<T: Into<ModifiedKeySym>, F: FnMut() + 'static>(self, mod_sym: T, f: F) {
get!().bind(self, mod_sym, f)
self.bind_masked(Modifiers(!0), mod_sym, f)
}

/// Creates a compositor-wide hotkey while ignoring some modifiers.
///
/// This is similar to `bind` except that only the masked modifiers are considered.
///
/// For example, if this function is invoked with `mod_mask = Modifiers::NONE` and
/// `mod_sym = SYM_XF86AudioRaiseVolume`, then the callback will be invoked whenever
/// `SYM_XF86AudioRaiseVolume` is pressed. Even if the user is simultaneously holding
/// the shift key which would otherwise prevent the callback from taking effect.
///
/// For example, if this function is invoked with `mod_mask = CTRL | SHIFT` and
/// `mod_sym = CTRL | SYM_x`, then the callback will be invoked whenever the user
/// presses `ctrl+x` without pressing the shift key. Even if the user is
/// simultaneously holding the alt key.
///
/// If `mod_sym` contains any modifiers, then these modifiers are automatically added
/// to the mask. The synthetic `RELEASE` modifier is always added to the mask.
pub fn bind_masked<T: Into<ModifiedKeySym>, F: FnMut() + 'static>(
self,
mod_mask: Modifiers,
mod_sym: T,
f: F,
) {
get!().bind_masked(self, mod_mask, mod_sym.into(), f)
}

/// Unbinds a hotkey.
pub fn unbind<T: Into<ModifiedKeySym>>(self, mod_sym: T) {
get!().unbind(self, mod_sym)
get!().unbind(self, mod_sym.into())
}

/// Moves the keyboard focus of the seat in the specified direction.
Expand Down
5 changes: 5 additions & 0 deletions jay-config/src/keyboard/mods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ use {
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Default, Hash, Debug)]
pub struct Modifiers(pub u32);

impl Modifiers {
/// No modifiers.
pub const NONE: Self = Modifiers(0);
}

/// The Shift modifier
pub const SHIFT: Modifiers = Modifiers(1 << 0);
/// The CapsLock modifier.
Expand Down
13 changes: 11 additions & 2 deletions src/config/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1127,11 +1127,12 @@ impl ConfigProxyHandler {
fn handle_add_shortcut(
&self,
seat: Seat,
mod_mask: Modifiers,
mods: Modifiers,
sym: KeySym,
) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
seat.add_shortcut(mods, sym);
seat.add_shortcut(mod_mask, mods, sym);
Ok(())
}

Expand Down Expand Up @@ -1499,7 +1500,7 @@ impl ConfigProxyHandler {
self.handle_set_split(seat, axis).wrn("set_split")?
}
ClientMessage::AddShortcut { seat, mods, sym } => self
.handle_add_shortcut(seat, mods, sym)
.handle_add_shortcut(seat, Modifiers(!0), mods, sym)
.wrn("add_shortcut")?,
ClientMessage::RemoveShortcut { seat, mods, sym } => self
.handle_remove_shortcut(seat, mods, sym)
Expand Down Expand Up @@ -1773,6 +1774,14 @@ impl ConfigProxyHandler {
ClientMessage::SetForward { seat, forward } => {
self.handle_set_forward(seat, forward).wrn("set_forward")?
}
ClientMessage::AddShortcut2 {
seat,
mod_mask,
mods,
sym,
} => self
.handle_add_shortcut(seat, mod_mask, mods, sym)
.wrn("add_shortcut")?,
}
Ok(())
}
Expand Down
3 changes: 1 addition & 2 deletions src/ifs/wl_seat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ use {
xkbcommon::{DynKeyboardState, KeyboardState, KeymapId, XkbKeymap, XkbState},
},
ahash::AHashMap,
jay_config::keyboard::mods::Modifiers,
smallvec::SmallVec,
std::{
cell::{Cell, RefCell},
Expand Down Expand Up @@ -160,7 +159,7 @@ pub struct WlSeatGlobal {
pointer_owner: PointerOwnerHolder,
kb_owner: KbOwnerHolder,
dropped_dnd: RefCell<Option<DroppedDnd>>,
shortcuts: CopyHashMap<(u32, u32), Modifiers>,
shortcuts: RefCell<AHashMap<u32, SmallMap<u32, u32, 2>>>,
queue_link: Cell<Option<LinkedNode<Rc<Self>>>>,
tree_changed_handler: Cell<Option<SpawnedFuture<()>>>,
output: CloneCell<Rc<OutputNode>>,
Expand Down
34 changes: 24 additions & 10 deletions src/ifs/wl_seat/event_handling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use {
ModifiedKeySym,
},
smallvec::SmallVec,
std::{cell::RefCell, rc::Rc},
std::{cell::RefCell, collections::hash_map::Entry, rc::Rc},
};

#[derive(Default)]
Expand Down Expand Up @@ -380,13 +380,18 @@ impl WlSeatGlobal {
if state == wl_keyboard::RELEASED {
mods |= RELEASE.0;
}
let scs = &*self.shortcuts.borrow();
let keysyms = xkb_state.unmodified_keysyms(key);
for &sym in keysyms {
if let Some(mods) = self.shortcuts.get(&(mods, sym)) {
shortcuts.push(ModifiedKeySym {
mods,
sym: KeySym(sym),
});
if let Some(key_mods) = scs.get(&sym) {
for (key_mods, mask) in key_mods {
if mods & mask == key_mods {
shortcuts.push(ModifiedKeySym {
mods: Modifiers(key_mods),
sym: KeySym(sym),
});
}
}
}
}
}
Expand Down Expand Up @@ -608,15 +613,24 @@ impl WlSeatGlobal {
}

pub fn clear_shortcuts(&self) {
self.shortcuts.clear();
self.shortcuts.borrow_mut().clear();
}

pub fn add_shortcut(&self, mods: Modifiers, keysym: KeySym) {
self.shortcuts.set((mods.0, keysym.0), mods);
pub fn add_shortcut(&self, mod_mask: Modifiers, mods: Modifiers, keysym: KeySym) {
self.shortcuts
.borrow_mut()
.entry(keysym.0)
.or_default()
.insert(mods.0, mod_mask.0);
}

pub fn remove_shortcut(&self, mods: Modifiers, keysym: KeySym) {
self.shortcuts.remove(&(mods.0, keysym.0));
if let Entry::Occupied(mut oe) = self.shortcuts.borrow_mut().entry(keysym.0) {
oe.get_mut().remove(&mods.0);
if oe.get().is_empty() {
oe.remove();
}
}
}

pub fn trigger_tree_changed(&self) {
Expand Down
11 changes: 9 additions & 2 deletions toml-config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use {
},
jay_config::{
input::acceleration::AccelProfile,
keyboard::{Keymap, ModifiedKeySym},
keyboard::{mods::Modifiers, Keymap, ModifiedKeySym},
logging::LogLevel,
status::MessageFormat,
theme::Color,
Expand Down Expand Up @@ -280,11 +280,18 @@ pub struct RepeatRate {
pub delay: i32,
}

#[derive(Debug, Clone)]
pub struct Shortcut {
pub mask: Modifiers,
pub keysym: ModifiedKeySym,
pub action: Action,
}

#[derive(Debug, Clone)]
pub struct Config {
pub keymap: Option<ConfigKeymap>,
pub repeat_rate: Option<RepeatRate>,
pub shortcuts: Vec<(ModifiedKeySym, Action)>,
pub shortcuts: Vec<Shortcut>,
pub on_graphics_initialized: Option<Action>,
pub on_idle: Option<Action>,
pub status: Option<Status>,
Expand Down
29 changes: 24 additions & 5 deletions toml-config/src/config/parsers/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use {
log_level::LogLevelParser,
output::OutputsParser,
repeat_rate::RepeatRateParser,
shortcuts::{ShortcutsParser, ShortcutsParserError},
shortcuts::{ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError},
status::StatusParser,
theme::ThemeParser,
},
Expand All @@ -30,6 +30,7 @@ use {
},
},
indexmap::IndexMap,
std::collections::HashSet,
thiserror::Error,
};

Expand Down Expand Up @@ -96,7 +97,7 @@ impl Parser for ConfigParser<'_> {
_,
idle_val,
),
(explicit_sync, repeat_rate_val),
(explicit_sync, repeat_rate_val, complex_shortcuts_val),
) = ext.extract((
(
opt(val("keymap")),
Expand All @@ -122,7 +123,11 @@ impl Parser for ConfigParser<'_> {
opt(val("$schema")),
opt(val("idle")),
),
(recover(opt(bol("explicit-sync"))), opt(val("repeat-rate"))),
(
recover(opt(bol("explicit-sync"))),
opt(val("repeat-rate")),
opt(val("complex-shortcuts")),
),
))?;
let mut keymap = None;
if let Some(value) = keymap_val {
Expand All @@ -136,10 +141,24 @@ impl Parser for ConfigParser<'_> {
}
}
}
let mut used_keys = HashSet::new();
let mut shortcuts = vec![];
if let Some(value) = shortcuts_val {
shortcuts = value
.parse(&mut ShortcutsParser(self.0))
value
.parse(&mut ShortcutsParser {
cx: self.0,
used_keys: &mut used_keys,
shortcuts: &mut shortcuts,
})
.map_spanned_err(ConfigParserError::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(ConfigParserError::ParseShortcuts)?;
}
if shortcuts.is_empty() {
Expand Down
Loading

0 comments on commit 6ca044a

Please sign in to comment.