Skip to content
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
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Released binaries are not codesigned with a Developer ID, so after every upgrade
| Enter | Double click |
| `m` | Triple click |
| Delete | Right click |
| Insert | Middle click (macOS is not supported) |
| Escape | Close overlay |
| Backspace | Undo last step |
| Arrow keys | Scroll (up/down/left/right) |
Expand Down Expand Up @@ -115,7 +116,7 @@ An alternative grid mode that recursively subdivides. Instead of two-key combos

- Enter bisect mode: press `b` from normal mode, or launch with `--bisect`
- Each hint key splits the current region into a smaller grid (2x2 by default)
- Space / Enter / `m` / Delete act (click / double-click / triple-click / right-click) at the center of the current region and exit
- Space / Enter / `m` / Delete / Insert act (click / double-click / triple-click / right-click / middle-click) at the center of the current region and exit
- Backspace pops back up one level
- Subdivision stops automatically once a cell would fall below `min_cell_size` pixels — the region is highlighted and you can act or back out
- Press `n` to switch back to normal mode
Expand All @@ -127,7 +128,7 @@ An alternative mode for direct keyboard-driven cursor movement. Instead of selec
- Enter free mode: press `v` from normal or bisect mode, or launch with `--free` (starts at mouse position) or `--free-center` (starts at screen center)
- Move the cursor with `h` / `j` / `k` / `l` (left / down / up / right)
- Press `=` to increase speed and `-` to decrease it; the current speed is shown in the top-left corner
- Space / Enter / `m` / Delete act (click / double-click / triple-click / right-click) and exit
- Space / Enter / `m` / Delete / Insert act (click / double-click / triple-click / right-click / middle-click) and exit
- Backspace returns to the previous mode; Escape closes the overlay
- Key repeat is supported: hold a movement key to move continuously

Expand Down Expand Up @@ -183,6 +184,7 @@ triple_click = "m"
close = "escape"
undo = "backspace"
right_click = "delete"
middle_click = "insert"
scroll_up = "up"
scroll_down = "down"
scroll_left = "left"
Expand Down
5 changes: 5 additions & 0 deletions src/backend/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,11 @@ impl Backend for MacosBackend {
Ok(())
}

fn middle_click(&mut self, x: u32, y: u32) -> Result<()> {
unimplemented!();
Ok(())
}

