// Allow unnecessary casts since ffi types may differ by C ABI
// TODO Error type instead of `Result<_, ()>`
// TODO Better way to handle `SendEventsMode::ENABLED` being 0?
#![allow(
    clippy::bad_bit_mask,
    clippy::result_unit_err,
    clippy::unnecessary_cast
)]

#[cfg(feature = "libinput_1_23")]
use crate::accel_config::AccelConfig;
use crate::{
    event::{switch::Switch, tablet_pad::TabletPadModeGroup},
    ffi, AsRaw, FromRaw, Libinput, Seat,
};
use bitflags::bitflags;
use std::{
    borrow::Cow,
    ffi::{CStr, CString},
};
#[cfg(feature = "udev")]
use udev::{
    ffi::{udev as udev_context, udev_device, udev_device_get_udev, udev_ref},
    Device as UdevDevice, FromRawWithContext as UdevFromRawWithContext,
};

/// Capabilities on a device.
///
/// A device may have one or more capabilities at a time, capabilities
/// remain static for the lifetime of the device.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DeviceCapability {
    /// Keyboard capability
    Keyboard,
    /// Pointer capability
    Pointer,
    /// Touch capability
    Touch,
    /// TabletTool capability
    TabletTool,
    /// TabletPad capability
    TabletPad,
    /// Gesture capability
    Gesture,
    /// Switch capability
    Switch,
}

/// Pointer Acceleration Profile
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum AccelProfile {
    /// A flat acceleration profile.
    ///
    /// Pointer motion is accelerated by a constant (device-specific)
    /// factor, depending on the current speed.
    Flat,
    /// An adaptive acceleration profile.
    ///
    /// Pointer acceleration depends on the input speed. This is the
    /// default profile for most devices.
    Adaptive,
    /// A custom acceleration profile.
    ///
    /// Device movement acceleration depends on user defined custom
    /// acceleration functions for each movement type.
    #[cfg(feature = "libinput_1_23")]
    Custom,
}

/// Acceleration types are categories of movement by a device that may have
/// specific acceleration functions applied. A device always supports the
/// [`AccelType::Motion`] type (for regular pointer motion).
/// Other types (e.g. scrolling) may be added in the future.
///
/// The special type [`AccelType::Fallback`] specifies the acceleration function
/// to be moved for any movement produced by the device that does not have a
/// specific acceleration type defined.
///
/// Use to specify the acceleration function type in [`AccelConfig::set_points`].
///
/// Each device implements a subset of those types, see a list of supported
/// devices for each movement type definition.
#[cfg(feature = "libinput_1_23")]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum AccelType {
    /// The default acceleration type used as a fallback when other acceleration
    /// types are not provided.
    Fallback,
    /// Acceleration type for regular pointer movement.
    ///
    /// This type is always supported.
    Motion,
    /// Acceleration type for scroll movement.
    ///
    /// This type is supported by mouse and touchpad.
    Scroll,
}

/// The click method defines when to generate software-emulated
/// buttons, usually on a device that does not have a specific
/// physical button available.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ClickMethod {
    /// Use software-button areas (see [Clickfinger behavior](https://wayland.freedesktop.org/libinput/doc/latest/clickpad_softbuttons.html#clickfinger))
    /// to generate button events.
    ButtonAreas,
    /// The number of fingers decides which button press to generate.
    Clickfinger,
}

/// The scroll method of a device selects when to generate scroll axis
/// events instead of pointer motion events.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ScrollMethod {
    /// Never send scroll events instead of pointer motion events.
    ///
    /// This has no effect on events generated by scroll wheels.
    NoScroll,
    /// Send scroll events when two fingers are logically down on the
    /// device.
    TwoFinger,
    /// Send scroll events when a finger moves along the bottom or
    /// right edge of a device.
    Edge,
    /// Send scroll events when a button is down and the device moves
    /// along a scroll-capable axis.
    OnButtonDown,
}

/// Errors returned when applying configuration settings.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum DeviceConfigError {
    /// Configuration not available on this device.
    Unsupported,
    /// Invalid parameter range.
    Invalid,
}

impl DeviceConfigError {
    #[cfg(feature = "libinput_1_29")]
    pub(crate) fn from_ffi(v: ffi::libinput_config_status) -> DeviceConfigResult {
        match v {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => unreachable!("libinput returned invalid 'libinput_config_status'"),
        }
    }
}

bitflags! {
    /// The send-event mode of a device defines when a device may generate
    /// events and pass those events to the caller.
    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
    pub struct SendEventsMode: u32 {
        /// Send events from this device normally.
        ///
        /// This is a placeholder mode only, any device detected by
        /// libinput can be enabled. Do not test for this value as bitmask.
        const ENABLED = ffi::libinput_config_send_events_mode_LIBINPUT_CONFIG_SEND_EVENTS_ENABLED;
        /// Do not send events through this device.
        ///
        /// Depending on the device, this may close all file descriptors
        /// on the device or it may leave the file descriptors open and
        /// route events through a different device.
        ///
        /// If this mode is set, other disable modes may be ignored.
        /// For example, if both `Disabled` and `DisabledOnExternalMouse`
        /// are set, the device remains disabled when all external pointer
        /// devices are unplugged.
        const DISABLED = ffi::libinput_config_send_events_mode_LIBINPUT_CONFIG_SEND_EVENTS_DISABLED;
        /// If an external pointer device is plugged in, do not send
        /// events from this device.
        ///
        /// This option may be available on built-in touchpads.
        const DISABLED_ON_EXTERNAL_MOUSE = ffi::libinput_config_send_events_mode_LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE;
    }
}

/// Map 1/2/3 finger tips to buttons
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TapButtonMap {
    /// 1/2/3 finger tap maps to left/right/middle
    LeftRightMiddle,
    /// 1/2/3 finger tap maps to left/middle/right
    LeftMiddleRight,
}

/// Map 1/2/3 finger clicks to buttons
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[cfg(feature = "libinput_1_26")]
pub enum ClickfingerButtonMap {
    /// 1/2/3 finger click maps to left/right/middle
    LeftRightMiddle,
    /// 1/2/3 finger click maps to left/middle/right
    LeftMiddleRight,
}

/// Drag lock state
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DragLockState {
    /// Drag lock is to be disabled, or is currently disabled
    Disabled,
    /// Drag lock is to be enabled in timeout mode,
    /// or is currently enabled in timeout mode
    EnabledTimeout,
    /// Drag lock is to be enabled in sticky mode,
    /// or is currently enabled in sticky mode
    #[cfg(feature = "libinput_1_27")]
    EnabledSticky,
}

/// A config status to distinguish or set 3-finger dragging on a device.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[cfg(feature = "libinput_1_28")]
pub enum ThreeFingerDragState {
    /// Drag is to be disabled, or is currently disabled
    Disabled,
    /// Drag is to be enabled for 3 fingers, or is currently enabled
    EnabledThreeFinger,
    /// Drag is to be enabled for 4 fingers, or is currently enabled
    EnabledFourFinger,
}

/// Whenever scroll button lock is enabled or not
#[cfg(feature = "libinput_1_15")]
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ScrollButtonLockState {
    Disabled,
    Enabled,
}

/// Result returned when applying configuration settings.
pub type DeviceConfigResult = Result<(), DeviceConfigError>;

bitflags! {
    /// Mask reflecting LEDs on a device.
    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
    pub struct Led: u32 {
        /// Num Lock Led
        const NUMLOCK = ffi::libinput_led_LIBINPUT_LED_NUM_LOCK;
        /// Caps Lock Led
        const CAPSLOCK = ffi::libinput_led_LIBINPUT_LED_CAPS_LOCK;
        /// Scroll Lock Led
        const SCROLLLOCK = ffi::libinput_led_LIBINPUT_LED_SCROLL_LOCK;
        /// Compose Key Led
        #[cfg(feature = "libinput_1_26")]
        const COMPOSE = ffi::libinput_led_LIBINPUT_LED_COMPOSE;
        /// Kana Key Led
        #[cfg(feature = "libinput_1_26")]
        const KANA = ffi::libinput_led_LIBINPUT_LED_KANA;
    }
}

/// Describes a rectangle to configure a device’s area, see [`Device::config_area_set_rectangle`].
///
/// This struct describes a rectangle via the upper left points (x1, y1) and the lower right point (x2, y2).
///
/// All arguments are normalized to the range [0.0, 1.0] to represent the corresponding proportion of the device’s width and height, respectively. A rectangle covering the whole device thus comprises of the points (0.0, 0.0) and (1.0, 1.0).
///
/// The conditions x1 < x2 and y1 < y2 must be true.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg(feature = "libinput_1_27")]
pub struct AreaRectangle {
    /// x1 coordinate
    pub x1: f64,
    /// y1 coordinate
    pub y1: f64,
    /// x2 coordinate
    pub x2: f64,
    /// y2 coordinate
    pub y2: f64,
}

#[cfg(feature = "libinput_1_27")]
impl From<AreaRectangle> for ffi::libinput_config_area_rectangle {
    fn from(rect: AreaRectangle) -> Self {
        input_sys::libinput_config_area_rectangle {
            x1: rect.x1,
            y1: rect.y1,
            x2: rect.x2,
            y2: rect.y2,
        }
    }
}

#[cfg(feature = "libinput_1_27")]
impl From<ffi::libinput_config_area_rectangle> for AreaRectangle {
    fn from(rect: ffi::libinput_config_area_rectangle) -> Self {
        AreaRectangle {
            x1: rect.x1,
            y1: rect.y1,
            x2: rect.x2,
            y2: rect.y2,
        }
    }
}

