diff --git a/build/enums.rs b/build/enums.rs index 54c05db1..4a4382b3 100644 --- a/build/enums.rs +++ b/build/enums.rs @@ -131,6 +131,16 @@ pub fn main() -> anyhow::Result<()> { libinput::LIBINPUT_CONFIG_DRAG_LOCK_STATE, "libinput_config_drag_lock_state", )?; + write_ty( + &mut f, + libinput::LIBINPUT_CONFIG_CLICK_METHOD, + "libinput_config_click_method", + )?; + write_ty( + &mut f, + libinput::LIBINPUT_CONFIG_MIDDLE_EMULATION_STATE, + "libinput_config_middle_emulation_state", + )?; let mut f = open("pango_tys.rs")?; write_ty(&mut f, pango::CAIRO_FORMATS, "cairo_format_t")?; diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 2adc6875..aed699ae 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -16,7 +16,7 @@ use { exec::Command, input::{ FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, acceleration::AccelProfile, - capability::Capability, + capability::Capability, clickmethod::ClickMethod, }, keyboard::{ Keymap, @@ -1174,6 +1174,14 @@ impl ConfigClient { self.send(&ClientMessage::SetDragLockEnabled { device, enabled }) } + pub fn set_input_click_method(&self, device: InputDevice, method: ClickMethod) { + self.send(&ClientMessage::SetClickMethod { device, method }) + } + + pub fn set_input_middle_button_emulation_enabled(&self, device: InputDevice, enabled: bool) { + self.send(&ClientMessage::SetMiddleButtonEmulationEnabled { device, enabled }) + } + pub fn device_name(&self, device: InputDevice) -> String { let res = self.send_with_response(&ClientMessage::GetDeviceName { device }); get_response!(res, String::new(), GetDeviceName { name }); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 681ee4b7..7fa1830a 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -5,7 +5,7 @@ use { client::{Client, ClientMatcher}, input::{ FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, acceleration::AccelProfile, - capability::Capability, + capability::Capability, clickmethod::ClickMethod, }, keyboard::{Keymap, mods::Modifiers, syms::KeySym}, logging::LogLevel, @@ -710,6 +710,14 @@ pub enum ClientMessage<'a> { seat: Seat, key: KeySym, }, + SetClickMethod { + device: InputDevice, + method: ClickMethod, + }, + SetMiddleButtonEmulationEnabled { + device: InputDevice, + enabled: bool, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index c80b6dcd..593bb079 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -2,12 +2,13 @@ pub mod acceleration; pub mod capability; +pub mod clickmethod; use { crate::{ _private::{DEFAULT_SEAT_NAME, ipc::WorkspaceSource}, Axis, Direction, ModifiedKeySym, Workspace, - input::{acceleration::AccelProfile, capability::Capability}, + input::{acceleration::AccelProfile, capability::Capability, clickmethod::ClickMethod}, keyboard::{Keymap, mods::Modifiers, syms::KeySym}, video::Connector, window::Window, @@ -133,6 +134,20 @@ impl InputDevice { get!().set_input_natural_scrolling_enabled(self, enabled); } + /// Sets the click method of the device. + /// + /// See + pub fn set_click_method(self, method: ClickMethod) { + get!().set_input_click_method(self, method); + } + + /// Sets whether middle button emulation is enabled for this device. + /// + /// See + pub fn set_middle_button_emulation_enabled(self, enabled: bool) { + get!().set_input_middle_button_emulation_enabled(self, enabled); + } + /// Returns the syspath of this device. /// /// E.g. `/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.4/usb5/5-1/5-1.1/5-1.1.3/5-1.1.3:1.0`. diff --git a/jay-config/src/input/clickmethod.rs b/jay-config/src/input/clickmethod.rs new file mode 100644 index 00000000..db62303b --- /dev/null +++ b/jay-config/src/input/clickmethod.rs @@ -0,0 +1,18 @@ +//! Constants determining the click method of a device. +//! +//! See the libinput documentation for details. + +use serde::{Deserialize, Serialize}; + +/// The click method of a device. +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub struct ClickMethod(pub u32); + +/// No click method handling +pub const CLICK_METHOD_NONE: ClickMethod = ClickMethod(0); + +/// Button area +pub const CLICK_METHOD_BUTTON_AREAS: ClickMethod = ClickMethod(1 << 0); + +/// Clickfinger +pub const CLICK_METHOD_CLICKFINGER: ClickMethod = ClickMethod(1 << 1); diff --git a/src/backend.rs b/src/backend.rs index 2f555402..6e063fbb 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -229,6 +229,14 @@ pub trait InputDevice { None } fn set_natural_scrolling_enabled(&self, enabled: bool); + fn click_method(&self) -> Option { + None + } + fn set_click_method(&self, method: InputDeviceClickMethod); + fn middle_button_emulation_enabled(&self) -> Option { + None + } + fn set_middle_button_emulation_enabled(&self, enabled: bool); fn tablet_info(&self) -> Option> { None } @@ -269,6 +277,13 @@ pub enum InputDeviceAccelProfile { Adaptive, } +#[derive(Debug, Copy, Clone)] +pub enum InputDeviceClickMethod { + None, + ButtonAreas, + Clickfinger, +} + pub enum BackendEvent { NewDrmDevice(Rc), NewConnector(Rc), diff --git a/src/backends/metal.rs b/src/backends/metal.rs index 2ab08d57..5f22c744 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -8,7 +8,8 @@ use { async_engine::SpawnedFuture, backend::{ Backend, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, - InputDeviceGroupId, InputDeviceId, InputEvent, KeyState, TransformMatrix, + InputDeviceClickMethod, InputDeviceGroupId, InputDeviceId, InputEvent, KeyState, + TransformMatrix, }, backends::metal::video::{ MetalDrmDeviceData, MetalLeaseData, MetalRenderContext, PendingDrmDevice, @@ -26,9 +27,10 @@ use { libinput::{ LibInput, LibInputAdapter, LibInputError, consts::{ - AccelProfile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, - LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, LIBINPUT_DEVICE_CAP_TABLET_PAD, - LIBINPUT_DEVICE_CAP_TABLET_TOOL, + AccelProfile, ConfigClickMethod, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS, + LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER, LIBINPUT_CONFIG_CLICK_METHOD_NONE, + LIBINPUT_DEVICE_CAP_TABLET_PAD, LIBINPUT_DEVICE_CAP_TABLET_TOOL, }, device::{LibInputDevice, RegisteredDevice}, }, @@ -400,6 +402,8 @@ struct InputDeviceProperties { drag_lock_enabled: Cell>, natural_scrolling_enabled: Cell>, calibration_matrix: Cell>, + click_method: Cell>, + middle_button_emulation_enabled: Cell>, } #[derive(Clone)] @@ -463,6 +467,12 @@ impl MetalInputDevice { if let Some(lh) = self.desired.calibration_matrix.get() { self.set_calibration_matrix(lh); } + if let Some(method) = self.desired.click_method.get() { + self.set_click_method_(method); + } + if let Some(enabled) = self.desired.middle_button_emulation_enabled.get() { + self.set_middle_button_emulation_enabled(enabled); + } self.fetch_effective(); } @@ -497,6 +507,14 @@ impl MetalInputDevice { .calibration_matrix .set(Some(device.get_calibration_matrix())); } + if device.has_click_methods() { + self.effective.click_method.set(Some(device.click_method())); + } + if device.middle_button_emulation_available() { + self.effective + .middle_button_emulation_enabled + .set(Some(device.middle_button_emulation_enabled())); + } } fn pre_pause(&self) { @@ -528,6 +546,18 @@ impl MetalInputDevice { } } } + + fn set_click_method_(&self, method: ConfigClickMethod) { + self.desired.click_method.set(Some(method)); + if let Some(dev) = self.inputdev.get() { + if dev.device().has_click_methods() { + dev.device().set_click_method(method); + self.effective + .click_method + .set(Some(dev.device().click_method())); + } + } + } } impl InputDevice for MetalInputDevice { @@ -559,6 +589,10 @@ impl InputDevice for MetalInputDevice { } } + fn left_handed(&self) -> Option { + self.effective.left_handed.get() + } + fn set_left_handed(&self, left_handed: bool) { self.desired.left_handed.set(Some(left_handed)); if let Some(dev) = self.inputdev.get() { @@ -571,6 +605,16 @@ impl InputDevice for MetalInputDevice { } } + fn accel_profile(&self) -> Option { + let p = self.effective.accel_profile.get()?; + let p = match p { + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT => InputDeviceAccelProfile::Flat, + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE => InputDeviceAccelProfile::Adaptive, + _ => return None, + }; + Some(p) + } + fn set_accel_profile(&self, profile: InputDeviceAccelProfile) { let profile = match profile { InputDeviceAccelProfile::Flat => LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, @@ -579,6 +623,10 @@ impl InputDevice for MetalInputDevice { self.set_accel_profile_(profile); } + fn accel_speed(&self) -> Option { + self.effective.accel_speed.get() + } + fn set_accel_speed(&self, speed: f64) { self.desired.accel_speed.set(Some(speed)); if let Some(dev) = self.inputdev.get() { @@ -591,10 +639,30 @@ impl InputDevice for MetalInputDevice { } } + fn transform_matrix(&self) -> Option { + self.transform_matrix.get() + } + fn set_transform_matrix(&self, matrix: TransformMatrix) { self.transform_matrix.set(Some(matrix)); } + fn calibration_matrix(&self) -> Option<[[f32; 3]; 2]> { + self.effective.calibration_matrix.get() + } + + fn set_calibration_matrix(&self, m: [[f32; 3]; 2]) { + self.desired.calibration_matrix.set(Some(m)); + if let Some(dev) = self.inputdev.get() { + if dev.device().has_calibration_matrix() { + dev.device().set_calibration_matrix(m); + self.effective + .calibration_matrix + .set(Some(dev.device().get_calibration_matrix())); + } + } + } + fn name(&self) -> Rc { self.name.get() } @@ -603,6 +671,10 @@ impl InputDevice for MetalInputDevice { Some(self.devnum) } + fn tap_enabled(&self) -> Option { + self.effective.tap_enabled.get() + } + fn set_tap_enabled(&self, enabled: bool) { self.desired.tap_enabled.set(Some(enabled)); if let Some(dev) = self.inputdev.get() { @@ -615,6 +687,10 @@ impl InputDevice for MetalInputDevice { } } + fn drag_enabled(&self) -> Option { + self.effective.drag_enabled.get() + } + fn set_drag_enabled(&self, enabled: bool) { self.desired.drag_enabled.set(Some(enabled)); if let Some(dev) = self.inputdev.get() { @@ -627,6 +703,10 @@ impl InputDevice for MetalInputDevice { } } + fn drag_lock_enabled(&self) -> Option { + self.effective.drag_lock_enabled.get() + } + fn set_drag_lock_enabled(&self, enabled: bool) { self.desired.drag_lock_enabled.set(Some(enabled)); if let Some(dev) = self.inputdev.get() { @@ -639,6 +719,10 @@ impl InputDevice for MetalInputDevice { } } + fn natural_scrolling_enabled(&self) -> Option { + self.effective.natural_scrolling_enabled.get() + } + fn set_natural_scrolling_enabled(&self, enabled: bool) { self.desired.natural_scrolling_enabled.set(Some(enabled)); if let Some(dev) = self.inputdev.get() { @@ -651,42 +735,42 @@ impl InputDevice for MetalInputDevice { } } - fn left_handed(&self) -> Option { - self.effective.left_handed.get() - } - - fn accel_profile(&self) -> Option { - let p = self.effective.accel_profile.get()?; + fn click_method(&self) -> Option { + let p = self.effective.click_method.get()?; let p = match p { - LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT => InputDeviceAccelProfile::Flat, - LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE => InputDeviceAccelProfile::Adaptive, + LIBINPUT_CONFIG_CLICK_METHOD_NONE => InputDeviceClickMethod::None, + LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS => InputDeviceClickMethod::ButtonAreas, + LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER => InputDeviceClickMethod::Clickfinger, _ => return None, }; Some(p) } - fn accel_speed(&self) -> Option { - self.effective.accel_speed.get() + fn set_click_method(&self, method: InputDeviceClickMethod) { + let method = match method { + InputDeviceClickMethod::None => LIBINPUT_CONFIG_CLICK_METHOD_NONE, + InputDeviceClickMethod::ButtonAreas => LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS, + InputDeviceClickMethod::Clickfinger => LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER, + }; + self.set_click_method_(method); } - fn transform_matrix(&self) -> Option { - self.transform_matrix.get() + fn middle_button_emulation_enabled(&self) -> Option { + self.effective.middle_button_emulation_enabled.get() } - fn tap_enabled(&self) -> Option { - self.effective.tap_enabled.get() - } - - fn drag_enabled(&self) -> Option { - self.effective.drag_enabled.get() - } - - fn drag_lock_enabled(&self) -> Option { - self.effective.drag_lock_enabled.get() - } - - fn natural_scrolling_enabled(&self) -> Option { - self.effective.natural_scrolling_enabled.get() + fn set_middle_button_emulation_enabled(&self, enabled: bool) { + self.desired + .middle_button_emulation_enabled + .set(Some(enabled)); + if let Some(dev) = self.inputdev.get() { + if dev.device().middle_button_emulation_available() { + dev.device().set_middle_button_emulation_enabled(enabled); + self.effective + .middle_button_emulation_enabled + .set(Some(dev.device().middle_button_emulation_enabled())); + } + } } fn tablet_info(&self) -> Option> { @@ -757,22 +841,6 @@ impl InputDevice for MetalInputDevice { groups, })) } - - fn calibration_matrix(&self) -> Option<[[f32; 3]; 2]> { - self.effective.calibration_matrix.get() - } - - fn set_calibration_matrix(&self, m: [[f32; 3]; 2]) { - self.desired.calibration_matrix.set(Some(m)); - if let Some(dev) = self.inputdev.get() { - if dev.device().has_calibration_matrix() { - dev.device().set_calibration_matrix(m); - self.effective - .calibration_matrix - .set(Some(dev.device().get_calibration_matrix())); - } - } - } } impl MetalInputDevice { diff --git a/src/backends/x.rs b/src/backends/x.rs index edc74761..c55c87b7 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -6,7 +6,8 @@ use { AXIS_120, AxisSource, Backend, BackendColorSpace, BackendDrmDevice, BackendEvent, BackendTransferFunction, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, DrmEvent, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, - InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis, TransformMatrix, + InputDeviceClickMethod, InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, + ScrollAxis, TransformMatrix, }, cmm::cmm_primaries::Primaries, fixed::Fixed, @@ -1219,6 +1220,14 @@ impl InputDevice for XSeatKeyboard { fn set_natural_scrolling_enabled(&self, enabled: bool) { let _ = enabled; } + + fn set_click_method(&self, method: InputDeviceClickMethod) { + let _ = method; + } + + fn set_middle_button_emulation_enabled(&self, enabled: bool) { + let _ = enabled; + } } impl InputDevice for XSeatMouse { @@ -1288,4 +1297,12 @@ impl InputDevice for XSeatMouse { fn set_natural_scrolling_enabled(&self, enabled: bool) { let _ = enabled; } + + fn set_click_method(&self, method: InputDeviceClickMethod) { + let _ = method; + } + + fn set_middle_button_emulation_enabled(&self, enabled: bool) { + let _ = enabled; + } } diff --git a/src/cli/input.rs b/src/cli/input.rs index c83684d0..970d9844 100644 --- a/src/cli/input.rs +++ b/src/cli/input.rs @@ -1,10 +1,12 @@ use { crate::{ - backend::{InputDeviceAccelProfile, InputDeviceCapability}, + backend::{InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod}, cli::GlobalArgs, clientmem::ClientMem, libinput::consts::{ - LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, + ConfigClickMethod, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS, + LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER, LIBINPUT_CONFIG_CLICK_METHOD_NONE, }, tools::tool_client::{Handle, ToolClient, with_tool_client}, utils::{errorfmt::ErrorFmt, string_ext::StringExt}, @@ -133,6 +135,10 @@ pub enum DeviceCommand { RemoveMapping, /// Set the calibration matrix. SetCalibrationMatrix(SetCalibrationMatrixArgs), + /// Set the click method. + SetClickMethod(SetClickMethodArgs), + /// Set whether the device uses middle button emulation. + SetMiddleButtonEmulation(SetMiddleButtonEmulationArgs), } #[derive(ValueEnum, Debug, Clone)] @@ -212,6 +218,26 @@ pub struct SetCalibrationMatrixArgs { pub m12: f32, } +#[derive(ValueEnum, Debug, Clone)] +pub enum ClickMethod { + None, + ButtonAreas, + Clickfinger, +} + +#[derive(Args, Debug, Clone)] +pub struct SetClickMethodArgs { + /// The method. + pub method: ClickMethod, +} + +#[derive(Args, Debug, Clone)] +pub struct SetMiddleButtonEmulationArgs { + /// Whether middle button emulation is enabled. + #[arg(action = clap::ArgAction::Set)] + pub middle_button_emulation: bool, +} + #[derive(Args, Debug, Clone)] pub struct MapToOutputArgs { /// The output to map to. @@ -286,6 +312,8 @@ struct InputDevice { pub transform_matrix: Option<[[f64; 2]; 2]>, pub output: Option, pub calibration_matrix: Option<[[f32; 3]; 2]>, + pub click_method: Option, + pub middle_button_emulation_enabled: Option, } #[derive(Clone, Debug, Default)] @@ -626,6 +654,34 @@ impl Input { m12: a.m12, }); } + DeviceCommand::SetClickMethod(a) => { + let method = match a.method { + ClickMethod::None => LIBINPUT_CONFIG_CLICK_METHOD_NONE.0, + ClickMethod::ButtonAreas => LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS.0, + ClickMethod::Clickfinger => LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER.0, + }; + self.handle_error(input, |e| { + eprintln!("Could not set the click method: {}", e); + }); + tc.send(jay_input::SetClickMethod { + self_id: input, + id: args.device, + method, + }); + } + DeviceCommand::SetMiddleButtonEmulation(a) => { + self.handle_error(input, |e| { + eprintln!( + "Could not modify the middle-button-emulation setting: {}", + e + ); + }); + tc.send(jay_input::SetMiddleButtonEmulation { + self_id: input, + id: args.device, + enabled: a.middle_button_emulation as _, + }); + } } tc.round_trip().await; } @@ -762,6 +818,17 @@ impl Input { if let Some(v) = &device.calibration_matrix { println!("{prefix} calibration matrix: {:?}", v); } + if let Some(v) = &device.click_method { + let name = match v { + InputDeviceClickMethod::None => "none", + InputDeviceClickMethod::ButtonAreas => "button-areas", + InputDeviceClickMethod::Clickfinger => "clickfinger", + }; + println!("{prefix} click method: {}", name); + } + if let Some(v) = &device.middle_button_emulation_enabled { + println!("{prefix} middle button emulation: {}", v); + } } async fn get(self: &Rc, input: JayInputId) -> Data { @@ -827,6 +894,8 @@ impl Input { transform_matrix: uapi::pod_read(msg.transform_matrix).ok(), output: None, calibration_matrix: None, + click_method: None, + middle_button_emulation_enabled: None, }); }); jay_input::InputDeviceOutput::handle(tc, input, data.clone(), |data, msg| { @@ -842,6 +911,29 @@ impl Input { Some([[msg.m00, msg.m01, msg.m02], [msg.m10, msg.m11, msg.m12]]); } }); + jay_input::ClickMethod::handle(tc, input, data.clone(), |data, msg| { + let click_method = match ConfigClickMethod(msg.click_method) { + LIBINPUT_CONFIG_CLICK_METHOD_NONE => Some(InputDeviceClickMethod::None), + LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS => { + Some(InputDeviceClickMethod::ButtonAreas) + } + LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER => { + Some(InputDeviceClickMethod::Clickfinger) + } + _ => None, + }; + let mut data = data.borrow_mut(); + if let Some(last) = data.input_device.last_mut() { + last.click_method = click_method; + } + }); + jay_input::MiddleButtonEmulation::handle(tc, input, data.clone(), |data, msg| { + let mut data = data.borrow_mut(); + if let Some(last) = data.input_device.last_mut() { + last.middle_button_emulation_enabled = + Some(msg.middle_button_emulation_enabled != 0); + } + }); tc.round_trip().await; let x = data.borrow_mut().clone(); x diff --git a/src/config/handler.rs b/src/config/handler.rs index 069cf35e..fba4db9e 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -3,7 +3,7 @@ use { async_engine::SpawnedFuture, backend::{ self, BackendColorSpace, BackendTransferFunction, ConnectorId, DrmDeviceId, - InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId, + InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, }, client::{Client, ClientId}, cmm::cmm_transfer_function::TransferFunction, @@ -56,6 +56,9 @@ use { CAP_GESTURE, CAP_KEYBOARD, CAP_POINTER, CAP_SWITCH, CAP_TABLET_PAD, CAP_TABLET_TOOL, CAP_TOUCH, Capability, }, + clickmethod::{ + CLICK_METHOD_BUTTON_AREAS, CLICK_METHOD_CLICKFINGER, CLICK_METHOD_NONE, ClickMethod, + }, }, keyboard::{Keymap, mods::Modifiers, syms::KeySym}, logging::LogLevel, @@ -828,6 +831,32 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_click_method( + &self, + device: InputDevice, + click_method: ClickMethod, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + let method = match click_method { + CLICK_METHOD_NONE => InputDeviceClickMethod::None, + CLICK_METHOD_BUTTON_AREAS => InputDeviceClickMethod::ButtonAreas, + CLICK_METHOD_CLICKFINGER => InputDeviceClickMethod::Clickfinger, + _ => return Err(CphError::UnknownClickMethod(click_method)), + }; + dev.device.set_click_method(method); + Ok(()) + } + + fn handle_set_middle_button_emulation_enabled( + &self, + device: InputDevice, + enabled: bool, + ) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + dev.device.set_middle_button_emulation_enabled(enabled); + Ok(()) + } + fn handle_set_ei_socket_enabled(&self, enabled: bool) { self.state.enable_ei_acceptor.set(enabled); self.state.update_ei_acceptor(); @@ -2916,6 +2945,12 @@ impl ConfigProxyHandler { ClientMessage::SetPointerRevertKey { seat, key } => self .handle_set_pointer_revert_key(seat, key) .wrn("set_pointer_revert_key")?, + ClientMessage::SetClickMethod { device, method } => self + .handle_set_click_method(device, method) + .wrn("set_click_method")?, + ClientMessage::SetMiddleButtonEmulationEnabled { device, enabled } => self + .handle_set_middle_button_emulation_enabled(device, enabled) + .wrn("set_middle_button_emulation_enabled")?, } Ok(()) } @@ -2945,6 +2980,8 @@ enum CphError { UnknownAccelProfile(AccelProfile), #[error("Queried unknown capability: {}", (.0).0)] UnknownCapability(Capability), + #[error("Tried to set an unknown click method: {}", (.0).0)] + UnknownClickMethod(ClickMethod), #[error("The sized {} is outside the valid range [{}, {}] for component {}", .0, .1.min(), .1.max(), .1.name())] InvalidSize(i32, ThemeSized), #[error("The ol' forker is not available")] diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 70e4e31d..e3b35b0f 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -79,7 +79,7 @@ impl Global for JayCompositorGlobal { } fn version(&self) -> u32 { - 18 + 19 } fn required_caps(&self) -> ClientCaps { diff --git a/src/ifs/jay_input.rs b/src/ifs/jay_input.rs index 8557df16..782a2478 100644 --- a/src/ifs/jay_input.rs +++ b/src/ifs/jay_input.rs @@ -1,14 +1,15 @@ use { crate::{ - backend::{self, InputDeviceAccelProfile, InputDeviceId}, + backend::{self, InputDeviceAccelProfile, InputDeviceClickMethod, InputDeviceId}, client::{Client, ClientError}, clientmem::{ClientMem, ClientMemError}, ifs::wl_seat::WlSeatGlobal, kbvm::{KbvmError, KbvmMap}, leaks::Tracker, libinput::consts::{ - AccelProfile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, - LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, + AccelProfile, ConfigClickMethod, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS, + LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER, LIBINPUT_CONFIG_CLICK_METHOD_NONE, }, object::{Object, Version}, state::{DeviceHandlerData, InputDeviceData}, @@ -28,6 +29,8 @@ pub struct JayInput { } const CALIBRATION_MATRIX_SINCE: Version = Version(4); +const CLICK_METHOD_SINCE: Version = Version(19); +const MIDDLE_BUTTON_EMULATION_SINCE: Version = Version(19); impl JayInput { pub fn new(id: JayInputId, client: &Rc, version: Version) -> Self { @@ -155,6 +158,30 @@ impl JayInput { }); } } + if self.version >= CLICK_METHOD_SINCE { + if let Some(click_method) = dev.click_method() { + self.client.event(ClickMethod { + self_id: self.id, + click_method: match click_method { + InputDeviceClickMethod::None => LIBINPUT_CONFIG_CLICK_METHOD_NONE.0, + InputDeviceClickMethod::Clickfinger => { + LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER.0 + } + InputDeviceClickMethod::ButtonAreas => { + LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS.0 + } + }, + }); + } + } + if self.version >= MIDDLE_BUTTON_EMULATION_SINCE { + if let Some(middle_button_emulation) = dev.middle_button_emulation_enabled() { + self.client.event(MiddleButtonEmulation { + self_id: self.id, + middle_button_emulation_enabled: middle_button_emulation as _, + }); + } + } } fn device(&self, id: u32) -> Result, JayInputError> { @@ -461,6 +488,33 @@ impl JayInputRequestHandler for JayInput { Ok(()) }) } + + fn set_click_method(&self, req: SetClickMethod, _slf: &Rc) -> Result<(), Self::Error> { + self.or_error(|| { + let dev = self.device(req.id)?; + let method = match ConfigClickMethod(req.method) { + LIBINPUT_CONFIG_CLICK_METHOD_NONE => InputDeviceClickMethod::None, + LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS => InputDeviceClickMethod::ButtonAreas, + LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER => InputDeviceClickMethod::Clickfinger, + _ => return Err(JayInputError::UnknownClickMethod(req.method)), + }; + dev.device.set_click_method(method); + Ok(()) + }) + } + + fn set_middle_button_emulation( + &self, + req: SetMiddleButtonEmulation, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.or_error(|| { + let dev = self.device(req.id)?; + dev.device + .set_middle_button_emulation_enabled(req.enabled != 0); + Ok(()) + }) + } } object_base! { @@ -482,6 +536,8 @@ pub enum JayInputError { DeviceDoesNotExist(u32), #[error("There is no acceleration profile with id {0}")] UnknownAccelerationProfile(i32), + #[error("There is no click method with id {0}")] + UnknownClickMethod(i32), #[error("Repeat rate must not be negative")] NegativeRepeatRate, #[error("Repeat delay must not be negative")] diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index a098e208..83bf4e62 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -5,8 +5,8 @@ use { backend::{ AxisSource, Backend, BackendColorSpace, BackendEvent, BackendTransferFunction, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, InputDevice, - InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId, InputEvent, KeyState, - Mode, MonitorInfo, ScrollAxis, TransformMatrix, + InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, + InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis, TransformMatrix, }, cmm::cmm_primaries::Primaries, compositor::TestFuture, @@ -543,6 +543,14 @@ trait TestInputDevice: InputDevice { fn set_natural_scrolling_enabled(&self, enabled: bool) { let _ = enabled; } + + fn set_click_method(&self, method: InputDeviceClickMethod) { + let _ = method; + } + + fn set_middle_button_emulation_enabled(&self, enabled: bool) { + let _ = enabled; + } } impl InputDevice for T { @@ -609,4 +617,12 @@ impl InputDevice for T { fn set_natural_scrolling_enabled(&self, enabled: bool) { ::set_natural_scrolling_enabled(self, enabled) } + + fn set_click_method(&self, method: InputDeviceClickMethod) { + ::set_click_method(self, method) + } + + fn set_middle_button_emulation_enabled(&self, enabled: bool) { + ::set_middle_button_emulation_enabled(self, enabled) + } } diff --git a/src/libinput/consts.rs b/src/libinput/consts.rs index fac6b120..616bf10e 100644 --- a/src/libinput/consts.rs +++ b/src/libinput/consts.rs @@ -188,3 +188,18 @@ cenum! { LIBINPUT_CONFIG_DRAG_LOCK_DISABLED = 0, LIBINPUT_CONFIG_DRAG_LOCK_ENABLED = 1, } + +cenum! { + ConfigClickMethod, LIBINPUT_CONFIG_CLICK_METHOD; + + LIBINPUT_CONFIG_CLICK_METHOD_NONE = 0, + LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS = 1 << 0, + LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER = 1 << 1, +} + +cenum! { + ConfigMiddleEmulationState, LIBINPUT_CONFIG_MIDDLE_EMULATION_STATE; + + LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED = 0, + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED = 1, +} diff --git a/src/libinput/device.rs b/src/libinput/device.rs index 68d89638..ead512c2 100644 --- a/src/libinput/device.rs +++ b/src/libinput/device.rs @@ -2,9 +2,11 @@ use { crate::libinput::{ LibInput, consts::{ - AccelProfile, ConfigDragLockState, ConfigDragState, ConfigTapState, DeviceCapability, + AccelProfile, ConfigClickMethod, ConfigDragLockState, ConfigDragState, + ConfigMiddleEmulationState, ConfigTapState, DeviceCapability, LIBINPUT_CONFIG_DRAG_DISABLED, LIBINPUT_CONFIG_DRAG_ENABLED, LIBINPUT_CONFIG_DRAG_LOCK_DISABLED, LIBINPUT_CONFIG_DRAG_LOCK_ENABLED, + LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED, LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED, LIBINPUT_CONFIG_TAP_DISABLED, LIBINPUT_CONFIG_TAP_ENABLED, }, sys::{ @@ -13,9 +15,14 @@ use { libinput_device_config_accel_set_profile, libinput_device_config_accel_set_speed, libinput_device_config_calibration_get_matrix, libinput_device_config_calibration_has_matrix, - libinput_device_config_calibration_set_matrix, libinput_device_config_left_handed_get, + libinput_device_config_calibration_set_matrix, libinput_device_config_click_get_method, + libinput_device_config_click_get_methods, libinput_device_config_click_set_method, + libinput_device_config_left_handed_get, libinput_device_config_left_handed_is_available, libinput_device_config_left_handed_set, + libinput_device_config_middle_emulation_get_enabled, + libinput_device_config_middle_emulation_is_available, + libinput_device_config_middle_emulation_set_enabled, libinput_device_config_scroll_get_natural_scroll_enabled, libinput_device_config_scroll_has_natural_scroll, libinput_device_config_scroll_set_natural_scroll_enabled, @@ -209,6 +216,46 @@ impl<'a> LibInputDevice<'a> { unsafe { libinput_device_config_scroll_has_natural_scroll(self.dev) != 0 } } + pub fn has_click_methods(&self) -> bool { + unsafe { libinput_device_config_click_get_methods(self.dev) != 0 } + } + + pub fn click_method(&self) -> ConfigClickMethod { + unsafe { ConfigClickMethod(libinput_device_config_click_get_method(self.dev)) } + } + + pub fn set_click_method(&self, method: ConfigClickMethod) { + unsafe { + libinput_device_config_click_set_method(self.dev, method.raw() as _); + } + } + + pub fn set_middle_button_emulation_enabled(&self, enabled: bool) { + let enabled = match enabled { + true => LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED, + false => LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED, + }; + unsafe { + libinput_device_config_middle_emulation_set_enabled(self.dev, enabled.raw() as _); + } + } + + pub fn middle_button_emulation_enabled(&self) -> bool { + let enabled = unsafe { + ConfigMiddleEmulationState(libinput_device_config_middle_emulation_get_enabled( + self.dev, + )) + }; + match enabled { + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED => true, + _ => false, + } + } + + pub fn middle_button_emulation_available(&self) -> bool { + unsafe { libinput_device_config_middle_emulation_is_available(self.dev) != 0 } + } + pub fn device_group(&self) -> LibInputDeviceGroup<'_> { LibInputDeviceGroup { group: unsafe { libinput_device_get_device_group(self.dev) }, diff --git a/src/libinput/sys.rs b/src/libinput/sys.rs index f53bb4ab..e849d0f2 100644 --- a/src/libinput/sys.rs +++ b/src/libinput/sys.rs @@ -114,6 +114,26 @@ unsafe extern "C" { device: *mut libinput_device, ) -> c::c_int; + pub fn libinput_device_config_click_get_methods(device: *mut libinput_device) -> u32; + pub fn libinput_device_config_click_get_method( + device: *mut libinput_device, + ) -> libinput_config_click_method; + pub fn libinput_device_config_click_set_method( + device: *mut libinput_device, + method: libinput_config_click_method, + ) -> libinput_config_status; + + pub fn libinput_device_config_middle_emulation_set_enabled( + device: *mut libinput_device, + enable: libinput_config_middle_emulation_state, + ) -> libinput_config_status; + pub fn libinput_device_config_middle_emulation_get_enabled( + device: *mut libinput_device, + ) -> libinput_config_middle_emulation_state; + pub fn libinput_device_config_middle_emulation_is_available( + device: *mut libinput_device, + ) -> c::c_int; + pub fn libinput_event_destroy(event: *mut libinput_event); pub fn libinput_event_get_type(event: *mut libinput_event) -> libinput_event_type; pub fn libinput_event_get_device(event: *mut libinput_event) -> *mut libinput_device; diff --git a/src/tools/tool_client.rs b/src/tools/tool_client.rs index 995d240e..1913a7e3 100644 --- a/src/tools/tool_client.rs +++ b/src/tools/tool_client.rs @@ -335,7 +335,7 @@ impl ToolClient { self_id: s.registry, name: s.jay_compositor.0, interface: JayCompositor.name(), - version: s.jay_compositor.1.min(18), + version: s.jay_compositor.1.min(19), id: id.into(), }); self.jay_compositor.set(Some(id)); diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 192cae7b..1a5537ea 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -22,7 +22,7 @@ use { ahash::AHashMap, jay_config::{ Axis, Direction, Workspace, - input::{SwitchEvent, acceleration::AccelProfile}, + input::{SwitchEvent, acceleration::AccelProfile, clickmethod::ClickMethod}, keyboard::{Keymap, ModifiedKeySym, mods::Modifiers, syms::KeySym}, logging::LogLevel, status::MessageFormat, @@ -362,6 +362,8 @@ pub struct Input { pub tap_drag_lock_enabled: Option, pub left_handed: Option, pub natural_scrolling: Option, + pub click_method: Option, + pub middle_button_emulation: Option, pub px_per_wheel_scroll: Option, pub transform_matrix: Option<[[f64; 2]; 2]>, pub keymap: Option, diff --git a/toml-config/src/config/parsers/input.rs b/toml-config/src/config/parsers/input.rs index c0949228..2b4115a3 100644 --- a/toml-config/src/config/parsers/input.rs +++ b/toml-config/src/config/parsers/input.rs @@ -22,6 +22,7 @@ use { jay_config::input::{ SwitchEvent, acceleration::{ACCEL_PROFILE_ADAPTIVE, ACCEL_PROFILE_FLAT}, + clickmethod::{CLICK_METHOD_BUTTON_AREAS, CLICK_METHOD_CLICKFINGER, CLICK_METHOD_NONE}, }, thiserror::Error, }; @@ -87,7 +88,9 @@ impl Parser for InputParser<'_> { output_val, remove_mapping, calibration_matrix, + click_method, ), + (middle_button_emulation,), ) = ext.extract(( ( opt(str("tag")), @@ -111,7 +114,9 @@ impl Parser for InputParser<'_> { opt(val("output")), recover(opt(bol("remove-mapping"))), recover(opt(val("calibration-matrix"))), + recover(opt(str("click-method"))), ), + (recover(opt(bol("middle-button-emulation"))),), ))?; let accel_profile = match accel_profile { None => None, @@ -124,6 +129,18 @@ impl Parser for InputParser<'_> { } }, }; + let click_method = match click_method { + None => None, + Some(p) => match p.value.to_ascii_lowercase().as_str() { + "none" => Some(CLICK_METHOD_NONE), + "button-areas" => Some(CLICK_METHOD_BUTTON_AREAS), + "clickfinger" => Some(CLICK_METHOD_CLICKFINGER), + v => { + log::warn!("Unknown click-method {v}: {}", self.cx.error3(p.span)); + None + } + }, + }; let transform_matrix = match transform_matrix { None => None, Some(matrix) => match matrix.parse(&mut TransformMatrixParser) { @@ -242,6 +259,8 @@ impl Parser for InputParser<'_> { tap_drag_lock_enabled: tap_drag_lock_enabled.despan(), left_handed: left_handed.despan(), natural_scrolling: natural_scrolling.despan(), + middle_button_emulation: middle_button_emulation.despan(), + click_method, px_per_wheel_scroll: px_per_wheel_scroll.despan(), transform_matrix, keymap, diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index af42da4f..ebac33c6 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -546,6 +546,12 @@ impl Input { if let Some(v) = self.calibration_matrix { c.set_calibration_matrix(v); } + if let Some(v) = self.click_method { + c.set_click_method(v); + } + if let Some(v) = self.middle_button_emulation { + c.set_middle_button_emulation_enabled(v); + } } } diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index d5a81922..00311f57 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -500,6 +500,15 @@ } ] }, + "ClickMethod": { + "type": "string", + "description": "The click method to apply to an input device.\n\nSee the libinput documentation for more details.\n", + "enum": [ + "none", + "button-areas", + "clickfinger" + ] + }, "ClientMatch": { "description": "Criteria for matching clients.\n\nIf no fields are set, all clients are matched. If multiple fields are set, all fields\nmust match the client.\n", "type": "object", @@ -1173,6 +1182,14 @@ "type": "boolean", "description": "Whether the device uses natural scrolling.\n\nSee the libinput documentation for more details.\n" }, + "middle-button-emulation": { + "type": "boolean", + "description": "Converts a simultaneous left and right button click into a middle button click.\n\nSee the libinput documentation for more details.\n" + }, + "click-method": { + "description": "Defines how button events are triggered on a clickable touchpad.\n\nSee the libinput documentation for more details.\n", + "$ref": "#/$defs/ClickMethod" + }, "px-per-wheel-scroll": { "type": "boolean", "description": "The number of pixels to scroll for each scroll wheel dedent.\n" diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index cd0cb221..132300c7 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -700,6 +700,32 @@ The string should have one of the following values: The brightness in cd/m^2. + +### `ClickMethod` + +The click method to apply to an input device. + +See the libinput documentation for more details. + +Values of this type should be strings. + +The string should have one of the following values: + +- `none`: + + No click method handling. + +- `button-areas`: + + Bottom area of the touchpad is divided into a left, middle and right button area. + +- `clickfinger`: + + Number of fingers on the touchpad decide the button type. + Clicking with 1, 2, 3 fingers triggers a left, right, or middle click, respectively. + + + ### `ClientMatch` @@ -2455,6 +2481,22 @@ The table has the following fields: The value of this field should be a boolean. +- `middle-button-emulation` (optional): + + Converts a simultaneous left and right button click into a middle button click. + + See the libinput documentation for more details. + + The value of this field should be a boolean. + +- `click-method` (optional): + + Defines how button events are triggered on a clickable touchpad. + + See the libinput documentation for more details. + + The value of this field should be a [ClickMethod](#types-ClickMethod). + - `px-per-wheel-scroll` (optional): The number of pixels to scroll for each scroll wheel dedent. diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index da6c07e6..796d4885 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -1372,6 +1372,20 @@ Input: description: | Whether the device uses natural scrolling. + See the libinput documentation for more details. + middle-button-emulation: + kind: boolean + required: false + description: | + Converts a simultaneous left and right button click into a middle button click. + + See the libinput documentation for more details. + click-method: + ref: ClickMethod + required: false + description: | + Defines how button events are triggered on a clickable touchpad. + See the libinput documentation for more details. px-per-wheel-scroll: kind: boolean @@ -1524,6 +1538,23 @@ AccelProfile: See the libinput documentation for more details. +ClickMethod: + kind: string + values: + - value: none + description: No click method handling. + - value: button-areas + description: Bottom area of the touchpad is divided into a left, middle and right button area. + - value: clickfinger + description: | + Number of fingers on the touchpad decide the button type. + Clicking with 1, 2, 3 fingers triggers a left, right, or middle click, respectively. + description: | + The click method to apply to an input device. + + See the libinput documentation for more details. + + LogLevel: kind: string description: A log level. diff --git a/wire/jay_input.txt b/wire/jay_input.txt index c5469936..f94b95ce 100644 --- a/wire/jay_input.txt +++ b/wire/jay_input.txt @@ -124,6 +124,16 @@ request set_calibration_matrix (since = 4) { m12: pod(f32), } +request set_click_method (since = 19) { + id: u32, + method: i32, +} + +request set_middle_button_emulation (since = 19) { + id: u32, + enabled: u32, +} + # events event seat { @@ -177,3 +187,11 @@ event calibration_matrix (since = 4) { m11: pod(f32), m12: pod(f32), } + +event click_method (since = 19) { + click_method: i32, +} + +event middle_button_emulation (since = 19) { + middle_button_emulation_enabled: u32, +}