fn drag_select(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) -> Result<()> {
self.hide_overlay();
self.focus_target_app();
Expand Down
4 changes: 4 additions & 0 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub enum KeyEvent {
DoubleClick,
TripleClick,
RightClick,
MiddleClick,
Close,
Undo,
MacroMenu,
Expand Down Expand Up @@ -50,6 +51,9 @@ pub trait Backend {
/// Tear down the overlay, right click at (x, y), then return.
fn right_click(&mut self, x: u32, y: u32) -> Result<()>;

/// Tear down the overlay, middle click at (x, y), then return.
fn middle_click(&mut self, x: u32, y: u32) -> Result<()>;

/// Tear down the overlay, drag from (x1,y1) to (x2,y2), then return.
fn drag_select(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) -> Result<()>;

Expand Down
27 changes: 27 additions & 0 deletions src/backend/wayland.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::config::{config, Key};

const BTN_LEFT: u32 = 0x110;
const BTN_RIGHT: u32 = 0x111;
const BTN_MIDDLE: u32 = 0x112;

const REPEAT_DELAY_MS: u64 = 300;
const REPEAT_RATE_HZ: u64 = 25;
Expand Down Expand Up @@ -291,6 +292,32 @@ impl Backend for WaylandBackend {
Ok(())
}

/// Destroy the overlay so the compositor removes it from the surface stack,
/// then re-send motion to trigger a focus update, then middle click.
fn middle_click(&mut self, x: u32, y: u32) -> Result<()> {
self.teardown_surface()?;

if let Some(vp) = &self.state.vp {
vp.motion_absolute(timestamp(), x, y, self.state.screen_w, self.state.screen_h);
vp.frame();
}
self.eq
.roundtrip(&mut self.state)
.context("roundtrip after motion")?;

if let Some(vp) = &self.state.vp {
let ts = timestamp();
vp.button(ts, BTN_MIDDLE, wl_pointer::ButtonState::Pressed);
vp.frame();
vp.button(ts, BTN_MIDDLE, wl_pointer::ButtonState::Released);
vp.frame();
}
self.eq
.roundtrip(&mut self.state)
.context("roundtrip after click")?;
Ok(())
}

fn scroll_up(&mut self) -> Result<()> {
self.scroll(Axis::VerticalScroll, -15.0, -1)
}
Expand Down
7 changes: 7 additions & 0 deletions src/backend/x11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use super::{Backend, KeyEvent};
use crate::config::{config, Key};

const BTN_LEFT: u8 = 1;
const BTN_MIDDLE: u8 = 2;
const BTN_RIGHT: u8 = 3;
const BTN_SCROLL_UP: u8 = 4;
const BTN_SCROLL_DOWN: u8 = 5;
Expand Down Expand Up @@ -254,6 +255,12 @@ impl Backend for X11Backend {
self.fake_button_click(BTN_RIGHT)
}

fn middle_click(&mut self, x: u32, y: u32) -> Result<()> {
self.teardown()?;
self.warp_and_sync(x, y)?;
self.fake_button_click(BTN_MIDDLE)
}

fn drag_select(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) -> Result<()> {
self.teardown()?;
self.warp_and_sync(x1, y1)?;
Expand Down
5 changes: 5 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ pub struct KeyBindings {
pub macro_menu: Key,
pub macro_record: Key,
pub right_click: Key,
pub middle_click: Key,
pub scroll_up: Key,
pub scroll_down: Key,
pub scroll_left: Key,
Expand All @@ -239,6 +240,7 @@ impl Default for KeyBindings {
macro_menu: Key::Tab,
macro_record: Key::Char('`'),
right_click: Key::Delete,
middle_click: Key::Insert,
scroll_up: Key::Up,
scroll_down: Key::Down,
scroll_left: Key::Left,
Expand Down Expand Up @@ -284,6 +286,9 @@ impl KeyBindings {
if key == self.right_click {
return Some(KeyEvent::RightClick);
}
if key == self.middle_click {
return Some(KeyEvent::MiddleClick);
}
if key == self.scroll_up {
return Some(KeyEvent::ScrollUp);
}
Expand Down
1 change: 1 addition & 0 deletions src/macro_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub enum MacroActionKind {
DoubleClick(String),
TripleClick(String),
RightClick(String),
MiddleClick(String),
Drag(String, String),
}

Expand Down
4 changes: 4 additions & 0 deletions src/mode/bisect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ pub(super) fn handle_key<B: Backend>(
backend.right_click(cx, cy)?;
Ok(ModeTransition::Exit)
}
KeyEvent::MiddleClick => {
backend.middle_click(cx, cy)?;
Ok(ModeTransition::Exit)
}
KeyEvent::ScrollUp => {
backend.scroll_up()?;
Ok(ModeTransition::Redraw)
Expand Down
4 changes: 4 additions & 0 deletions src/mode/free.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ pub(super) fn handle_key<B: Backend>(
backend.right_click(x, y)?;
Ok(ModeTransition::Exit)
}
KeyEvent::MiddleClick => {
backend.middle_click(x, y)?;
Ok(ModeTransition::Exit)
}
KeyEvent::ScrollUp => {
backend.scroll_up()?;
Ok(ModeTransition::Redraw)
Expand Down
5 changes: 5 additions & 0 deletions src/mode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,11 @@ pub(super) fn replay_macro(
backend.right_click(x, y)?;
}
}
MacroActionKind::MiddleClick(keys) => {
if let Some((x, y)) = keys_to_pos(keys, w, h) {
backend.middle_click(x, y)?;
}
}
MacroActionKind::Drag(start_keys, end_keys) => {
if let (Some((x1, y1)), Some((x2, y2))) =
(keys_to_pos(start_keys, w, h), keys_to_pos(end_keys, w, h))
Expand Down
6 changes: 6 additions & 0 deletions src/mode/normal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ pub(super) fn handle_key<B: Backend>(
}
Ok(ModeTransition::Exit)
}
KeyEvent::MiddleClick if drag_origin.is_none() => {
if let Some((x, y)) = target {
backend.middle_click(x, y)?;
}
Ok(ModeTransition::Exit)
}
KeyEvent::Char('/')
if matches!(
input_state,
Expand Down
9 changes: 8 additions & 1 deletion src/mode/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ pub(super) fn handle_key<B: Backend>(
InputState::Ready { .. } => Ok(ModeTransition::Stay),
}
}
KeyEvent::Click | KeyEvent::DoubleClick | KeyEvent::TripleClick | KeyEvent::RightClick
KeyEvent::Click | KeyEvent::DoubleClick | KeyEvent::TripleClick | KeyEvent::RightClick | KeyEvent::MiddleClick
if target.is_some() && drag_origin.is_none() =>
{
let (x, y) = target.unwrap();
Expand Down Expand Up @@ -168,6 +168,13 @@ pub(super) fn handle_key<B: Backend>(
wait_ms,
));
}
KeyEvent::MiddleClick => {
backend.middle_click(x, y)?;
new_actions.push(MacroAction::new(
MacroActionKind::MiddleClick(current_keys),
wait_ms,
));
}
_ => {}
}
backend.reopen()?;
Expand Down
Loading