1
0
Fork 0
forked from wry/wry

Merge pull request #469 from kotarac/master

input: add click method and middle button emulation
This commit is contained in:
mahkoh 2025-05-14 14:20:19 +02:00 committed by GitHub
commit 25848be92f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 640 additions and 63 deletions

View file

@ -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")?;

View file

@ -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 });

View file

@ -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)]

View file

@ -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 <https://wayland.freedesktop.org/libinput/doc/latest/configuration.html#click-method>
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 <https://wayland.freedesktop.org/libinput/doc/latest/configuration.html#middle-button-emulation>
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`.

View file

@ -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);

View file

@ -229,6 +229,14 @@ pub trait InputDevice {
None
}
fn set_natural_scrolling_enabled(&self, enabled: bool);
fn click_method(&self) -> Option<InputDeviceClickMethod> {
None
}
fn set_click_method(&self, method: InputDeviceClickMethod);
fn middle_button_emulation_enabled(&self) -> Option<bool> {
None
}
fn set_middle_button_emulation_enabled(&self, enabled: bool);
fn tablet_info(&self) -> Option<Box<TabletInit>> {
None
}
@ -269,6 +277,13 @@ pub enum InputDeviceAccelProfile {
Adaptive,
}
#[derive(Debug, Copy, Clone)]
pub enum InputDeviceClickMethod {
None,
ButtonAreas,
Clickfinger,
}
pub enum BackendEvent {
NewDrmDevice(Rc<dyn BackendDrmDevice>),
NewConnector(Rc<dyn Connector>),

View file

@ -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<Option<bool>>,
natural_scrolling_enabled: Cell<Option<bool>>,
calibration_matrix: Cell<Option<[[f32; 3]; 2]>>,
click_method: Cell<Option<ConfigClickMethod>>,
middle_button_emulation_enabled: Cell<Option<bool>>,
}
#[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<bool> {
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<InputDeviceAccelProfile> {
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<f64> {
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<TransformMatrix> {
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<String> {
self.name.get()
}
@ -603,6 +671,10 @@ impl InputDevice for MetalInputDevice {
Some(self.devnum)
}
fn tap_enabled(&self) -> Option<bool> {
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<bool> {
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<bool> {
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<bool> {
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<bool> {
self.effective.left_handed.get()
}
fn accel_profile(&self) -> Option<InputDeviceAccelProfile> {
let p = self.effective.accel_profile.get()?;
fn click_method(&self) -> Option<InputDeviceClickMethod> {
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<f64> {
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<TransformMatrix> {
self.transform_matrix.get()
fn middle_button_emulation_enabled(&self) -> Option<bool> {
self.effective.middle_button_emulation_enabled.get()
}
fn tap_enabled(&self) -> Option<bool> {
self.effective.tap_enabled.get()
}
fn drag_enabled(&self) -> Option<bool> {
self.effective.drag_enabled.get()
}
fn drag_lock_enabled(&self) -> Option<bool> {
self.effective.drag_lock_enabled.get()
}
fn natural_scrolling_enabled(&self) -> Option<bool> {
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<Box<TabletInit>> {
@ -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 {

View file

@ -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;
}
}

View file

@ -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<String>,
pub calibration_matrix: Option<[[f32; 3]; 2]>,
pub click_method: Option<InputDeviceClickMethod>,
pub middle_button_emulation_enabled: Option<bool>,
}
#[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<Self>, 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

View file

@ -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")]

View file

@ -79,7 +79,7 @@ impl Global for JayCompositorGlobal {
}
fn version(&self) -> u32 {
18
19
}
fn required_caps(&self) -> ClientCaps {

View file

@ -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<Client>, 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<Rc<DeviceHandlerData>, JayInputError> {
@ -461,6 +488,33 @@ impl JayInputRequestHandler for JayInput {
Ok(())
})
}
fn set_click_method(&self, req: SetClickMethod, _slf: &Rc<Self>) -> 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<Self>,
) -> 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")]

View file

@ -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<T: TestInputDevice> InputDevice for T {
@ -609,4 +617,12 @@ impl<T: TestInputDevice> InputDevice for T {
fn set_natural_scrolling_enabled(&self, enabled: bool) {
<Self as TestInputDevice>::set_natural_scrolling_enabled(self, enabled)
}
fn set_click_method(&self, method: InputDeviceClickMethod) {
<Self as TestInputDevice>::set_click_method(self, method)
}
fn set_middle_button_emulation_enabled(&self, enabled: bool) {
<Self as TestInputDevice>::set_middle_button_emulation_enabled(self, enabled)
}
}

View file

@ -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,
}

View file

@ -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) },

View file

@ -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;

View file

@ -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));

View file

@ -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<bool>,
pub left_handed: Option<bool>,
pub natural_scrolling: Option<bool>,
pub click_method: Option<ClickMethod>,
pub middle_button_emulation: Option<bool>,
pub px_per_wheel_scroll: Option<f64>,
pub transform_matrix: Option<[[f64; 2]; 2]>,
pub keymap: Option<ConfigKeymap>,

View file

@ -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,

View file

@ -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);
}
}
}

View file

@ -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"

View file

@ -700,6 +700,32 @@ The string should have one of the following values:
The brightness in cd/m^2.
<a name="types-ClickMethod"></a>
### `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.
<a name="types-ClientMatch"></a>
### `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.

View file

@ -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.

View file

@ -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,
}