Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Mouse{Left,Right,Middle,Back,Forward} binds #865

Merged
merged 1 commit into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion niri-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,11 @@ pub struct Key {
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum Trigger {
Keysym(Keysym),
MouseLeft,
MouseRight,
MouseMiddle,
MouseBack,
MouseForward,
WheelScrollDown,
WheelScrollUp,
WheelScrollLeft,
Expand Down Expand Up @@ -2924,7 +2929,17 @@ impl FromStr for Key {
}
}

let trigger = if key.eq_ignore_ascii_case("WheelScrollDown") {
let trigger = if key.eq_ignore_ascii_case("MouseLeft") {
Trigger::MouseLeft
} else if key.eq_ignore_ascii_case("MouseRight") {
Trigger::MouseRight
} else if key.eq_ignore_ascii_case("MouseMiddle") {
Trigger::MouseMiddle
} else if key.eq_ignore_ascii_case("MouseBack") {
Trigger::MouseBack
} else if key.eq_ignore_ascii_case("MouseForward") {
Trigger::MouseForward
} else if key.eq_ignore_ascii_case("WheelScrollDown") {
Trigger::WheelScrollDown
} else if key.eq_ignore_ascii_case("WheelScrollUp") {
Trigger::WheelScrollUp
Expand Down
67 changes: 55 additions & 12 deletions src/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1656,11 +1656,43 @@ impl State {

let serial = SERIAL_COUNTER.next_serial();

let button = event.button_code();
let button = event.button();

let button_code = event.button_code();

let button_state = event.state();

// Ignore release events for mouse clicks that triggered a bind.
if self.niri.suppressed_buttons.remove(&button_code) {
return;
}

if ButtonState::Pressed == button_state {
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
let modifiers = modifiers_from_state(mods);

if self.niri.mods_with_mouse_binds.contains(&modifiers) {
let comp_mod = self.backend.mod_key();

if let Some(bind) = match button {
Some(MouseButton::Left) => Some(Trigger::MouseLeft),
Some(MouseButton::Right) => Some(Trigger::MouseRight),
Some(MouseButton::Middle) => Some(Trigger::MouseMiddle),
Some(MouseButton::Back) => Some(Trigger::MouseBack),
Some(MouseButton::Forward) => Some(Trigger::MouseForward),
_ => None,
}
.and_then(|trigger| {
let config = self.niri.config.borrow();
let bindings = &config.binds;
find_configured_bind(bindings, comp_mod, trigger, mods)
}) {
self.niri.suppressed_buttons.insert(button_code);
self.handle_bind(bind.clone());
return;
bbb651 marked this conversation as resolved.
Show resolved Hide resolved
};
}

// We received an event for the regular pointer, so show it now.
self.niri.pointer_hidden = false;
self.niri.tablet_cursor_location = None;
Expand All @@ -1669,8 +1701,7 @@ impl State {
let window = mapped.window.clone();

// Check if we need to start an interactive move.
if event.button() == Some(MouseButton::Left) && !pointer.is_grabbed() {
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
if button == Some(MouseButton::Left) && !pointer.is_grabbed() {
let mod_down = match self.backend.mod_key() {
CompositorMod::Super => mods.logo,
CompositorMod::Alt => mods.alt,
Expand All @@ -1689,7 +1720,7 @@ impl State {
) {
let start_data = PointerGrabStartData {
focus: None,
button: event.button_code(),
button: button_code,
location,
};
let grab = MoveGrab::new(start_data, window.clone());
Expand All @@ -1701,8 +1732,7 @@ impl State {
}
}
// Check if we need to start an interactive resize.
else if event.button() == Some(MouseButton::Right) && !pointer.is_grabbed() {
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
else if button == Some(MouseButton::Right) && !pointer.is_grabbed() {
let mod_down = match self.backend.mod_key() {
CompositorMod::Super => mods.logo,
CompositorMod::Alt => mods.alt,
Expand Down Expand Up @@ -1762,7 +1792,7 @@ impl State {
{
let start_data = PointerGrabStartData {
focus: None,
button: event.button_code(),
button: button_code,
location,
};
let grab = ResizeGrab::new(start_data, window.clone());
Expand All @@ -1786,8 +1816,7 @@ impl State {
self.niri.queue_redraw_all();
}

if event.button() == Some(MouseButton::Middle) && !pointer.is_grabbed() {
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
if button == Some(MouseButton::Middle) && !pointer.is_grabbed() {
let mod_down = match self.backend.mod_key() {
CompositorMod::Super => mods.logo,
CompositorMod::Alt => mods.alt,
Expand All @@ -1797,7 +1826,7 @@ impl State {
let location = pointer.current_location();
let start_data = PointerGrabStartData {
focus: None,
button: event.button_code(),
button: button_code,
location,
};
let grab = SpatialMovementGrab::new(start_data, output);
Expand All @@ -1817,7 +1846,7 @@ impl State {
self.niri.focus_layer_surface_if_on_demand(layer_under);
}

if let Some(button) = event.button() {
if let Some(button) = button {
let pos = pointer.current_location();
if let Some((output, _)) = self.niri.output_under(pos) {
let output = output.clone();
Expand Down Expand Up @@ -1845,7 +1874,7 @@ impl State {
pointer.button(
self,
&ButtonEvent {
button,
button: button_code,
state: button_state,
serial,
time: event.time_msec(),
Expand Down Expand Up @@ -3110,6 +3139,20 @@ pub fn mods_with_binds(
rv
}

pub fn mods_with_mouse_binds(comp_mod: CompositorMod, binds: &Binds) -> HashSet<Modifiers> {
mods_with_binds(
comp_mod,
binds,
&[
Trigger::MouseLeft,
Trigger::MouseRight,
Trigger::MouseMiddle,
Trigger::MouseBack,
Trigger::MouseForward,
],
)
}

pub fn mods_with_wheel_binds(comp_mod: CompositorMod, binds: &Binds) -> HashSet<Modifiers> {
mods_with_binds(
comp_mod,
Expand Down
11 changes: 10 additions & 1 deletion src/niri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ use crate::frame_clock::FrameClock;
use crate::handlers::{configure_lock_surface, XDG_ACTIVATION_TOKEN_TIMEOUT};
use crate::input::scroll_tracker::ScrollTracker;
use crate::input::{
apply_libinput_settings, mods_with_finger_scroll_binds, mods_with_wheel_binds, TabletData,
apply_libinput_settings, mods_with_finger_scroll_binds, mods_with_mouse_binds,
mods_with_wheel_binds, TabletData,
};
use crate::ipc::server::IpcServer;
use crate::layer::mapped::LayerSurfaceRenderElement;
Expand Down Expand Up @@ -277,6 +278,8 @@ pub struct Niri {
pub seat: Seat<State>,
/// Scancodes of the keys to suppress.
pub suppressed_keys: HashSet<Keycode>,
/// Button codes of the mouse buttons to suppress.
pub suppressed_buttons: HashSet<u32>,
pub bind_cooldown_timers: HashMap<Key, RegistrationToken>,
pub bind_repeat_timer: Option<RegistrationToken>,
pub keyboard_focus: KeyboardFocus,
Expand Down Expand Up @@ -313,6 +316,7 @@ pub struct Niri {
pub gesture_swipe_3f_cumulative: Option<(f64, f64)>,
pub vertical_wheel_tracker: ScrollTracker,
pub horizontal_wheel_tracker: ScrollTracker,
pub mods_with_mouse_binds: HashSet<Modifiers>,
pub mods_with_wheel_binds: HashSet<Modifiers>,
pub vertical_finger_scroll_tracker: ScrollTracker,
pub horizontal_finger_scroll_tracker: ScrollTracker,
Expand Down Expand Up @@ -1142,6 +1146,8 @@ impl State {

if config.binds != old_config.binds {
self.niri.hotkey_overlay.on_hotkey_config_updated();
self.niri.mods_with_mouse_binds =
mods_with_mouse_binds(self.backend.mod_key(), &config.binds);
self.niri.mods_with_wheel_binds =
mods_with_wheel_binds(self.backend.mod_key(), &config.binds);
self.niri.mods_with_finger_scroll_binds =
Expand Down Expand Up @@ -1846,6 +1852,7 @@ impl Niri {
let cursor_manager =
CursorManager::new(&config_.cursor.xcursor_theme, config_.cursor.xcursor_size);

let mods_with_mouse_binds = mods_with_mouse_binds(backend.mod_key(), &config_.binds);
let mods_with_wheel_binds = mods_with_wheel_binds(backend.mod_key(), &config_.binds);
let mods_with_finger_scroll_binds =
mods_with_finger_scroll_binds(backend.mod_key(), &config_.binds);
Expand Down Expand Up @@ -1998,6 +2005,7 @@ impl Niri {
popups: PopupManager::default(),
popup_grab: None,
suppressed_keys: HashSet::new(),
suppressed_buttons: HashSet::new(),
bind_cooldown_timers: HashMap::new(),
bind_repeat_timer: Option::default(),
presentation_state,
Expand Down Expand Up @@ -2025,6 +2033,7 @@ impl Niri {
gesture_swipe_3f_cumulative: None,
vertical_wheel_tracker: ScrollTracker::new(120),
horizontal_wheel_tracker: ScrollTracker::new(120),
mods_with_mouse_binds,
bbb651 marked this conversation as resolved.
Show resolved Hide resolved
mods_with_wheel_binds,

// 10 is copied from Clutter: DISCRETE_SCROLL_STEP.
Expand Down
5 changes: 5 additions & 0 deletions src/ui/hotkey_overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,11 @@ fn key_name(comp_mod: CompositorMod, key: &Key) -> String {

let pretty = match key.trigger {
Trigger::Keysym(keysym) => prettify_keysym_name(&keysym_get_name(keysym)),
Trigger::MouseLeft => String::from("Mouse Left"),
Trigger::MouseRight => String::from("Mouse Right"),
Trigger::MouseMiddle => String::from("Mouse Middle"),
Trigger::MouseBack => String::from("Mouse Back"),
Trigger::MouseForward => String::from("Mouse Forward"),
Trigger::WheelScrollDown => String::from("Wheel Scroll Down"),
Trigger::WheelScrollUp => String::from("Wheel Scroll Up"),
Trigger::WheelScrollLeft => String::from("Wheel Scroll Left"),
Expand Down
20 changes: 20 additions & 0 deletions wiki/Configuration:-Key-Bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,26 @@ binds {
Both mouse wheel and touchpad scroll binds will prevent applications from receiving any scroll events when their modifiers are held down.
For example, if you have a `Mod+WheelScrollDown` bind, then while holding `Mod`, all mouse wheel scrolling will be consumed by niri.

### Mouse Click Bindings

<sup>Since: next release</sup>

You can bind mouse clicks using the following syntax.

```kdl
binds {
Mod+MouseLeft { close-window; }
Mod+MouseRight { close-window; }
Mod+MouseMiddle { close-window; }
Mod+MouseForward { close-window; }
Mod+MouseBack { close-window; }
}
```

Mouse clicks operate on the window that was focused at the time of the click, not the window you're clicking.

Note that binding `Mod+MouseLeft` or `Mod+MouseRight` will override the corresponding gesture (moving or resizing the window).

### Actions

Every action that you can bind is also available for programmatic invocation via `niri msg action`.
Expand Down