diff --git a/src/config.rs b/src/config.rs index 8665233..7c945b7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -48,6 +48,7 @@ pub struct Config { input_text: InputText, list_items: ListItems, + mouse: Mouse, } impl Config { @@ -111,6 +112,13 @@ struct Icon { fallback_icon_path: Option, } +#[derive(Defaults, Deserialize)] +#[serde(default)] +struct Mouse { + launch_on_middle: bool, + wheel_scroll_multiplier: f64, +} + fn default_config_path() -> Result> { let file = xdg::BaseDirectories::with_prefix(crate::prog_name!()) .context("failed to get xdg dirs")? diff --git a/src/config/params.rs b/src/config/params.rs index 933490d..ee35ee1 100644 --- a/src/config/params.rs +++ b/src/config/params.rs @@ -10,7 +10,7 @@ use crate::desktop::IconConfig; use crate::draw::{BgParams, InputTextParams, ListParams}; use crate::font::{Font, FontBackend, InnerFont}; use crate::icon::Icon; -use crate::window::Params as WindowParams; +use crate::window::{Params as WindowParams, PointerParams}; macro_rules! select_conf { ($config:ident, $inner:ident, $field:ident) => { @@ -123,6 +123,15 @@ impl<'a> From<&'a Config> for Option { } } +impl<'a> From<&'a Config> for PointerParams { + fn from(config: &'a Config) -> Self { + Self { + launch_on_middle: config.mouse.launch_on_middle, + wheel_scroll_multiplier: config.mouse.wheel_scroll_multiplier, + } + } +} + fn default_font() -> Font { std::thread_local! { static DEFAULT_FONT: Lazy = Lazy::new(|| Rc::new(InnerFont::default())); diff --git a/src/window.rs b/src/window.rs index a9421e7..cab52a4 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,10 +1,10 @@ use anyhow::Context; use sctk::{ - delegate_compositor, delegate_keyboard, delegate_layer, delegate_output, delegate_registry, - delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, + delegate_compositor, delegate_keyboard, delegate_layer, delegate_output, delegate_pointer, + delegate_registry, delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, output::OutputState, reexports::client::{ - protocol::{wl_keyboard::WlKeyboard, wl_surface::WlSurface}, + protocol::{wl_keyboard::WlKeyboard, wl_pointer::WlPointer, wl_surface::WlSurface}, *, }, reexports::{ @@ -25,11 +25,13 @@ use sctk::{ }; use crate::state::State; +pub use pointer::Params as PointerParams; mod compositor; mod keyboard; mod layer_shell; mod output; +mod pointer; mod registry; mod seat; mod shm; @@ -60,8 +62,9 @@ pub struct Window { height: u32, scale: u16, - keyboard: Option, + input: InputSource, key_modifiers: sctk::seat::keyboard::Modifiers, + wheel_scroll_pending: f64, loop_handle: LoopHandle<'static, Window>, exit: bool, @@ -69,6 +72,11 @@ pub struct Window { error: Option, } +struct InputSource { + keyboard: Option, + pointer: Option, +} + enum RenderSurface { Xdg(xdg_win::Window), LayerShell(wlr_layer::LayerSurface), @@ -159,8 +167,12 @@ impl Window { width, height, scale, - keyboard: None, + input: InputSource { + keyboard: None, + pointer: None, + }, key_modifiers: Default::default(), + wheel_scroll_pending: 0.0, loop_handle: event_loop.handle(), exit: false, error: None, @@ -276,3 +288,4 @@ delegate_xdg_shell!(Window); delegate_layer!(Window); delegate_xdg_window!(Window); delegate_registry!(Window); +delegate_pointer!(Window); diff --git a/src/window/pointer.rs b/src/window/pointer.rs new file mode 100644 index 0000000..0eaea38 --- /dev/null +++ b/src/window/pointer.rs @@ -0,0 +1,107 @@ +use sctk::reexports::client::protocol::wl_pointer::AxisSource; +use sctk::reexports::client::{protocol, Connection, QueueHandle}; +use sctk::seat::keyboard::Modifiers; +use sctk::seat::pointer::{PointerEvent, PointerEventKind, PointerHandler, *}; + +use super::Window; + +// According to https://wayland.freedesktop.org/libinput/doc/1.19.0/wheel-api.html +// wheel typically has this angle per step. +// This actually should be configured and auto-detected (from udev probably?) but +// for now it should work for most cases and could be tuned via config. +const SCROLL_PER_STEP: f64 = 15.0; + +pub struct Params { + pub launch_on_middle: bool, + pub wheel_scroll_multiplier: f64, +} + +impl PointerHandler for Window { + fn pointer_frame( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _pointer: &protocol::wl_pointer::WlPointer, + events: &[PointerEvent], + ) { + let mut changed = false; + let config = self.config.param::(); + + for event in events { + // Ignore events for other surfaces + if event.surface != *self.surface { + continue; + } + + match event.kind { + // TODO: implement precise clicks on items + // PointerEventKind::Release { + // button: BTN_LEFT, .. + // } => .., + PointerEventKind::Release { + button: BTN_MIDDLE, .. + } if config.launch_on_middle => { + let with_fork = matches!(self.key_modifiers, Modifiers { ctrl: true, .. }); + if let Err(err) = self.state.eval_input(with_fork) { + self.error = Some(err); + } + } + PointerEventKind::Release { + button: BTN_RIGHT, .. + } => self.exit = true, + PointerEventKind::Release { + button: BTN_BACK, .. + } => self.state.prev_subitem(), + PointerEventKind::Release { + button: BTN_FORWARD, + .. + } => self.state.next_subitem(), + PointerEventKind::Axis { + vertical: + AxisScroll { + absolute, + discrete: _, + // XXX: handle this one? + stop: _, + }, + source: + Some(AxisSource::Wheel) + | Some(AxisSource::Finger) + | Some(AxisSource::Continuous), + time: _, + horizontal: _, + } => { + self.wheel_scroll_pending += absolute; + } + PointerEventKind::Enter { .. } + | PointerEventKind::Leave { .. } + | PointerEventKind::Motion { .. } + | PointerEventKind::Press { .. } + | PointerEventKind::Release { .. } + | PointerEventKind::Axis { .. } => continue, + } + changed = true; + } + + if changed { + let scroll_per_step = SCROLL_PER_STEP + * if config.wheel_scroll_multiplier > 0.0 { + config.wheel_scroll_multiplier + } else { + 1.0 + }; + let wheel_steps = (self.wheel_scroll_pending / scroll_per_step) as i32; + if wheel_steps != 0 { + self.wheel_scroll_pending -= f64::from(wheel_steps) * scroll_per_step; + } + let is_wheel_down = wheel_steps > 0; + for _ in 0..wheel_steps.abs() { + if is_wheel_down { + self.state.next_item(); + } else { + self.state.prev_item(); + } + } + } + } +} diff --git a/src/window/seat.rs b/src/window/seat.rs index 19d8f9a..8c8dfe8 100644 --- a/src/window/seat.rs +++ b/src/window/seat.rs @@ -18,7 +18,7 @@ impl SeatHandler for Window { capability: Capability, ) { match capability { - Capability::Keyboard if self.keyboard.is_none() => { + Capability::Keyboard if self.input.keyboard.is_none() => { let wl_keyboard = match self.seat_state.get_keyboard_with_repeat( qh, &seat, @@ -32,7 +32,12 @@ impl SeatHandler for Window { return; } }; - self.keyboard = Some(wl_keyboard); + self.input.keyboard = Some(wl_keyboard); + } + Capability::Pointer if self.input.pointer.is_none() => { + if let Ok(p) = self.seat_state.get_pointer(qh, &seat) { + self.input.pointer = Some(p); + } } _ => {} } @@ -46,7 +51,7 @@ impl SeatHandler for Window { capability: Capability, ) { if let Capability::Keyboard = capability { - if let Some(k) = self.keyboard.take() { + if let Some(k) = self.input.keyboard.take() { k.release(); } }