ffi_ref_struct!(
/// Device group
///
/// Some physical devices like graphics tablets are represented by
/// multiple kernel devices and thus by multiple `Device`s.
///
/// libinput assigns these devices to the same `DeviceGroup` allowing
/// the caller to identify such devices and adjust configuration
/// settings accordingly. For example, setting a tablet to left-handed
/// often means turning it upside down. A touch device on the same
/// tablet would need to be turned upside down too to work correctly.
///
/// All devices are part of a device group though for most devices the
/// group will be a singleton. A device is assigned to a device group
/// on `DeviceAddedEvent` and removed from that group on
/// `DeviceRemovedEvent`. It is up to the caller to track how many
/// devices are in each device group.
///
/// Device groups do not get re-used once the last device in the group
/// was removed, i.e. unplugging and re-plugging a physical device
/// with grouped devices will return a different device group after
/// every unplug.
///
/// Device groups are assigned based on the LIBINPUT_DEVICE_GROUP udev
/// property, see [Static device configuration](https://wayland.freedesktop.org/libinput/doc/latest/udev_config.html) via udev.
struct DeviceGroup, ffi::libinput_device_group, ffi::libinput_device_group_ref, ffi::libinput_device_group_unref);

ffi_ref_struct!(
/// Representation of a single input device as seen by the kernel.
///
/// A single physical device might consist out of multiple input
/// devices like a keyboard-touchpad combination. See `DeviceGroup`
/// if you want to track such combined physical devices.
struct Device, ffi::libinput_device, ffi::libinput_device_ref, ffi::libinput_device_unref);

impl Device {
    /// Get the libinput context from the device.
    pub fn context(&self) -> Libinput {
        self.context.clone()
    }

    /// Get the device group this device is assigned to.
    ///
    /// Some physical devices like graphics tablets are represented by
    /// multiple kernel devices and thus by multiple `Device`s.
    ///
    /// libinput assigns these devices to the same `DeviceGroup`
    /// allowing the caller to identify such devices and adjust
    /// configuration settings accordingly. For example, setting a
    /// tablet to left-handed often means turning it upside down. A
    /// touch device on the same tablet would need to be turned upside
    /// down too to work correctly.
    ///
    /// All devices are part of a device group though for most devices
    /// the group will be a singleton. A device is assigned to a
    /// device group on `DeviceAddedEvent` and removed from that group
    /// on `DeviceRemovedEvent`. It is up to the caller to track how
    /// many devices are in each device group.
    ///
    /// Device groups do not get re-used once the last device in the
    /// group was removed, i.e. unplugging and re-plugging a physical
    /// device with grouped devices will return a different device
    /// group after every unplug.
    ///
    /// Device groups are assigned based on the `LIBINPUT_DEVICE_GROUP`
    /// udev property, see [Static device configuration](https://wayland.freedesktop.org/libinput/doc/latest/udev_config.html) via udev.
    pub fn device_group(&self) -> DeviceGroup {
        unsafe {
            DeviceGroup::from_raw(
                ffi::libinput_device_get_device_group(self.as_raw_mut()),
                &self.context,
            )
        }
    }

    /// Get the system name of the device.
    ///
    /// To get the descriptive device name, use `name`.
    pub fn sysname(&self) -> &str {
        unsafe {
            CStr::from_ptr(ffi::libinput_device_get_sysname(self.as_raw_mut()))
                .to_str()
                .expect("Device sysname is no valid utf8")
        }
    }

    /// The descriptive device name as advertised by the kernel and/or
    /// the hardware itself.
    ///
    /// To get the sysname for this device, use `sysname`.
    pub fn name(&self) -> Cow<'_, str> {
        unsafe {
            CStr::from_ptr(ffi::libinput_device_get_name(self.as_raw_mut())).to_string_lossy()
        }
    }

    /// A device may be mapped to a single output, or all available
    /// outputs.
    ///
    /// If a device is mapped to a single output only, a relative
    /// device may not move beyond the boundaries of this output. An
    /// absolute device has its input coordinates mapped to the
    /// extents of this output.
    pub fn output_name(&self) -> Option<Cow<'_, str>> {
        unsafe {
            let ptr = ffi::libinput_device_get_output_name(self.as_raw_mut());
            if !ptr.is_null() {
                Some(CStr::from_ptr(ptr).to_string_lossy())
            } else {
                None
            }
        }
    }

    ffi_func!(
    /// Get the product ID for this device.
    pub fn id_product, ffi::libinput_device_get_id_product, u32);

    #[cfg(feature = "libinput_1_26")]
    ffi_func!(
    /// Get the bus type ID for this device.
    pub fn id_bustype, ffi::libinput_device_get_id_bustype, u32);

    ffi_func!(
    /// Get the vendor ID for this device.
    pub fn id_vendor, ffi::libinput_device_get_id_vendor, u32);

    /// Get the seat associated with this input device, see
    /// [Seats](https://wayland.freedesktop.org/libinput/doc/latest/seats.html)
    /// for details.
    ///
    /// A seat can be uniquely identified by the physical and logical
    /// seat name. There will ever be only one seat instance with a
    /// given physical and logical seat name pair at any given time,
    /// but if no external reference is kept, it may be destroyed if
    /// no device belonging to it is left.
    pub fn seat(&self) -> Seat {
        unsafe {
            Seat::from_raw(
                ffi::libinput_device_get_seat(self.as_raw_mut()),
                &self.context,
            )
        }
    }

    /// Change the logical seat associated with this device by removing the device and adding it to the new seat.
    ///
    /// This command is identical to physically unplugging the device,
    /// then re-plugging it as a member of the new seat. libinput will
    /// generate a `DeviceRemovedEvent` event and this `Device` is
    /// considered removed from the context; it will not generate
    /// further events and will be freed when it goes out of scope.
    /// A `DeviceAddedEvent` event is generated with a new `Device`
    /// handle. It is the caller's responsibility to update references
    /// to the new device accordingly.
    ///
    /// If the logical seat name already exists in the device's
    /// physical seat, the device is added to this seat. Otherwise, a
    /// new seat is created.
    ///
    /// ## Note
    /// This change applies to this device until removal or `suspend`,
    /// whichever happens earlier.
    pub fn set_seat_logical_name(&mut self, name: &str) -> Result<(), ()> {
        let name = CString::new(name).expect("New logical_seat name contained a null-byte");
        unsafe {
            if ffi::libinput_device_set_seat_logical_name(self.as_raw_mut(), name.as_ptr()) == 0 {
                Ok(())
            } else {
                Err(())
            }
        }
    }

    /// Return a udev handle to the device that is this libinput
    /// device, if any.
    ///
    /// Some devices may not have a udev device, or the udev device
    /// may be unobtainable. This function returns `None` if no udev
    /// device was available.
    ///
    /// Calling this function multiple times for the same device may
    /// not return the same udev handle each time.
    ///
    /// # Safety
    ///
    /// The result of this function is not definied if the passed udev `Context`
    /// is not the same as the one the libinput `Context` was created from.
    #[cfg(feature = "udev")]
    pub unsafe fn udev_device(&self) -> Option<UdevDevice> {
        let dev: *mut udev_device = ffi::libinput_device_get_udev_device(self.ffi) as *mut _;
        if dev.is_null() {
            None
        } else {
            // We have to ref the returned udev context as udev_device_get_udev does not
            // increase the ref_count but dropping a UdevDevice will unref it
            let ctx: *mut udev_context = udev_ref(udev_device_get_udev(dev));
            Some(UdevDevice::from_raw_with_context(ctx, dev))
        }
    }

    /// Update the LEDs on the device, if any.
    ///
    /// If the device does not have LEDs, or does not have one or more
    /// of the LEDs given in the mask, this function does nothing.
    ///
    /// ## Arguments
    ///
    /// leds: Leds to turn on
    ///
    /// Missing `Led`s will be turned off.
    /// The slice is interpreted as a bitmap.
    pub fn led_update(&mut self, leds: Led) {
        unsafe { ffi::libinput_device_led_update(self.as_raw_mut(), leds.bits()) }
    }

    /// Check if the given device has the specified capability.
    pub fn has_capability(&self, cap: DeviceCapability) -> bool {
        unsafe {
            ffi::libinput_device_has_capability(
                self.as_raw_mut(),
                match cap {
                    DeviceCapability::Keyboard => {
                        ffi::libinput_device_capability_LIBINPUT_DEVICE_CAP_KEYBOARD
                    }
                    DeviceCapability::Pointer => {
                        ffi::libinput_device_capability_LIBINPUT_DEVICE_CAP_POINTER
                    }
                    DeviceCapability::Touch => {
                        ffi::libinput_device_capability_LIBINPUT_DEVICE_CAP_TOUCH
                    }
                    DeviceCapability::TabletTool => {
                        ffi::libinput_device_capability_LIBINPUT_DEVICE_CAP_TABLET_TOOL
                    }
                    DeviceCapability::TabletPad => {
                        ffi::libinput_device_capability_LIBINPUT_DEVICE_CAP_TABLET_PAD
                    }
                    DeviceCapability::Gesture => {
                        ffi::libinput_device_capability_LIBINPUT_DEVICE_CAP_GESTURE
                    }
                    DeviceCapability::Switch => {
                        ffi::libinput_device_capability_LIBINPUT_DEVICE_CAP_SWITCH
                    }
                },
            ) != 0
        }
    }

    /// Get the physical size of a device in mm, where meaningful.
    ///
    /// This function only succeeds on devices with the required data,
    /// i.e. tablets, touchpads and touchscreens.
    pub fn size(&self) -> Option<(f64, f64)> {
        let mut width = 0.0;
        let mut height = 0.0;

        match unsafe {
            ffi::libinput_device_get_size(
                self.as_raw_mut(),
                &mut width as *mut _,
                &mut height as *mut _,
            )
        } {
            0 => Some((width, height)),
            _ => None,
        }
    }

    /// Check if a `DeviceCapability::Pointer` device has a button
    /// with the given code (see linux/input.h).
    pub fn pointer_has_button(&self, button: u32) -> Result<bool, ()> {
        match unsafe { ffi::libinput_device_pointer_has_button(self.as_raw_mut(), button) } {
            1 => Ok(true),
            0 => Ok(false),
            -1 => Err(()),
            _ => unreachable!(),
        }
    }

    /// Check if a `DeviceCapability::Keyboard` device has a key with
    /// the given code (see linux/input.h).
    pub fn keyboard_has_key(&self, key: u32) -> Result<bool, ()> {
        match unsafe { ffi::libinput_device_keyboard_has_key(self.as_raw_mut(), key) } {
            1 => Ok(true),
            0 => Ok(false),
            -1 => Err(()),
            _ => unreachable!(),
        }
    }

    /// Check if a `DeviceCapability::Switch` device has a switch of the
    /// given type.
    pub fn switch_has_switch(&self, switch: Switch) -> Result<bool, ()> {
        match unsafe { ffi::libinput_device_switch_has_switch(self.as_raw_mut(), switch as u32) } {
            1 => Ok(true),
            0 => Ok(false),
            -1 => Err(()),
            _ => unreachable!(),
        }
    }

    ffi_func!(
    /// Return the number of buttons on a device with the
    /// `DeviceCapability::TabletPad` capability.
    ///
    /// Buttons on a pad device are numbered sequentially, see Tablet
    /// pad button numbers for details.
    pub fn tablet_pad_number_of_buttons, ffi::libinput_device_tablet_pad_get_num_buttons, i32);
    ffi_func!(
    /// Return the number of rings a device with the
    /// `DeviceCapability::TabletPad` capability provides.
    pub fn tablet_pad_number_of_rings, ffi::libinput_device_tablet_pad_get_num_rings, i32);
    ffi_func!(
    /// Return the number of strips a device with the
    /// `DeviceCapability::TabletPad` capability provides.
    pub fn tablet_pad_number_of_strips, ffi::libinput_device_tablet_pad_get_num_strips, i32);
    ffi_func!(
    /// Most devices only provide a single mode group, however devices
    /// such as the Wacom Cintiq 22HD provide two mode groups.
    ///
    /// If multiple mode groups are available, a caller should use
    /// `TabletPadModeGroup::has_button`,
    /// `TabletPadModeGroup::has_ring`,
    /// `TabletPadModeGroup::has_dial` (since libinput 1.26.0) and
    /// `TabletPadModeGroup::has_strip()` to associate each button,
    /// ring and strip with the correct mode group.
    pub fn tablet_pad_number_of_mode_groups, ffi::libinput_device_tablet_pad_get_num_mode_groups, i32);

    /// Return the current mode this mode group is in.
    ///
    /// Note that the returned mode is the mode valid as of completing
    /// the last `dispatch`. The returned mode may thus be
    /// different than the mode returned by
    /// `TabletPadEventTrait::mode`.
    ///
    /// For example, if the mode was toggled three times between the
    /// call to `dispatch`, this function returns the third mode but
    /// the events in the event queue will return the modes 1, 2 and
    /// 3, respectively.
    pub fn tablet_pad_mode_group(&self, index: u32) -> Option<TabletPadModeGroup> {
        let ptr =
            unsafe { ffi::libinput_device_tablet_pad_get_mode_group(self.as_raw_mut(), index) };
        if ptr.is_null() {
            None
        } else {
            Some(unsafe { TabletPadModeGroup::from_raw(ptr, &self.context) })
        }
    }

    /// Check if a `DeviceCapability::TabletPad`-device has a key with the given code (see linux/input-event-codes.h).
    ///
    /// ## Returns
    /// - `Some(true)` if it has the requested key
    /// - `Some(false)` if it has not
    /// - `None` on error (no TabletPad device)
    #[cfg(feature = "libinput_1_15")]
    pub fn tablet_pad_has_key(&self, code: u32) -> Option<bool> {
        match unsafe { ffi::libinput_device_tablet_pad_has_key(self.as_raw_mut(), code) } {
            -1 => None,
            0 => Some(false),
            1 => Some(true),
            _ => panic!(
                "libinput returned invalid return code for libinput_device_tablet_pad_has_key"
            ),
        }
    }

    /// Check how many touches a `DeviceCapability::Touch`-exposing Device supports simultaneously.
    ///
    /// ## Returns
    /// - `Some(n)` amount of touches
    /// - `Some(0)` if unknown
    /// - `None` on error (no touch device)
    #[cfg(feature = "libinput_1_11")]
    pub fn touch_count(&mut self) -> Option<u32> {
        match unsafe { ffi::libinput_device_touch_get_touch_count(self.as_raw_mut()) } {
            -1 => None,
            n => Some(n as u32),
        }
    }

    /// Apply this pointer acceleration configuration to the device.
    ///
    /// This changes the device’s pointer acceleration method to the method
    /// given in [`AccelConfig::new`] and applies all other configuration
    /// settings.
    #[cfg(feature = "libinput_1_23")]
    pub fn config_accel_apply(&self, accel_config: AccelConfig) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_accel_apply(self.as_raw_mut(), accel_config.as_raw_mut())
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Return the default pointer acceleration profile for this
    /// pointer device.
    pub fn config_accel_default_profile(&self) -> Option<AccelProfile> {
        match unsafe { ffi::libinput_device_config_accel_get_default_profile(self.as_raw_mut()) } {
            ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_NONE => None,
            ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT => {
                Some(AccelProfile::Flat)
            }
            ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE => {
                Some(AccelProfile::Adaptive)
            }
            #[cfg(feature = "libinput_1_23")]
            ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM => {
                Some(AccelProfile::Custom)
            }
            _x => {
                #[cfg(feature = "log")]
                log::warn!(
                    "Unknown AccelProfile ({}). Unsupported libinput version?",
                    _x
                );
                None
            }
        }
    }

    /// Get the current pointer acceleration profile for this pointer
    /// device.
    pub fn config_accel_profile(&self) -> Option<AccelProfile> {
        match unsafe { ffi::libinput_device_config_accel_get_profile(self.as_raw_mut()) } {
            ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_NONE => None,
            ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT => {
                Some(AccelProfile::Flat)
            }
            ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE => {
                Some(AccelProfile::Adaptive)
            }
            #[cfg(feature = "libinput_1_23")]
            ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM => {
                Some(AccelProfile::Custom)
            }
            _x => {
                #[cfg(feature = "log")]
                log::warn!(
                    "Unknown AccelProfile ({}). Unsupported libinput version?",
                    _x
                );
                None
            }
        }
    }

    /// Returns a bitmask of the configurable acceleration modes
    /// available on this device.
    pub fn config_accel_profiles(&self) -> Vec<AccelProfile> {
        let mut profiles = Vec::new();
        let bitmask = unsafe { ffi::libinput_device_config_accel_get_profiles(self.as_raw_mut()) };
        if bitmask & ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT as u32
            != 0
        {
            profiles.push(AccelProfile::Flat);
        }
        if bitmask
            & ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE as u32
            != 0
        {
            profiles.push(AccelProfile::Adaptive);
        }
        #[cfg(feature = "libinput_1_23")]
        if bitmask & ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM as u32
            != 0
        {
            profiles.push(AccelProfile::Custom);
        }
        profiles
    }

    /// Set the pointer acceleration profile of this pointer device to
    /// the given mode.
    pub fn config_accel_set_profile(&mut self, profile: AccelProfile) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_accel_set_profile(
                self.as_raw_mut(),
                match profile {
                    AccelProfile::Flat => {
                        ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT
                    }
                    AccelProfile::Adaptive => {
                        ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE
                    }
                    #[cfg(feature = "libinput_1_23")]
                    AccelProfile::Custom => {
                        ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM
                    }
                },
            )
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    ffi_func!(
    /// Return the default speed setting for this device, normalized
    /// to a range of [-1, 1].
    pub fn config_accel_default_speed, ffi::libinput_device_config_accel_get_default_speed, f64);
    ffi_func!(
    /// Get the current pointer acceleration setting for this pointer
    /// device.
    ///
    /// The returned value is normalized to a range of [-1, 1]. See
    /// `config_accel_set_speed` for details.
    pub fn config_accel_speed, ffi::libinput_device_config_accel_get_speed, f64);

    /// Set the pointer acceleration speed of this pointer device
    /// within a range of [-1, 1], where 0 is the default acceleration
    /// for this device, -1 is the slowest acceleration and 1 is the
    /// maximum acceleration available on this device.
    ///
    /// The actual pointer acceleration mechanism is
    /// implementation-dependent, as is the number of steps available
    /// within the range. libinput picks the semantically closest
    /// acceleration step if the requested value does not match a
    /// discrete setting.
    pub fn config_accel_set_speed(&mut self, speed: f64) -> DeviceConfigResult {
        match unsafe { ffi::libinput_device_config_accel_set_speed(self.as_raw_mut(), speed) } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    ffi_func!(
    /// Check if a device uses libinput-internal pointer-acceleration.
    pub fn config_accel_is_available, ffi::libinput_device_config_accel_is_available, bool);

    /// Return the default calibration matrix for this device.
    ///
    /// On most devices, this is the identity matrix. If the udev
    /// property `LIBINPUT_CALIBRATION_MATRIX` is set on the respective u
    /// dev device, that property's value becomes the default matrix,
    /// see [Static device configuration via udev](https://wayland.freedesktop.org/libinput/doc/latest/udev_config.html).
    pub fn config_calibration_default_matrix(&self) -> Option<[f32; 6]> {
        let mut matrix = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
        if unsafe {
            ffi::libinput_device_config_calibration_get_default_matrix(
                self.as_raw_mut(),
                matrix.as_mut_ptr(),
            ) != 0
        } {
            Some(matrix)
        } else {
            None
        }
    }

    /// Return the current calibration matrix for this device.
    pub fn config_calibration_matrix(&self) -> Option<[f32; 6]> {
        let mut matrix = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
        if unsafe {
            ffi::libinput_device_config_calibration_get_matrix(
                self.as_raw_mut(),
                matrix.as_mut_ptr(),
            ) != 0
        } {
            Some(matrix)
        } else {
            None
        }
    }

    ffi_func!(
    /// Check if the device can be calibrated via a calibration matrix.
    pub fn config_calibration_has_matrix, ffi::libinput_device_config_calibration_has_matrix, bool);

    #[cfg(feature = "libinput_1_27")]
    ffi_func!(
    /// Check if the device can change its logical input area via a rectangle.
    pub fn config_area_has_rectangle, ffi::libinput_device_config_area_has_rectangle, bool);

    /// Set the given rectangle as the logical input area of this device.
    ///
    /// Future interactions by a tablet tool on this devices are scaled to only
    /// consider events within this logical input area - as if the logical input
    /// area were the available physical area.
    ///
    /// The coordinates of the rectangle represent the proportion of the
    /// available maximum physical area, normalized to the range [0.0, 1.0].
    /// For example, a rectangle with the two points 0.25, 0.5, 0.75, 1.0 adds
    /// a 25% dead zone to the left and right and a 50% dead zone on the top:
    ///
    /// ```text
    /// +----------------------------------+
    /// |                                  |
    /// |                50%               |
    /// |                                  |
    /// |        +-----------------+       |
    /// |        |                 |       |
    /// |   25%  |                 |  25%  |
    /// |        |                 |       |
    /// +--------+-----------------+-------+
    /// ```
    /// The area applies in the tablet’s current logical rotation, i.e. the
    /// above example is always at the bottom of the tablet.
    ///
    /// Once applied, the logical area’s top-left coordinate (in the current
    /// logical rotation) becomes the new offset (0/0) and the return values
    /// of [`TabletToolEventTrait::x`](crate::event::tablet_tool::TabletToolEventTrait::x) and
    /// [`TabletToolEventTrait::y`](crate::event::tablet_tool::TabletToolEventTrait::y)
    /// are in relation to this new offset.
    ///
    /// Likewise, [`TabletToolEventTrait::x_transformed`](crate::event::tablet_tool::TabletToolEventTrait::x_transformed)
    /// and [`TabletToolEventTrait::y_transformed`](crate::event::tablet_tool::TabletToolEventTrait::y_transformed)
    /// represent the value scaled into the configured logical area.
    ///
    /// The return value of libinput_device_get_size() is not affected by the configured area.
    ///
    /// Changing the area may not take effect immediately, the device may wait until it is in a
    /// neutral state before applying any changes.
    #[cfg(feature = "libinput_1_27")]
    pub fn config_area_set_rectangle(&self, area: AreaRectangle) -> DeviceConfigResult {
        let area = area.into();
        match unsafe {
            ffi::libinput_device_config_area_set_rectangle(self.as_raw_mut(), &raw const area)
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Return the current area rectangle for this device.
    ///
    /// The return value for a device that does not support area rectangles is
    /// a rectangle with the points 0/0 and 1/1.
    #[cfg(feature = "libinput_1_27")]
    pub fn config_area_get_rectangle(&self) -> AreaRectangle {
        unsafe { ffi::libinput_device_config_area_get_rectangle(self.as_raw_mut()) }.into()
    }

    /// Return the default area rectangle for this device.
    ///
    /// The return value for a device that does not support area rectangles is
    /// a rectangle with the points 0/0 and 1/1.
    #[cfg(feature = "libinput_1_27")]
    pub fn config_area_get_default_rectangle(&self) -> AreaRectangle {
        unsafe { ffi::libinput_device_config_area_get_default_rectangle(self.as_raw_mut()) }.into()
    }

    /// Apply the 3x3 transformation matrix to absolute device
    /// coordinates.
    ///
    /// This matrix has no effect on relative events.
    ///
    /// Given a 6-element array [a, b, c, d, e, f], the matrix is
    /// applied as
    /// ```text
    /// [ a  b  c ]   [ x ]
    /// [ d  e  f ] * [ y ]
    /// [ 0  0  1 ]   [ 1 ]
    /// # *
    /// ```
    /// The translation component (c, f) is expected to be normalized
    /// to the device coordinate range. For example, the matrix
    /// ```text
    /// [ 1 0  1 ]
    /// [ 0 1 -1 ]
    /// [ 0 0  1 ]
    /// ```
    /// moves all coordinates by 1 device-width to the right and 1
    /// device-height up.
    ///
    /// The rotation matrix for rotation around the origin is defined
    /// as
    /// ```text
    /// [ cos(a) -sin(a) 0 ]
    /// [ sin(a)  cos(a) 0 ]
    /// [   0      0     1 ]
    /// ```
    /// Note that any rotation requires an additional translation
    /// component to translate the rotated coordinates back into the
    /// original device space. The rotation matrixes for 90, 180 and
    /// 270 degrees clockwise are:
    /// ```text
    /// 90 deg cw:              180 deg cw:             270 deg cw:
    /// [ 0 -1 1]               [ -1  0 1]              [  0 1 0 ]
    /// [ 1  0 0]               [  0 -1 1]              [ -1 0 1 ]
    /// [ 0  0 1]               [  0  0 1]              [  0 0 1 ]
    /// ```
    pub fn config_calibration_set_matrix(&mut self, matrix: [f32; 6]) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_calibration_set_matrix(self.as_raw_mut(), matrix.as_ptr())
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Get the default button click method for this device.
    ///
    /// The button click method defines when to generate
    /// software-emulated buttons, usually on a device that does not
    /// have a specific physical button available.
    pub fn config_click_default_method(&self) -> Option<ClickMethod> {
        match unsafe { ffi::libinput_device_config_click_get_default_method(self.as_raw_mut()) } {
            ffi::libinput_config_click_method_LIBINPUT_CONFIG_CLICK_METHOD_NONE => None,
            ffi::libinput_config_click_method_LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS => {
                Some(ClickMethod::ButtonAreas)
            }
            ffi::libinput_config_click_method_LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER => {
                Some(ClickMethod::Clickfinger)
            }
            _x => {
                #[cfg(feature = "log")]
                log::warn!(
                    "Unknown ClickMethod ({}). Unsupported libinput version?",
                    _x
                );
                None
            }
        }
    }

    /// Get the button click method for this device.
    ///
    /// The button click method defines when to generate
    /// software-emulated buttons, usually on a device that does not
    /// have a specific physical button available.
    pub fn config_click_method(&self) -> Option<ClickMethod> {
        match unsafe { ffi::libinput_device_config_click_get_method(self.as_raw_mut()) } {
            ffi::libinput_config_click_method_LIBINPUT_CONFIG_CLICK_METHOD_NONE => None,
            ffi::libinput_config_click_method_LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS => {
                Some(ClickMethod::ButtonAreas)
            }
            ffi::libinput_config_click_method_LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER => {
                Some(ClickMethod::Clickfinger)
            }
            _x => {
                #[cfg(feature = "log")]
                log::warn!(
                    "Unknown ClickMethod ({}). Unsupported libinput version?",
                    _x
                );
                None
            }
        }
    }

    /// Check which button click methods a device supports.
    ///
    /// The button click method defines when to generate
    /// software-emulated buttons, usually on a device that does not
    /// have a specific physical button available.
    pub fn config_click_methods(&self) -> Vec<ClickMethod> {
        let mut methods = Vec::new();
        let bitmask = unsafe { ffi::libinput_device_config_click_get_methods(self.as_raw_mut()) };
        if bitmask
            & ffi::libinput_config_click_method_LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER as u32
            != 0
        {
            methods.push(ClickMethod::Clickfinger);
        }
        if bitmask
            & ffi::libinput_config_click_method_LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS as u32
            != 0
        {
            methods.push(ClickMethod::ButtonAreas);
        }
        methods
    }

    /// Set the button click method for this device.
    ///
    /// The button click method defines when to generate
    /// software-emulated buttons, usually on a device that does not
    /// have a specific physical button available.
    ///
    /// ## Note
    ///
    /// The selected click method may not take effect immediately. The
    /// device may require changing to a neutral state first before
    /// activating the new method.
    pub fn config_click_set_method(&mut self, method: ClickMethod) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_click_set_method(
                self.as_raw_mut(),
                match method {
                    ClickMethod::ButtonAreas => {
                        ffi::libinput_config_click_method_LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS
                    }
                    ClickMethod::Clickfinger => {
                        ffi::libinput_config_click_method_LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER
                    }
                },
            )
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Get the finger number to button number mapping for clickfinger.
    ///
    /// The return value for a device that does not support clickfinger is
    /// always [`ClickfingerButtonMap::LeftRightMiddle`].
    #[cfg(feature = "libinput_1_26")]
    pub fn config_click_clickfinger_default_button_map(&self) -> ClickfingerButtonMap {
        match unsafe {
            ffi::libinput_device_config_click_get_default_clickfinger_button_map(self.as_raw_mut())
        } {
            ffi::libinput_config_clickfinger_button_map_LIBINPUT_CONFIG_CLICKFINGER_MAP_LRM => {
                ClickfingerButtonMap::LeftRightMiddle
            }
            ffi::libinput_config_clickfinger_button_map_LIBINPUT_CONFIG_CLICKFINGER_MAP_LMR => {
                ClickfingerButtonMap::LeftMiddleRight
            }
            _ => panic!("libinput returned invalid 'libinput_config_tap_button_map'"),
        }
    }

    /// Get the default finger number to button number mapping for clickfinger.
    ///
    /// The return value for a device that does not support clickfinger is
    /// always [`ClickfingerButtonMap::LeftRightMiddle`].
    #[cfg(feature = "libinput_1_26")]
    pub fn config_click_clickfinger_button_map(&self) -> ClickfingerButtonMap {
        match unsafe {
            ffi::libinput_device_config_click_get_clickfinger_button_map(self.as_raw_mut())
        } {
            ffi::libinput_config_clickfinger_button_map_LIBINPUT_CONFIG_CLICKFINGER_MAP_LRM => {
                ClickfingerButtonMap::LeftRightMiddle
            }
            ffi::libinput_config_clickfinger_button_map_LIBINPUT_CONFIG_CLICKFINGER_MAP_LMR => {
                ClickfingerButtonMap::LeftMiddleRight
            }
            _ => panic!("libinput returned invalid 'libinput_config_tap_button_map'"),
        }
    }

    /// Set the finger number to button number mapping for clickfinger.
    ///
    /// The default mapping on most devices is to have a 1, 2 and 3 finger tap
    /// to map to the left, right and middle button, respectively. A device may
    /// permit changing the button mapping but disallow specific maps. In this
    /// case [`DeviceConfigError::Unsupported`] is returned, the caller is expected
    /// to handle this case correctly.
    ///
    /// Changing the button mapping may not take effect immediately, the device may
    /// wait until it is in a neutral state before applying any changes.
    #[cfg(feature = "libinput_1_26")]
    pub fn config_click_clickfinger_set_button_map(
        &self,
        map: ClickfingerButtonMap,
    ) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_click_set_clickfinger_button_map(self.as_raw_mut(), match map {
                ClickfingerButtonMap::LeftRightMiddle => ffi::libinput_config_clickfinger_button_map_LIBINPUT_CONFIG_CLICKFINGER_MAP_LRM,
                ClickfingerButtonMap::LeftMiddleRight => ffi::libinput_config_clickfinger_button_map_LIBINPUT_CONFIG_CLICKFINGER_MAP_LMR,
            })
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Check if the disable-while typing feature is enabled on this
    /// device by default.
    ///
    /// If the device does not support disable-while-typing, this
    /// function returns `false`.
    pub fn config_dwt_default_enabled(&self) -> bool {
        match unsafe { ffi::libinput_device_config_dwt_get_default_enabled(self.as_raw_mut()) } {
            ffi::libinput_config_dwt_state_LIBINPUT_CONFIG_DWT_ENABLED => true,
            ffi::libinput_config_dwt_state_LIBINPUT_CONFIG_DWT_DISABLED => false,
            _ => panic!("libinput returned invalid 'libinput_config_dwt_state'"),
        }
    }

    /// Check if the disable-while typing feature is currently enabled
    /// on this device.
    ///
    /// If the device does not support disable-while-typing, this
    /// function returns `false`.
    pub fn config_dwt_enabled(&self) -> bool {
        match unsafe { ffi::libinput_device_config_dwt_get_enabled(self.as_raw_mut()) } {
            ffi::libinput_config_dwt_state_LIBINPUT_CONFIG_DWT_ENABLED => true,
            ffi::libinput_config_dwt_state_LIBINPUT_CONFIG_DWT_DISABLED => false,
            _ => panic!("libinput returned invalid 'libinput_config_dwt_state'"),
        }
    }

    ffi_func!(
    /// Check if this device supports configurable
    /// disable-while-typing feature.
    ///
    /// This feature is usually available on built-in touchpads and
    /// disables the touchpad while typing. See [Disable-while-typing](https://wayland.freedesktop.org/libinput/doc/latest/palm_detection.html#disable-while-typing)
    /// for details.
    pub fn config_dwt_is_available, ffi::libinput_device_config_dwt_is_available, bool);

    /// Enable or disable the disable-while-typing feature.
    ///
    /// When enabled, the device will be disabled while typing and
    /// for a short period after. See Disable-while-typing for
    /// details.
    ///
    /// ## Note
    ///
    /// Enabling or disabling disable-while-typing may not take
    /// effect immediately.
    pub fn config_dwt_set_enabled(&self, enabled: bool) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_dwt_set_enabled(
                self.as_raw_mut(),
                if enabled {
                    ffi::libinput_config_dwt_state_LIBINPUT_CONFIG_DWT_ENABLED
                } else {
                    ffi::libinput_config_dwt_state_LIBINPUT_CONFIG_DWT_DISABLED
                },
            )
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Check if the disable-while trackpointing feature is enabled on this
    /// device by default.
    ///
    /// If the device does not support disable-while-trackpointing, this
    /// function returns `false`.
    #[cfg(feature = "libinput_1_21")]
    pub fn config_dwtp_default_enabled(&self) -> bool {
        match unsafe { ffi::libinput_device_config_dwtp_get_default_enabled(self.as_raw_mut()) } {
            ffi::libinput_config_dwtp_state_LIBINPUT_CONFIG_DWTP_ENABLED => true,
            ffi::libinput_config_dwtp_state_LIBINPUT_CONFIG_DWTP_DISABLED => false,
            _ => panic!("libinput returned invalid 'libinput_config_dwtp_state'"),
        }
    }

    /// Check if the disable-while trackpointing feature is currently enabled
    /// on this device.
    ///
    /// If the device does not support disable-while-trackpointing, this
    /// function returns `false`.
    #[cfg(feature = "libinput_1_21")]
    pub fn config_dwtp_enabled(&self) -> bool {
        match unsafe { ffi::libinput_device_config_dwtp_get_enabled(self.as_raw_mut()) } {
            ffi::libinput_config_dwtp_state_LIBINPUT_CONFIG_DWTP_ENABLED => true,
            ffi::libinput_config_dwtp_state_LIBINPUT_CONFIG_DWTP_DISABLED => false,
            _ => panic!("libinput returned invalid 'libinput_config_dwtp_state'"),
        }
    }

    ffi_func!(
    /// Check if this device supports configurable
    /// disable-while-trackpointing feature.
    ///
    /// This feature is usually available on Thinkpads and
    /// disables the touchpad while using the trackpoint.
    #[cfg(feature = "libinput_1_21")]
    pub fn config_dwtp_is_available, ffi::libinput_device_config_dwtp_is_available, bool);

    /// Enable or disable the disable-while-trackpointing feature.
    ///
    /// When enabled, the device will be disabled while using the trackpoint and
    /// for a short period after.
    ///
    /// ## Note
    ///
    /// Enabling or disabling disable-while-trackpointing may not take
    /// effect immediately.
    #[cfg(feature = "libinput_1_21")]
    pub fn config_dwtp_set_enabled(&self, enabled: bool) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_dwtp_set_enabled(
                self.as_raw_mut(),
                if enabled {
                    ffi::libinput_config_dwtp_state_LIBINPUT_CONFIG_DWTP_ENABLED
                } else {
                    ffi::libinput_config_dwtp_state_LIBINPUT_CONFIG_DWTP_DISABLED
                },
            )
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    ffi_func!(
    /// Get the current left-handed configuration of the device.
    pub fn config_left_handed, ffi::libinput_device_config_left_handed_get, bool);
    ffi_func!(
    /// Get the default left-handed configuration of the device.
    pub fn config_left_handed_default, ffi::libinput_device_config_left_handed_get_default, bool);
    ffi_func!(
    /// Check if a device has a configuration that supports
    /// left-handed usage.
    pub fn config_left_handed_is_available, ffi::libinput_device_config_left_handed_is_available, bool);

    /// Set the left-handed configuration of the device.
    ///
    /// The exact behavior is device-dependent. On a mouse and most
    /// pointing devices, left and right buttons are swapped but the
    /// middle button is unmodified. On a touchpad, physical buttons
    /// (if present) are swapped. On a clickpad, the top and bottom
    /// software-emulated buttons are swapped where present, the main
    /// area of the touchpad remains a left button. Tapping and
    /// clickfinger behavior is not affected by this setting.
    ///
    /// Changing the left-handed configuration of a device may not
    /// take effect until all buttons have been logically released.
    pub fn config_left_handed_set(&self, enabled: bool) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_left_handed_set(self.as_raw_mut(), enabled as i32)
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Check if configurable middle button emulation is enabled by
    /// default on this device.
    ///
    /// See [Middle button emulation](https://wayland.freedesktop.org/libinput/doc/latest/middle-button-emulation.html) for details.
    ///
    /// If the device does not have configurable middle button
    /// emulation, this function returns `false`.
    ///
    /// ## Note
    ///
    /// Some devices provide middle mouse button emulation but do not
    /// allow enabling/disabling that emulation. These devices always
    /// return `false`.
    pub fn config_middle_emulation_default_enabled(&self) -> bool {
        match unsafe {
            ffi::libinput_device_config_middle_emulation_get_default_enabled(self.as_raw_mut())
        } {
            ffi::libinput_config_middle_emulation_state_LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED => true,
            ffi::libinput_config_middle_emulation_state_LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED => false,
            _ => panic!("libinput returned invalid 'libinput_config_middle_emulation_state'"),
        }
    }

    /// Check if configurable middle button emulation is enabled on
    /// this device.
    ///
    /// See [Middle button emulation](https://wayland.freedesktop.org/libinput/doc/latest/middle-button-emulation.html)
    /// for details.
    ///
    /// If the device does not have configurable middle button
    /// emulation, this function returns `false`.
    ///
    /// ## Note
    ///
    /// Some devices provide middle mouse button emulation but do not
    /// allow enabling/disabling that emulation. These devices always
    /// return `false`.
    pub fn config_middle_emulation_enabled(&self) -> bool {
        match unsafe { ffi::libinput_device_config_middle_emulation_get_enabled(self.as_raw_mut()) } {
            ffi::libinput_config_middle_emulation_state_LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED => true,
            ffi::libinput_config_middle_emulation_state_LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED => false,
            _ => panic!("libinput returned invalid 'libinput_config_middle_emulation_state'"),
        }
    }

    ffi_func!(
    /// Check if middle mouse button emulation configuration is
    /// available on this device.
    ///
    /// See [Middle button emulation](https://wayland.freedesktop.org/libinput/doc/latest/middle-button-emulation.html)
    /// for details.
    ///
    /// ## Note
    ///
    /// Some devices provide middle mouse button emulation but do not
    /// allow enabling/disabling that emulation. These devices return
    /// `false` in `config_middle_emulation_is_available`.
    pub fn config_middle_emulation_is_available, ffi::libinput_device_config_middle_emulation_is_available, bool);

    /// Enable or disable middle button emulation on this device.
    ///
    /// When enabled, a simultaneous press of the left and right
    /// button generates a middle mouse button event. Releasing the
    /// buttons generates a middle mouse button release, the left and
    /// right button events are discarded otherwise.
    ///
    /// See [Middle button emulation](https://wayland.freedesktop.org/libinput/doc/latest/middle-button-emulation.html)
    /// for details.
    pub fn config_middle_emulation_set_enabled(&self, enabled: bool) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_middle_emulation_set_enabled(
                self.as_raw_mut(),
                if enabled {
                    ffi::libinput_config_middle_emulation_state_LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED
                } else {
                    ffi::libinput_config_middle_emulation_state_LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED
                },
            )
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    ffi_func!(
    /// Get the current rotation of a device in degrees clockwise off
    /// the logical neutral position.
    ///
    /// If this device does not support rotation, the return value is
    /// always 0.
    pub fn config_rotation_angle, ffi::libinput_device_config_rotation_get_angle, u32);
    ffi_func!(
    /// Get the default rotation of a device in degrees clockwise off
    /// the logical neutral position.
    ///
    /// If this device does not support rotation, the return value is
    /// always 0.
    pub fn config_rotation_default_angle, ffi::libinput_device_config_rotation_get_default_angle, u32);
    ffi_func!(
    /// Check whether a device can have a custom rotation applied.
    pub fn config_rotation_is_available, ffi::libinput_device_config_rotation_is_available, bool);

    /// Set the rotation of a device in degrees clockwise off the
    /// logical neutral position.
    ///
    /// Any subsequent motion events are adjusted according to the
    /// given angle.
    ///
    /// The angle has to be in the range of [0, 360] degrees,
    /// otherwise this function returns `DeviceConfigError::Invalid`.
    /// If the angle is a multiple of 360 or negative, the caller
    /// must ensure the correct ranging before calling this function.
    ///
    /// The rotation angle is applied to all motion events emitted by
    /// the device. Thus, rotating the device also changes the angle
    /// required or presented by scrolling, gestures, etc.
    ///
    /// Setting a rotation of 0 degrees on a device that does not
    /// support rotation always succeeds.
    pub fn config_rotation_set_angle(&self, angle: u32) -> DeviceConfigResult {
        match unsafe { ffi::libinput_device_config_rotation_set_angle(self.as_raw_mut(), angle) } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    ffi_func!(
    /// Get the button for the `ScrollMethod::OnButtonDown` method
    /// for this device.
    ///
    /// If `ScrollMethod::OnButtonDown` scroll method is not
    /// supported, or no button is set, this function returns 0.
    ///
    /// ## Note
    ///
    /// The return value is independent of the currently selected
    /// scroll-method. For button scrolling to activate, a device
    /// must have the `ScrollMethod::OnButtonDown` method enabled,
    /// and a non-zero button set as scroll button.
    pub fn config_scroll_button, ffi::libinput_device_config_scroll_get_button, u32);
    ffi_func!(
    /// Get the default button for the `ScrollMethod::OnButtonDown`
    /// method for this device.
    ///
    /// If `ScrollMethod::OnButtonDown` scroll method is not
    /// supported, or no default button is set,
    /// this function returns 0.
    pub fn config_scroll_default_button, ffi::libinput_device_config_scroll_get_default_button, u32);

    /// Get the default scroll method for this device.
    ///
    /// The method defines when to generate scroll axis events
    /// instead of pointer motion events.
    ///
    /// A return value of `None` means the scroll method is not known
    pub fn config_scroll_default_method(&self) -> Option<ScrollMethod> {
        match unsafe { ffi::libinput_device_config_scroll_get_default_method(self.as_raw_mut()) } {
            ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_NO_SCROLL => {
                Some(ScrollMethod::NoScroll)
            }
            ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_2FG => {
                Some(ScrollMethod::TwoFinger)
            }
            ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_EDGE => {
                Some(ScrollMethod::Edge)
            }
            ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN => {
                Some(ScrollMethod::OnButtonDown)
            }
            _x => {
                #[cfg(feature = "log")]
                log::warn!(
                    "Unknown ScrollMethod ({}). Unsupported libinput version?",
                    _x
                );
                None
            }
        }
    }

    /// Get the scroll method for this device.
    ///
    /// The method defines when to generate scroll axis events
    /// instead of pointer motion events.
    ///
    /// A return value of `None` means the scroll method is not known
    pub fn config_scroll_method(&self) -> Option<ScrollMethod> {
        match unsafe { ffi::libinput_device_config_scroll_get_method(self.as_raw_mut()) } {
            ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_NO_SCROLL => {
                Some(ScrollMethod::NoScroll)
            }
            ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_2FG => {
                Some(ScrollMethod::TwoFinger)
            }
            ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_EDGE => {
                Some(ScrollMethod::Edge)
            }
            ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN => {
                Some(ScrollMethod::OnButtonDown)
            }
            _ => panic!("libinput returned invalid 'libinput_config_scroll_method'"),
        }
    }

    /// Check which scroll methods a device supports.
    ///
    /// The method defines when to generate scroll axis events
    /// instead of pointer motion events.
    pub fn config_scroll_methods(&self) -> Vec<ScrollMethod> {
        let mut methods = Vec::new();
        let bitmask = unsafe { ffi::libinput_device_config_scroll_get_methods(self.as_raw_mut()) };
        if bitmask & ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_NO_SCROLL as u32 != 0
        {
            methods.push(ScrollMethod::NoScroll);
        }
        if bitmask & ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_2FG as u32 != 0 {
            methods.push(ScrollMethod::TwoFinger);
        }
        if bitmask & ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_EDGE as u32 != 0 {
            methods.push(ScrollMethod::Edge);
        }
        if bitmask & ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN as u32
            != 0
        {
            methods.push(ScrollMethod::OnButtonDown);
        }
        methods
    }

    /// Set the scroll method for this device.
    ///
    /// The method defines when to generate scroll axis events
    /// instead of pointer motion events.
    ///
    /// ## Note
    ///
    /// Setting `ScrollMethod::OnButtonDown` enables the scroll
    /// method, but scrolling is only activated when the configured
    /// button is held down. If no button is set, i.e.
    /// `config_scroll_button` returns 0, scrolling cannot activate.
    pub fn config_scroll_set_method(&mut self, method: ScrollMethod) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_scroll_set_method(
                self.as_raw_mut(),
                match method {
                    ScrollMethod::NoScroll => {
                        ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_NO_SCROLL
                    }
                    ScrollMethod::TwoFinger => {
                        ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_2FG
                    }
                    ScrollMethod::Edge => {
                        ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_EDGE
                    }
                    ScrollMethod::OnButtonDown => {
                        ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN
                    }
                },
            )
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    ffi_func!(
    /// Get the default mode for scrolling on this device.
    pub fn config_scroll_default_natural_scroll_enabled, ffi::libinput_device_config_scroll_get_default_natural_scroll_enabled, bool);
    ffi_func!(
    /// Get the current mode for scrolling on this device.
    pub fn config_scroll_natural_scroll_enabled, ffi::libinput_device_config_scroll_get_natural_scroll_enabled, bool);
    ffi_func!(
    /// Return non-zero if the device supports "natural scrolling".
    ///
    /// In traditional scroll mode, the movement of fingers on a
    /// touchpad when scrolling matches the movement of the scroll
    /// bars. When the fingers move down, the scroll bar moves down,
    /// a line of text on the screen moves towards the upper end of
    /// the screen. This also matches scroll wheels on mice (wheel
    /// down, content moves up).
    ///
    /// Natural scrolling is the term coined by Apple for inverted
    /// scrolling. In this mode, the effect of scrolling movement of
    /// fingers on a touchpad resemble physical manipulation of
    /// paper. When the fingers move down, a line of text on the
    /// screen moves down (scrollbars move up). This is the opposite
    /// of scroll wheels on mice.
    ///
    /// A device supporting natural scrolling can be switched between
    /// traditional scroll mode and natural scroll mode.
    pub fn config_scroll_has_natural_scroll, ffi::libinput_device_config_scroll_has_natural_scroll, bool);

    /// Enable or disable natural scrolling on the device.
    pub fn config_scroll_set_natural_scroll_enabled(
        &mut self,
        enabled: bool,
    ) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_scroll_set_natural_scroll_enabled(
                self.as_raw_mut(),
                enabled as i32,
            )
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Set the button for the `ScrollMethod::OnButtonDown` method
    /// for this device.
    ///
    /// When the current scroll method is set to
    /// `ScrollMethod::OnButtonDown`, no button press/release events
    /// will be send for the configured button.
    ///
    /// When the configured button is pressed, any motion events
    /// along a scroll-capable axis are turned into scroll axis
    /// events.
    ///
    /// ## Note
    ///
    /// Setting the button does not change the scroll method. To
    /// change the scroll method call `config_scroll_set_method`.
    /// If the button is 0, button scrolling is effectively disabled.
    pub fn config_scroll_set_button(&mut self, button: u32) -> DeviceConfigResult {
        match unsafe { ffi::libinput_device_config_scroll_set_button(self.as_raw_mut(), button) } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Get the current scroll button lock state
    ///
    /// If `ScrollMethod::OnButtonDown` is not supported, or no button is set,
    /// this functions returns `Disabled`.
    ///
    /// ## Note
    ///
    /// The return value is independent of the currently selected scroll-method.
    /// For the scroll button lock to activate, a device must have the
    /// `ScrollMethod::OnButtonDown` enabled, and a non-zero button set as scroll button.
    #[cfg(feature = "libinput_1_15")]
    pub fn config_scroll_button_lock(&self) -> ScrollButtonLockState {
        match unsafe { ffi::libinput_device_config_scroll_get_button_lock(self.as_raw() as *mut _) } {
            ffi::libinput_config_scroll_button_lock_state_LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED => ScrollButtonLockState::Disabled,
            ffi::libinput_config_scroll_button_lock_state_LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED => ScrollButtonLockState::Enabled,
            _ => panic!("libinput returned invalid libinput_config_scroll_button_lock_state"),
        }
    }

    /// Get the default scroll button lock state
    ///
    /// If `ScrollMethod::OnButtonDown` is not supported, or no button is set,
    /// this functions returns `Disabled`.
    #[cfg(feature = "libinput_1_15")]
    pub fn config_scroll_default_button_lock(&self) -> ScrollButtonLockState {
        match unsafe { ffi::libinput_device_config_scroll_get_default_button_lock(self.as_raw() as *mut _) } {
            ffi::libinput_config_scroll_button_lock_state_LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED => ScrollButtonLockState::Disabled,
            ffi::libinput_config_scroll_button_lock_state_LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED => ScrollButtonLockState::Enabled,
            _ => panic!("libinput returned invalid libinput_config_scroll_button_lock_state"),
        }
    }

    /// Set the scroll button lock.
    ///
    /// If the state is `Disabled` the button must physically be held down for
    /// button scrolling to work. If the state is `Enabled`, the button is considered
    /// logically down after the first press and release sequence, and logically
    /// up after the second press and release sequence.
    #[cfg(feature = "libinput_1_15")]
    pub fn config_scroll_set_button_lock(
        &mut self,
        state: ScrollButtonLockState,
    ) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_scroll_set_button_lock(self.as_raw_mut(),
            match state {
                ScrollButtonLockState::Enabled => ffi::libinput_config_scroll_button_lock_state_LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED,
                ScrollButtonLockState::Disabled => ffi::libinput_config_scroll_button_lock_state_LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED,
            }
        )
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Get the send-event mode for this device.
    ///
    /// The mode defines when the device processes and sends events
    /// to the caller.
    ///
    /// If a caller enables the bits for multiple modes, some of
    /// which are subsets of another mode libinput may drop the bits
    /// that are subsets. In other words, don't expect
    /// `config_send_events_mode` to always return exactly the same
    /// as passed into `config_send_events_set_mode`.
    pub fn config_send_events_mode(&self) -> SendEventsMode {
        SendEventsMode::from_bits_truncate(unsafe {
            ffi::libinput_device_config_send_events_get_mode(self.as_raw_mut())
        })
    }

    /// Return the possible send-event modes for this device.
    ///
    /// These modes define when a device may process and send events.
    pub fn config_send_events_modes(&self) -> SendEventsMode {
        SendEventsMode::from_bits_truncate(unsafe {
            ffi::libinput_device_config_send_events_get_modes(self.as_raw_mut())
        })
    }

    /// Set the send-event mode for this device.
    ///
    /// The mode defines when the device processes and sends events
    /// to the caller.
    ///
    /// The selected mode may not take effect immediately. Events
    /// already received and processed from this device are
    /// unaffected and will be passed to the caller on the next call
    /// to `<Libinput as Iterator>::next()`.
    ///
    /// If the mode is a mixture of `SendEventsMode`s, the device may
    /// wait for or generate events until it is in a neutral state.
    /// For example, this may include waiting for or generating
    /// button release events.
    ///
    /// If the device is already suspended, this function does
    /// nothing and returns success. Changing the send-event mode on
    /// a device that has been removed is permitted.
    pub fn config_send_events_set_mode(&self, mode: SendEventsMode) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_send_events_set_mode(self.as_raw_mut(), mode.bits())
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Get the finger number to button number mapping for
    /// tap-to-click.
    ///
    /// The return value for a device that does not support tapping
    /// is always `TapButtonMap::LeftRightMiddle`.
    ///
    /// This will return `None` for devices
    /// where `config_tap_finger_count` returns 0.
    pub fn config_tap_button_map(&self) -> Option<TapButtonMap> {
        if self.config_tap_finger_count() == 0 {
            None
        } else {
            match unsafe { ffi::libinput_device_config_tap_get_button_map(self.as_raw_mut()) } {
                ffi::libinput_config_tap_button_map_LIBINPUT_CONFIG_TAP_MAP_LRM => {
                    Some(TapButtonMap::LeftRightMiddle)
                }
                ffi::libinput_config_tap_button_map_LIBINPUT_CONFIG_TAP_MAP_LMR => {
                    Some(TapButtonMap::LeftMiddleRight)
                }
                _ => panic!("libinput returned invalid 'libinput_config_tap_button_map'"),
            }
        }
    }

    /// Get the default finger number to button number mapping for
    /// tap-to-click.
    ///
    /// The return value for a device that does not support tapping
    /// is always `TapButtonMap::LeftRightMiddle`.
    ///
    /// This will return `None` for devices
    /// where `config_tap_finger_count` returns 0.
    pub fn config_tap_default_button_map(&self) -> Option<TapButtonMap> {
        if self.config_tap_finger_count() == 0 {
            None
        } else {
            match unsafe {
                ffi::libinput_device_config_tap_get_default_button_map(self.as_raw_mut())
            } {
                ffi::libinput_config_tap_button_map_LIBINPUT_CONFIG_TAP_MAP_LRM => {
                    Some(TapButtonMap::LeftRightMiddle)
                }
                ffi::libinput_config_tap_button_map_LIBINPUT_CONFIG_TAP_MAP_LMR => {
                    Some(TapButtonMap::LeftMiddleRight)
                }
                _ => panic!("libinput returned invalid 'libinput_config_tap_button_map'"),
            }
        }
    }

    /// Return whether tap-and-drag is enabled or disabled by default
    /// on this device.
    pub fn config_tap_default_drag_enabled(&self) -> bool {
        match unsafe { ffi::libinput_device_config_tap_get_default_drag_enabled(self.as_raw_mut()) }
        {
            ffi::libinput_config_drag_state_LIBINPUT_CONFIG_DRAG_ENABLED => true,
            ffi::libinput_config_drag_state_LIBINPUT_CONFIG_DRAG_DISABLED => false,
            _ => panic!("libinput returned invalid 'libinput_config_drag_state'"),
        }
    }

    /// Check if drag-lock during tapping is enabled by default on
    /// this device.
    ///
    /// If the device does not support tapping, this function always
    /// returns `false`.
    ///
    /// Drag lock may be enabled by default even when tapping is
    /// disabled by default.
    pub fn config_tap_default_drag_lock_enabled(&self) -> DragLockState {
        match unsafe {
            ffi::libinput_device_config_tap_get_default_drag_lock_enabled(self.as_raw_mut())
        } {
            ffi::libinput_config_drag_lock_state_LIBINPUT_CONFIG_DRAG_LOCK_DISABLED => {
                DragLockState::Disabled
            }
            // legacy spelling for LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT
            #[cfg(not(feature = "libinput_1_27"))]
            ffi::libinput_config_drag_lock_state_LIBINPUT_CONFIG_DRAG_LOCK_ENABLED => {
                DragLockState::EnabledTimeout
            }
            #[cfg(feature = "libinput_1_27")]
            ffi::libinput_config_drag_lock_state_LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT => {
                DragLockState::EnabledTimeout
            }
            #[cfg(feature = "libinput_1_27")]
            ffi::libinput_config_drag_lock_state_LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY => {
                DragLockState::EnabledSticky
            }
            _ => panic!("libinput returned invalid 'libinput_config_drag_lock_state'"),
        }
    }

    /// Return the default setting for whether tap-to-click is
    /// enabled on this device.
    pub fn config_tap_default_enabled(&self) -> bool {
        match unsafe { ffi::libinput_device_config_tap_get_default_enabled(self.as_raw_mut()) } {
            ffi::libinput_config_tap_state_LIBINPUT_CONFIG_TAP_ENABLED => true,
            ffi::libinput_config_tap_state_LIBINPUT_CONFIG_TAP_DISABLED => false,
            _ => panic!("libinput returned invalid 'libinput_config_tap_state'"),
        }
    }

    /// Return whether tap-and-drag is enabled or disabled on this
    /// device.
    pub fn config_tap_drag_enabled(&self) -> bool {
        match unsafe { ffi::libinput_device_config_tap_get_drag_enabled(self.as_raw_mut()) } {
            ffi::libinput_config_drag_state_LIBINPUT_CONFIG_DRAG_ENABLED => true,
            ffi::libinput_config_drag_state_LIBINPUT_CONFIG_DRAG_DISABLED => false,
            _ => panic!("libinput returned invalid 'libinput_config_drag_state'"),
        }
    }

    /// Check if drag-lock during tapping is enabled on this device.
    ///
    /// If the device does not support tapping, this function always
    /// returns `false`.
    ///
    /// Drag lock may be enabled even when tapping is disabled.
    pub fn config_tap_drag_lock_enabled(&self) -> bool {
        match unsafe { ffi::libinput_device_config_tap_get_drag_lock_enabled(self.as_raw_mut()) } {
            ffi::libinput_config_drag_lock_state_LIBINPUT_CONFIG_DRAG_LOCK_ENABLED => true,
            ffi::libinput_config_drag_lock_state_LIBINPUT_CONFIG_DRAG_LOCK_DISABLED => false,
            _ => panic!("libinput returned invalid 'libinput_config_drag_lock_state'"),
        }
    }

    /// Check if tap-to-click is enabled on this device.
    ///
    /// If the device does not support tapping, this function always
    /// returns `false`.
    pub fn config_tap_enabled(&self) -> bool {
        match unsafe { ffi::libinput_device_config_tap_get_enabled(self.as_raw_mut()) } {
            ffi::libinput_config_tap_state_LIBINPUT_CONFIG_TAP_ENABLED => true,
            ffi::libinput_config_tap_state_LIBINPUT_CONFIG_TAP_DISABLED => false,
            _ => panic!("libinput returned invalid 'libinput_config_tap_state'"),
        }
    }

    ffi_func!(
    /// Check if the device supports tap-to-click and how many
    /// fingers can be used for tapping.
    ///
    /// See `config_tap_set_enabled` for more information.
    pub fn config_tap_finger_count, ffi::libinput_device_config_tap_get_finger_count, u32);

    #[cfg(feature = "libinput_1_28")]
    ffi_func!(
    /// Returns the maximum number of fingers available for 3-finger dragging.
    pub fn config_3fg_drag_get_finger_count, ffi::libinput_device_config_3fg_drag_get_finger_count, u32);

    /// Enable or disable 3-finger drag on this device.
    ///
    /// When enabled, three fingers down will result in a button down event,
    /// subsequent finger motion triggers a drag. The button is released shortly
    /// after all fingers are logically up.
    ///
    /// See [Three-finger drag](https://wayland.freedesktop.org/libinput/doc/latest/configuration.html#three-finger-drag) for details.
    #[cfg(feature = "libinput_1_28")]
    pub fn config_3fg_drag_set_enabled(&self, state: ThreeFingerDragState) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_3fg_drag_set_enabled(
                self.as_raw_mut(),
                match state {
                    ThreeFingerDragState::Disabled => {
                        ffi::libinput_config_3fg_drag_state_LIBINPUT_CONFIG_3FG_DRAG_DISABLED
                    }
                    ThreeFingerDragState::EnabledThreeFinger => {
                        ffi::libinput_config_3fg_drag_state_LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG
                    }
                    ThreeFingerDragState::EnabledFourFinger => {
                        ffi::libinput_config_3fg_drag_state_LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG
                    }
                },
            )
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Return whether 3-finger drag is enabled or disabled on this device.
    #[cfg(feature = "libinput_1_28")]
    pub fn config_3fg_drag_get_enabled(&self) -> ThreeFingerDragState {
        match unsafe { ffi::libinput_device_config_3fg_drag_get_enabled(self.as_raw_mut()) } {
            ffi::libinput_config_3fg_drag_state_LIBINPUT_CONFIG_3FG_DRAG_DISABLED => {
                ThreeFingerDragState::Disabled
            }
            ffi::libinput_config_3fg_drag_state_LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG => {
                ThreeFingerDragState::EnabledThreeFinger
            }
            ffi::libinput_config_3fg_drag_state_LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG => {
                ThreeFingerDragState::EnabledFourFinger
            }
            _ => panic!("libinput returned invalid 'libinput_config_3fg_drag_state'"),
        }
    }

    /// Return whether 3-finger drag is enabled or disabled by default on this device.
    #[cfg(feature = "libinput_1_28")]
    pub fn config_3fg_drag_get_default_enabled(&self) -> ThreeFingerDragState {
        match unsafe { ffi::libinput_device_config_3fg_drag_get_default_enabled(self.as_raw_mut()) }
        {
            ffi::libinput_config_3fg_drag_state_LIBINPUT_CONFIG_3FG_DRAG_DISABLED => {
                ThreeFingerDragState::Disabled
            }
            ffi::libinput_config_3fg_drag_state_LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG => {
                ThreeFingerDragState::EnabledThreeFinger
            }
            ffi::libinput_config_3fg_drag_state_LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG => {
                ThreeFingerDragState::EnabledFourFinger
            }
            _ => panic!("libinput returned invalid 'libinput_config_3fg_drag_state'"),
        }
    }

    /// Set the finger number to button number mapping for
    /// tap-to-click.
    ///
    /// The default mapping on most devices is to have a 1, 2 and 3
    /// finger tap to map to the left, right and middle button,
    /// respectively. A device may permit changing the button mapping
    /// but disallow specific maps. In this case
    /// `DeviceConfigError::Disabled` is returned, the caller is
    /// expected to handle this case correctly.
    ///
    /// Changing the button mapping may not take effect immediately,
    /// the device may wait until it is in a neutral state before
    /// applying any changes.
    ///
    /// The mapping may be changed when tap-to-click is disabled. The
    /// new mapping takes effect when tap-to-click is enabled in the
    /// future.
    ///
    /// ## Note
    ///
    /// This will return `None` for devices where
    /// `config_tap_finger_count` returns 0.
    pub fn config_tap_set_button_map(&mut self, map: TapButtonMap) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_tap_set_button_map(
                self.as_raw_mut(),
                match map {
                    TapButtonMap::LeftRightMiddle => {
                        ffi::libinput_config_tap_button_map_LIBINPUT_CONFIG_TAP_MAP_LRM
                    }
                    TapButtonMap::LeftMiddleRight => {
                        ffi::libinput_config_tap_button_map_LIBINPUT_CONFIG_TAP_MAP_LMR
                    }
                },
            )
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Enable or disable tap-and-drag on this device.
    ///
    /// When enabled, a single-finger tap immediately followed by a
    /// finger down results in a button down event, subsequent finger
    /// motion thus triggers a drag. The button is released on finger
    /// up.
    /// See [Tap-and-drag](https://wayland.freedesktop.org/libinput/doc/latest/tapping.html#tapndrag)
    /// for more details.
    pub fn config_tap_set_drag_enabled(&mut self, enabled: bool) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_tap_set_drag_enabled(
                self.as_raw_mut(),
                if enabled {
                    ffi::libinput_config_drag_state_LIBINPUT_CONFIG_DRAG_ENABLED
                } else {
                    ffi::libinput_config_drag_state_LIBINPUT_CONFIG_DRAG_DISABLED
                },
            )
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Enable or disable drag-lock during tapping on this device.
    ///
    /// When enabled, a finger may be lifted and put back on the
    /// touchpad within a timeout and the drag process continues.
    /// When disabled, lifting the finger during a tap-and-drag will
    /// immediately stop the drag.
    /// See [Tap-and-drag](https://wayland.freedesktop.org/libinput/doc/latest/tapping.html#tapndrag)
    /// for details.
    ///
    /// Enabling drag lock on a device that has tapping disabled is
    /// permitted, but has no effect until tapping is enabled.
    pub fn config_tap_set_drag_lock_enabled(&mut self, state: DragLockState) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_tap_set_drag_lock_enabled(
                self.as_raw_mut(),
                match state {
                    DragLockState::Disabled => {
                        ffi::libinput_config_drag_lock_state_LIBINPUT_CONFIG_DRAG_LOCK_DISABLED
                    }
                    #[cfg(not(feature = "libinput_1_27"))]
                    DragLockState::EnabledTimeout => {
                        // legacy spelling for LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT
                        ffi::libinput_config_drag_lock_state_LIBINPUT_CONFIG_DRAG_LOCK_ENABLED
                    }
                    #[cfg(feature = "libinput_1_27")]
                    DragLockState::EnabledTimeout => {
                        ffi::libinput_config_drag_lock_state_LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT
                    }
                    #[cfg(feature = "libinput_1_27")]
                    DragLockState::EnabledSticky => {
                        ffi::libinput_config_drag_lock_state_LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY
                    }
                },
            )
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }

    /// Enable or disable tap-to-click on this device, with a default
    /// mapping of 1, 2, 3 finger tap mapping to left, right, middle
    /// click, respectively.
    ///
    /// Tapping is limited by the number of simultaneous touches
    /// supported by the device, see `config_tap_finger_count`.
    pub fn config_tap_set_enabled(&mut self, enabled: bool) -> DeviceConfigResult {
        match unsafe {
            ffi::libinput_device_config_tap_set_enabled(
                self.as_raw_mut(),
                if enabled {
                    ffi::libinput_config_tap_state_LIBINPUT_CONFIG_TAP_ENABLED
                } else {
                    ffi::libinput_config_tap_state_LIBINPUT_CONFIG_TAP_DISABLED
                },
            )
        } {
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => Ok(()),
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
                Err(DeviceConfigError::Unsupported)
            }
            ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_INVALID => {
                Err(DeviceConfigError::Invalid)
            }
            _ => panic!("libinput returned invalid 'libinput_config_status'"),
        }
    }
}
