/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2016 Martin Gräßlin SPDX-License-Identifier: GPL-2.0-or-later */ #include "device.h" #include "config-kwin.h" #include "core/output.h" #include "core/outputbackend.h" #include "libinput_logging.h" #include "main.h" #include "mousebuttons.h" #include "pointer_input.h" #include #include #include #include #include namespace KWin { namespace LibInput { static const QRectF s_identityRect = QRectF(0, 0, 1, 1); TabletTool::TabletTool(libinput_tablet_tool *handle) : m_handle(libinput_tablet_tool_ref(handle)) { } TabletTool::~TabletTool() { libinput_tablet_tool_unref(m_handle); } libinput_tablet_tool *TabletTool::handle() const { return m_handle; } quint64 TabletTool::serialId() const { return libinput_tablet_tool_get_serial(m_handle); } quint64 TabletTool::uniqueId() const { return libinput_tablet_tool_get_tool_id(m_handle); } TabletTool::Type TabletTool::type() const { switch (libinput_tablet_tool_get_type(m_handle)) { case LIBINPUT_TABLET_TOOL_TYPE_PEN: return Type::Pen; case LIBINPUT_TABLET_TOOL_TYPE_ERASER: return Type::Eraser; case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: return Type::Brush; case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: return Type::Pencil; case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: return Type::Airbrush; case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: return Type::Mouse; case LIBINPUT_TABLET_TOOL_TYPE_LENS: return Type::Lens; case LIBINPUT_TABLET_TOOL_TYPE_TOTEM: return Type::Totem; default: return Type(); } } QList TabletTool::capabilities() const { QList capabilities; if (libinput_tablet_tool_has_pressure(m_handle)) { capabilities << Capability::Pressure; } if (libinput_tablet_tool_has_distance(m_handle)) { capabilities << Capability::Distance; } if (libinput_tablet_tool_has_rotation(m_handle)) { capabilities << Capability::Rotation; } if (libinput_tablet_tool_has_tilt(m_handle)) { capabilities << Capability::Tilt; } if (libinput_tablet_tool_has_slider(m_handle)) { capabilities << Capability::Slider; } if (libinput_tablet_tool_has_wheel(m_handle)) { capabilities << Capability::Wheel; } return capabilities; } static bool checkAlphaNumericKeyboard(libinput_device *device) { for (uint i = KEY_1; i <= KEY_0; i++) { if (libinput_device_keyboard_has_key(device, i) == 0) { return false; } } for (uint i = KEY_Q; i <= KEY_P; i++) { if (libinput_device_keyboard_has_key(device, i) == 0) { return false; } } for (uint i = KEY_A; i <= KEY_L; i++) { if (libinput_device_keyboard_has_key(device, i) == 0) { return false; } } for (uint i = KEY_Z; i <= KEY_M; i++) { if (libinput_device_keyboard_has_key(device, i) == 0) { return false; } } return true; } enum class ConfigKey { Enabled, DisableEventsOnExternalMouse, LeftHanded, DisableWhileTyping, PointerAcceleration, PointerAccelerationProfile, TapToClick, LmrTapButtonMap, TapAndDrag, TapDragLock, MiddleButtonEmulation, NaturalScroll, ScrollMethod, ScrollButton, ClickMethod, ScrollFactor, Orientation, Calibration, OutputName, OutputArea, MapToWorkspace, TabletToolPressureCurve, TabletToolPressureRangeMin, TabletToolPressureRangeMax, InputArea, }; struct ConfigDataBase { ConfigDataBase(const QByteArray &_key) : key(_key) { } virtual ~ConfigDataBase() = default; QByteArray key; virtual void read(Device * /*device*/, const KConfigGroup & /*values*/) const = 0; }; template struct ConfigData : public ConfigDataBase { using SetterFunction = std::function; using DefaultValueFunction = std::function; explicit ConfigData(const QByteArray &_key, const SetterFunction &_setter, const DefaultValueFunction &_defaultValue) : ConfigDataBase(_key) , setterFunction(_setter) , defaultValueFunction(_defaultValue) { } void read(Device *device, const KConfigGroup &values) const override { if (!setterFunction || !defaultValueFunction) { return; } setterFunction(device, values.readEntry(key.constData(), defaultValueFunction(device))); } SetterFunction setterFunction; DefaultValueFunction defaultValueFunction; }; // Template specializations for some specific config types that can't be handled // through plain readEntry. // // This uses tagged types to avoid specialising the general type since we // directly call the getters/setters. using DeviceOrientation = Qt::ScreenOrientation; template<> struct ConfigData : public ConfigDataBase { explicit ConfigData() : ConfigDataBase(QByteArrayLiteral("Orientation")) { } void read(Device *device, const KConfigGroup &values) const override { int defaultValue = device->defaultOrientation(); device->setOrientation(static_cast(values.readEntry(key.constData(), defaultValue))); } }; using CalibrationMatrix = QMatrix4x4; template<> struct ConfigData : public ConfigDataBase { explicit ConfigData() : ConfigDataBase(QByteArrayLiteral("CalibrationMatrix")) { } void read(Device *device, const KConfigGroup &values) const override { device->setCalibrationMatrix(values.readEntry(key.constData(), device->defaultCalibrationMatrix())); } }; static const QMap> s_configData{ {ConfigKey::Enabled, std::make_shared>(QByteArrayLiteral("Enabled"), &Device::setEnabled, &Device::isEnabledByDefault)}, {ConfigKey::DisableEventsOnExternalMouse, std::make_shared>(QByteArrayLiteral("DisableEventsOnExternalMouse"), &Device::setDisableEventsOnExternalMouse, &Device::disableEventsOnExternalMouseEnabledByDefault)}, {ConfigKey::LeftHanded, std::make_shared>(QByteArrayLiteral("LeftHanded"), &Device::setLeftHanded, &Device::leftHandedEnabledByDefault)}, {ConfigKey::DisableWhileTyping, std::make_shared>(QByteArrayLiteral("DisableWhileTyping"), &Device::setDisableWhileTyping, &Device::disableWhileTypingEnabledByDefault)}, {ConfigKey::PointerAcceleration, std::make_shared>(QByteArrayLiteral("PointerAcceleration"), &Device::setPointerAccelerationFromString, &Device::defaultPointerAccelerationToString)}, {ConfigKey::PointerAccelerationProfile, std::make_shared>(QByteArrayLiteral("PointerAccelerationProfile"), &Device::setPointerAccelerationProfileFromInt, &Device::defaultPointerAccelerationProfileToInt)}, {ConfigKey::TapToClick, std::make_shared>(QByteArrayLiteral("TapToClick"), &Device::setTapToClick, &Device::tapToClickEnabledByDefault)}, {ConfigKey::TapAndDrag, std::make_shared>(QByteArrayLiteral("TapAndDrag"), &Device::setTapAndDrag, &Device::tapAndDragEnabledByDefault)}, {ConfigKey::TapDragLock, std::make_shared>(QByteArrayLiteral("TapDragLock"), &Device::setTapDragLock, &Device::tapDragLockEnabledByDefault)}, {ConfigKey::MiddleButtonEmulation, std::make_shared>(QByteArrayLiteral("MiddleButtonEmulation"), &Device::setMiddleEmulation, &Device::middleEmulationEnabledByDefault)}, {ConfigKey::LmrTapButtonMap, std::make_shared>(QByteArrayLiteral("LmrTapButtonMap"), &Device::setLmrTapButtonMap, &Device::lmrTapButtonMapEnabledByDefault)}, {ConfigKey::NaturalScroll, std::make_shared>(QByteArrayLiteral("NaturalScroll"), &Device::setNaturalScroll, &Device::naturalScrollEnabledByDefault)}, {ConfigKey::ScrollMethod, std::make_shared>(QByteArrayLiteral("ScrollMethod"), &Device::activateScrollMethodFromInt, &Device::defaultScrollMethodToInt)}, {ConfigKey::ScrollButton, std::make_shared>(QByteArrayLiteral("ScrollButton"), &Device::setScrollButton, &Device::defaultScrollButton)}, {ConfigKey::ClickMethod, std::make_shared>(QByteArrayLiteral("ClickMethod"), &Device::setClickMethodFromInt, &Device::defaultClickMethodToInt)}, {ConfigKey::ScrollFactor, std::make_shared>(QByteArrayLiteral("ScrollFactor"), &Device::setScrollFactor, &Device::scrollFactorDefault)}, {ConfigKey::Orientation, std::make_shared>()}, {ConfigKey::Calibration, std::make_shared>()}, {ConfigKey::TabletToolPressureCurve, std::make_shared>(QByteArrayLiteral("TabletToolPressureCurve"), &Device::setPressureCurve, &Device::defaultPressureCurve)}, {ConfigKey::OutputName, std::make_shared>(QByteArrayLiteral("OutputName"), &Device::setOutputName, &Device::defaultOutputName)}, {ConfigKey::OutputArea, std::make_shared>(QByteArrayLiteral("OutputArea"), &Device::setOutputArea, &Device::defaultOutputArea)}, {ConfigKey::MapToWorkspace, std::make_shared>(QByteArrayLiteral("MapToWorkspace"), &Device::setMapToWorkspace, &Device::defaultMapToWorkspace)}, {ConfigKey::TabletToolPressureRangeMin, std::make_shared>(QByteArrayLiteral("TabletToolPressureRangeMin"), &Device::setPressureRangeMin, &Device::defaultPressureRangeMin)}, {ConfigKey::TabletToolPressureRangeMax, std::make_shared>(QByteArrayLiteral("TabletToolPressureRangeMax"), &Device::setPressureRangeMax, &Device::defaultPressureRangeMax)}, {ConfigKey::InputArea, std::make_shared>(QByteArrayLiteral("InputArea"), &Device::setInputArea, &Device::defaultInputArea)}, }; namespace { QMatrix4x4 getMatrix(libinput_device *device, std::function getter) { float matrix[6]; if (!getter(device, matrix)) { return {}; } return QMatrix4x4{ matrix[0], matrix[1], matrix[2], 0.0f, matrix[3], matrix[4], matrix[5], 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; } bool setOrientedCalibrationMatrix(libinput_device *device, QMatrix4x4 matrix, Qt::ScreenOrientation orientation) { // 90 deg cw static const QMatrix4x4 portraitMatrix{ 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; // 180 deg cw static const QMatrix4x4 invertedLandscapeMatrix{ -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; // 270 deg cw static const QMatrix4x4 invertedPortraitMatrix{ 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; switch (orientation) { case Qt::PortraitOrientation: matrix *= portraitMatrix; break; case Qt::InvertedLandscapeOrientation: matrix *= invertedLandscapeMatrix; break; case Qt::InvertedPortraitOrientation: matrix *= invertedPortraitMatrix; break; case Qt::PrimaryOrientation: case Qt::LandscapeOrientation: default: break; } float data[6]{matrix(0, 0), matrix(0, 1), matrix(0, 2), matrix(1, 0), matrix(1, 1), matrix(1, 2)}; return libinput_device_config_calibration_set_matrix(device, data) == LIBINPUT_CONFIG_STATUS_SUCCESS; } } Device::Device(libinput_device *device, QObject *parent) : InputDevice(parent) , m_device(device) , m_keyboard(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_KEYBOARD)) , m_pointer(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_POINTER)) , m_touch(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_TOUCH)) , m_tabletTool(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_TABLET_TOOL)) , m_tabletPad(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_TABLET_PAD)) , m_supportsGesture(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_GESTURE)) , m_switch(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_SWITCH)) , m_lidSwitch(m_switch ? libinput_device_switch_has_switch(m_device, LIBINPUT_SWITCH_LID) : false) , m_tabletSwitch(m_switch ? libinput_device_switch_has_switch(m_device, LIBINPUT_SWITCH_TABLET_MODE) : false) , m_touchpad(m_pointer && udev_device_get_property_value(libinput_device_get_udev_device(m_device), "ID_INPUT_TOUCHPAD")) , m_name(QString::fromLocal8Bit(libinput_device_get_name(m_device))) , m_sysName(QString::fromLocal8Bit(libinput_device_get_sysname(m_device))) , m_sysPath(QString::fromLocal8Bit(udev_device_get_syspath(libinput_device_get_udev_device(m_device)))) , m_outputName(QString::fromLocal8Bit(libinput_device_get_output_name(m_device))) , m_product(libinput_device_get_id_product(m_device)) , m_vendor(libinput_device_get_id_vendor(m_device)) , m_tapFingerCount(libinput_device_config_tap_get_finger_count(m_device)) , m_defaultTapButtonMap(libinput_device_config_tap_get_default_button_map(m_device)) , m_tapButtonMap(libinput_device_config_tap_get_button_map(m_device)) , m_tapToClickEnabledByDefault(true) , m_tapToClick(libinput_device_config_tap_get_enabled(m_device)) , m_tapAndDragEnabledByDefault(true) , m_tapAndDrag(libinput_device_config_tap_get_drag_enabled(m_device)) , m_tapDragLockEnabledByDefault(libinput_device_config_tap_get_default_drag_lock_enabled(m_device)) , m_tapDragLock(libinput_device_config_tap_get_drag_lock_enabled(m_device)) , m_supportsDisableWhileTyping(libinput_device_config_dwt_is_available(m_device)) , m_supportsPointerAcceleration(libinput_device_config_accel_is_available(m_device)) , m_supportsLeftHanded(libinput_device_config_left_handed_is_available(m_device)) , m_supportsCalibrationMatrix(libinput_device_config_calibration_has_matrix(m_device)) , m_supportsDisableEvents(libinput_device_config_send_events_get_modes(m_device) & LIBINPUT_CONFIG_SEND_EVENTS_DISABLED) , m_supportsDisableEventsOnExternalMouse(libinput_device_config_send_events_get_modes(m_device) & LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE) , m_supportsMiddleEmulation(libinput_device_config_middle_emulation_is_available(m_device)) , m_supportsNaturalScroll(libinput_device_config_scroll_has_natural_scroll(m_device)) , m_supportedScrollMethods(libinput_device_config_scroll_get_methods(m_device)) , m_leftHandedEnabledByDefault(libinput_device_config_left_handed_get_default(m_device)) , m_middleEmulationEnabledByDefault(libinput_device_config_middle_emulation_get_default_enabled(m_device) == LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED) , m_naturalScrollEnabledByDefault(libinput_device_config_scroll_get_default_natural_scroll_enabled(m_device)) , m_defaultScrollMethod(libinput_device_config_scroll_get_default_method(m_device)) , m_defaultScrollButton(libinput_device_config_scroll_get_default_button(m_device)) , m_disableWhileTypingEnabledByDefault(libinput_device_config_dwt_get_default_enabled(m_device)) , m_disableWhileTyping(m_supportsDisableWhileTyping ? libinput_device_config_dwt_get_enabled(m_device) == LIBINPUT_CONFIG_DWT_ENABLED : false) , m_middleEmulation(libinput_device_config_middle_emulation_get_enabled(m_device) == LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED) , m_leftHanded(m_supportsLeftHanded ? libinput_device_config_left_handed_get(m_device) : false) , m_naturalScroll(m_supportsNaturalScroll ? libinput_device_config_scroll_get_natural_scroll_enabled(m_device) : false) , m_scrollMethod(libinput_device_config_scroll_get_method(m_device)) , m_scrollButton(libinput_device_config_scroll_get_button(m_device)) , m_defaultPointerAcceleration(libinput_device_config_accel_get_default_speed(m_device)) , m_pointerAcceleration(libinput_device_config_accel_get_speed(m_device)) , m_scrollFactor(1.0) , m_supportedPointerAccelerationProfiles(libinput_device_config_accel_get_profiles(m_device)) , m_defaultPointerAccelerationProfile(libinput_device_config_accel_get_default_profile(m_device)) , m_pointerAccelerationProfile(libinput_device_config_accel_get_profile(m_device)) , m_enabled(m_supportsDisableEvents ? (libinput_device_config_send_events_get_mode(m_device) & LIBINPUT_CONFIG_SEND_EVENTS_DISABLED) == 0 : true) , m_disableEventsOnExternalMouseEnabledByDefault(m_supportsDisableEventsOnExternalMouse && (libinput_device_config_send_events_get_default_mode(m_device) & LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE)) , m_disableEventsOnExternalMouse(m_supportsDisableEventsOnExternalMouse && (libinput_device_config_send_events_get_mode(m_device) & LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE)) , m_config() , m_defaultCalibrationMatrix(getMatrix(m_device, &libinput_device_config_calibration_get_default_matrix)) , m_calibrationMatrix(getMatrix(m_device, &libinput_device_config_calibration_get_matrix)) , m_pressureCurve(deserializePressureCurve(defaultPressureCurve())) , m_supportedClickMethods(libinput_device_config_click_get_methods(m_device)) , m_defaultClickMethod(libinput_device_config_click_get_default_method(m_device)) , m_clickMethod(libinput_device_config_click_get_method(m_device)) , m_outputArea(s_identityRect) , m_supportsPressureRange(false) , m_pressureRangeMin(0.0) , m_pressureRangeMax(1.0) , m_defaultPressureRangeMin(0.0) , m_defaultPressureRangeMax(1.0) , m_inputArea(s_identityRect) { libinput_device_ref(m_device); libinput_device_set_user_data(m_device, this); qreal width = 0; qreal height = 0; if (libinput_device_get_size(m_device, &width, &height) == 0) { m_size = QSizeF(width, height); } if (m_pointer) { // 0x120 is the first joystick Button for (int button = BTN_LEFT; button < 0x120; ++button) { if (libinput_device_pointer_has_button(m_device, button)) { m_supportedButtons |= buttonToQtMouseButton(button); } } } if (m_keyboard) { m_alphaNumericKeyboard = checkAlphaNumericKeyboard(m_device); } if (m_supportsCalibrationMatrix && m_calibrationMatrix != m_defaultCalibrationMatrix) { float matrix[]{m_defaultCalibrationMatrix(0, 0), m_defaultCalibrationMatrix(0, 1), m_defaultCalibrationMatrix(0, 2), m_defaultCalibrationMatrix(1, 0), m_defaultCalibrationMatrix(1, 1), m_defaultCalibrationMatrix(1, 2)}; libinput_device_config_calibration_set_matrix(m_device, matrix); m_calibrationMatrix = m_defaultCalibrationMatrix; } if (supportsInputArea() && m_inputArea != defaultInputArea()) { #if HAVE_LIBINPUT_INPUT_AREA const libinput_config_area_rectangle rect{ .x1 = m_inputArea.topLeft().x(), .y1 = m_inputArea.topLeft().y(), .x2 = m_inputArea.bottomRight().x(), .y2 = m_inputArea.bottomRight().y(), }; libinput_device_config_area_set_rectangle(m_device, &rect); #endif } libinput_device_group *group = libinput_device_get_device_group(device); m_deviceGroupId = QCryptographicHash::hash(QString::asprintf("%p", group).toLatin1(), QCryptographicHash::Sha1).toBase64(); QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/InputDevice/") + m_sysName, QStringLiteral("org.kde.KWin.InputDevice"), this, QDBusConnection::ExportAllProperties); } Device::~Device() { QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/org/kde/KWin/InputDevice/") + m_sysName); libinput_device_set_user_data(m_device, nullptr); libinput_device_unref(m_device); } Device *Device::get(libinput_device *native) { return static_cast(libinput_device_get_user_data(native)); } template void Device::writeEntry(const ConfigKey &key, const T &value) { if (!m_config.isValid()) { return; } if (m_loading) { return; } auto it = s_configData.find(key); Q_ASSERT(it != s_configData.end()); m_config.writeEntry(it.value()->key.constData(), value); m_config.sync(); } void Device::loadConfiguration() { if (!m_config.isValid() && !m_defaultConfig.isValid()) { return; } m_loading = true; for (auto it = s_configData.begin(), end = s_configData.end(); it != end; ++it) { (*it)->read(this, m_config); }; m_loading = false; } void Device::setPointerAcceleration(qreal acceleration) { if (!m_supportsPointerAcceleration) { return; } acceleration = std::clamp(acceleration, -1.0, 1.0); if (libinput_device_config_accel_set_speed(m_device, acceleration) == LIBINPUT_CONFIG_STATUS_SUCCESS) { if (m_pointerAcceleration != acceleration) { m_pointerAcceleration = acceleration; Q_EMIT pointerAccelerationChanged(); writeEntry(ConfigKey::PointerAcceleration, QString::number(acceleration, 'f', 3)); } } } void Device::setScrollButton(quint32 button) { if (!(m_supportedScrollMethods & LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN)) { return; } if (libinput_device_config_scroll_set_button(m_device, button) == LIBINPUT_CONFIG_STATUS_SUCCESS) { if (m_scrollButton != button) { m_scrollButton = button; writeEntry(ConfigKey::ScrollButton, m_scrollButton); Q_EMIT scrollButtonChanged(); } } } void Device::setPointerAccelerationProfile(bool set, enum libinput_config_accel_profile profile) { if (!(m_supportedPointerAccelerationProfiles & profile)) { return; } if (!set) { profile = (profile == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) ? LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE : LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; if (!(m_supportedPointerAccelerationProfiles & profile)) { return; } } if (libinput_device_config_accel_set_profile(m_device, profile) == LIBINPUT_CONFIG_STATUS_SUCCESS) { if (m_pointerAccelerationProfile != profile) { m_pointerAccelerationProfile = profile; Q_EMIT pointerAccelerationProfileChanged(); writeEntry(ConfigKey::PointerAccelerationProfile, (quint32)profile); } } } void Device::setClickMethod(bool set, enum libinput_config_click_method method) { if (!(m_supportedClickMethods & method)) { return; } if (!set) { method = (method == LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS) ? LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER : LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; if (!(m_supportedClickMethods & method)) { return; } } if (libinput_device_config_click_set_method(m_device, method) == LIBINPUT_CONFIG_STATUS_SUCCESS) { if (m_clickMethod != method) { m_clickMethod = method; Q_EMIT clickMethodChanged(); writeEntry(ConfigKey::ClickMethod, (quint32)method); } } } void Device::setScrollMethod(bool set, enum libinput_config_scroll_method method) { if (!(m_supportedScrollMethods & method)) { return; } bool isCurrent = m_scrollMethod == method; if (!set) { if (isCurrent) { method = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; isCurrent = false; } else { return; } } if (libinput_device_config_scroll_set_method(m_device, method) == LIBINPUT_CONFIG_STATUS_SUCCESS) { if (!isCurrent) { m_scrollMethod = method; Q_EMIT scrollMethodChanged(); writeEntry(ConfigKey::ScrollMethod, (quint32)method); } } } void Device::setLmrTapButtonMap(bool set) { enum libinput_config_tap_button_map map = set ? LIBINPUT_CONFIG_TAP_MAP_LMR : LIBINPUT_CONFIG_TAP_MAP_LRM; if (m_tapFingerCount < 2) { return; } if (!set) { map = LIBINPUT_CONFIG_TAP_MAP_LRM; } if (libinput_device_config_tap_set_button_map(m_device, map) == LIBINPUT_CONFIG_STATUS_SUCCESS) { if (m_tapButtonMap != map) { m_tapButtonMap = map; writeEntry(ConfigKey::LmrTapButtonMap, set); Q_EMIT tapButtonMapChanged(); } } } void *Device::group() const { return libinput_device_get_device_group(m_device); } int Device::tabletPadButtonCount() const { return libinput_device_tablet_pad_get_num_buttons(m_device); } int Device::tabletPadRingCount() const { return libinput_device_tablet_pad_get_num_rings(m_device); } int Device::tabletPadStripCount() const { return libinput_device_tablet_pad_get_num_strips(m_device); } int Device::tabletPadModeCount() const { return libinput_device_tablet_pad_get_num_mode_groups(m_device); } int Device::tabletPadMode() const { return libinput_tablet_pad_mode_group_get_mode(libinput_device_tablet_pad_get_mode_group(m_device, 0)); } #define CONFIG(method, condition, function, variable, key) \ void Device::method(bool set) \ { \ if (condition) { \ return; \ } \ if (libinput_device_config_##function(m_device, set) == LIBINPUT_CONFIG_STATUS_SUCCESS) { \ if (m_##variable != set) { \ m_##variable = set; \ writeEntry(ConfigKey::key, m_##variable); \ Q_EMIT variable##Changed(); \ } \ } \ } CONFIG(setLeftHanded, !m_supportsLeftHanded, left_handed_set, leftHanded, LeftHanded) CONFIG(setNaturalScroll, !m_supportsNaturalScroll, scroll_set_natural_scroll_enabled, naturalScroll, NaturalScroll) #undef CONFIG #define CONFIG(method, condition, function, enum, variable, key) \ void Device::method(bool set) \ { \ if (condition) { \ return; \ } \ if (libinput_device_config_##function(m_device, set ? LIBINPUT_CONFIG_##enum##_ENABLED : LIBINPUT_CONFIG_##enum##_DISABLED) == LIBINPUT_CONFIG_STATUS_SUCCESS) { \ if (m_##variable != set) { \ m_##variable = set; \ writeEntry(ConfigKey::key, m_##variable); \ Q_EMIT variable##Changed(); \ } \ } \ } CONFIG(setDisableWhileTyping, !m_supportsDisableWhileTyping, dwt_set_enabled, DWT, disableWhileTyping, DisableWhileTyping) CONFIG(setTapToClick, m_tapFingerCount == 0, tap_set_enabled, TAP, tapToClick, TapToClick) CONFIG(setTapAndDrag, false, tap_set_drag_enabled, DRAG, tapAndDrag, TapAndDrag) CONFIG(setTapDragLock, false, tap_set_drag_lock_enabled, DRAG_LOCK, tapDragLock, TapDragLock) CONFIG(setMiddleEmulation, m_supportsMiddleEmulation == false, middle_emulation_set_enabled, MIDDLE_EMULATION, middleEmulation, MiddleButtonEmulation) #undef CONFIG void Device::setEnabled(bool set) { if (!m_supportsDisableEvents) { return; } const auto enabledMode = (m_supportsDisableEventsOnExternalMouse && m_disableEventsOnExternalMouse) ? LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE : LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; const auto mode = set ? enabledMode : LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; if (libinput_device_config_send_events_set_mode(m_device, mode) == LIBINPUT_CONFIG_STATUS_SUCCESS) { if (m_enabled != set) { m_enabled = set; writeEntry(ConfigKey::Enabled, m_enabled); Q_EMIT enabledChanged(); } } } void Device::setDisableEventsOnExternalMouse(bool set) { if (!m_supportsDisableEventsOnExternalMouse) { return; } const auto enabledMode = set ? LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE : LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; if (!m_enabled || libinput_device_config_send_events_set_mode(m_device, enabledMode) == LIBINPUT_CONFIG_STATUS_SUCCESS) { if (m_disableEventsOnExternalMouse != set) { m_disableEventsOnExternalMouse = set; writeEntry(ConfigKey::DisableEventsOnExternalMouse, m_disableEventsOnExternalMouse); Q_EMIT disableEventsOnExternalMouseChanged(); } } } void Device::setScrollFactor(qreal factor) { if (m_scrollFactor != factor) { m_scrollFactor = factor; writeEntry(ConfigKey::ScrollFactor, m_scrollFactor); Q_EMIT scrollFactorChanged(); } } void Device::setCalibrationMatrix(const QString &value) { const auto matrix = deserializeMatrix(value); if (!m_supportsCalibrationMatrix || m_calibrationMatrix == matrix) { return; } if (setOrientedCalibrationMatrix(m_device, matrix, m_orientation)) { QList list; list.reserve(16); for (uchar row = 0; row < 4; ++row) { for (uchar col = 0; col < 4; ++col) { list << matrix(row, col); } } writeEntry(ConfigKey::Calibration, list); m_calibrationMatrix = matrix; Q_EMIT calibrationMatrixChanged(); } } QString Device::defaultPressureCurve() const { QEasingCurve curve(QEasingCurve::Type::BezierSpline); curve.addCubicBezierSegment(QPointF{0.0f, 0.0f}, QPointF{1.0f, 1.0f}, QPointF{1.0f, 1.0f}); return serializePressureCurve(curve); } QEasingCurve Device::pressureCurve() const { return m_pressureCurve; } QString Device::serializedPressureCurve() const { return serializePressureCurve(m_pressureCurve); } void Device::setPressureCurve(const QString &curve) { const auto easingCurve = deserializePressureCurve(curve); if (m_pressureCurve != easingCurve) { writeEntry(ConfigKey::TabletToolPressureCurve, curve); m_pressureCurve = easingCurve; Q_EMIT pressureCurveChanged(); } } QString Device::serializePressureCurve(const QEasingCurve &curve) { // We only care about the first two points. toCubicSpline adds the end point as the third, but to us that's always (1,1). const auto points = curve.toCubicSpline().first(2); QString serializedString; for (const QPointF &pair : points) { serializedString += QString::number(pair.x()); serializedString += ','; serializedString += QString::number(pair.y()); serializedString += ';'; } return serializedString; } QEasingCurve Device::deserializePressureCurve(const QString &curve) { const QStringList data = curve.split(';'); QList points; for (const QString &pair : data) { if (pair.indexOf(',') > -1) { points.append({pair.section(',', 0, 0).toDouble(), pair.section(',', 1, 1).toDouble()}); } } auto easingCurve = QEasingCurve(QEasingCurve::Type::BezierSpline); // We only support 2 points if (points.size() >= 2) { easingCurve.addCubicBezierSegment(points.at(0), points.at(1), QPointF{1.0f, 1.0f}); } return easingCurve; } void Device::setOrientation(Qt::ScreenOrientation orientation) { if (!m_supportsCalibrationMatrix || m_orientation == orientation) { return; } if (setOrientedCalibrationMatrix(m_device, m_calibrationMatrix, orientation)) { writeEntry(ConfigKey::Orientation, static_cast(orientation)); m_orientation = orientation; Q_EMIT orientationChanged(); } } void Device::setOutputName(const QString &name) { #ifndef KWIN_BUILD_TESTING if (name == m_outputName) { return; } setOutput(nullptr); auto outputs = kwinApp()->outputBackend()->outputs(); for (int i = 0; i < outputs.count(); ++i) { if (!outputs[i]->isEnabled()) { continue; } if (outputs[i]->name() == name) { setOutput(outputs[i]); break; } } m_outputName = name; writeEntry(ConfigKey::OutputName, name); Q_EMIT outputNameChanged(); #endif } Output *Device::output() const { return m_output; } void Device::setOutput(Output *output) { m_output = output; } static libinput_led toLibinputLEDS(LEDs leds) { quint32 libinputLeds = 0; if (leds.testFlag(LED::NumLock)) { libinputLeds = libinputLeds | LIBINPUT_LED_NUM_LOCK; } if (leds.testFlag(LED::CapsLock)) { libinputLeds = libinputLeds | LIBINPUT_LED_CAPS_LOCK; } if (leds.testFlag(LED::ScrollLock)) { libinputLeds = libinputLeds | LIBINPUT_LED_SCROLL_LOCK; } if (leds.testFlag(LED::Compose)) { libinputLeds = libinputLeds | LIBINPUT_LED_COMPOSE; } if (leds.testFlag(LED::Kana)) { libinputLeds = libinputLeds | LIBINPUT_LED_KANA; } return libinput_led(libinputLeds); } LEDs Device::leds() const { return m_leds; } void Device::setLeds(LEDs leds) { if (m_leds != leds) { m_leds = leds; libinput_device_led_update(m_device, toLibinputLEDS(m_leds)); } } bool Device::supportsOutputArea() const { return m_tabletTool; } QRectF Device::defaultOutputArea() const { return s_identityRect; } QRectF Device::outputArea() const { return m_outputArea; } void Device::setOutputArea(const QRectF &outputArea) { if (m_outputArea != outputArea) { m_outputArea = outputArea; writeEntry(ConfigKey::OutputArea, m_outputArea); Q_EMIT outputAreaChanged(); } } void Device::setMapToWorkspace(bool mapToWorkspace) { if (m_mapToWorkspace != mapToWorkspace) { m_mapToWorkspace = mapToWorkspace; writeEntry(ConfigKey::MapToWorkspace, m_mapToWorkspace); Q_EMIT mapToWorkspaceChanged(); } } bool Device::supportsPressureRange() const { return m_supportsPressureRange; } void Device::setSupportsPressureRange(const bool supported) { if (m_supportsPressureRange != supported) { m_supportsPressureRange = supported; Q_EMIT supportsPressureRangeChanged(); } } double Device::pressureRangeMin() const { return m_pressureRangeMin; } void Device::setPressureRangeMin(const double value) { if (m_pressureRangeMin != value) { m_pressureRangeMin = value; writeEntry(ConfigKey::TabletToolPressureRangeMin, m_pressureRangeMin); Q_EMIT pressureRangeMinChanged(); } } double Device::pressureRangeMax() const { return m_pressureRangeMax; } void Device::setPressureRangeMax(const double value) { if (m_pressureRangeMax != value) { m_pressureRangeMax = value; writeEntry(ConfigKey::TabletToolPressureRangeMax, m_pressureRangeMax); Q_EMIT pressureRangeMaxChanged(); } } double Device::defaultPressureRangeMin() const { return m_defaultPressureRangeMin; } double Device::defaultPressureRangeMax() const { return m_defaultPressureRangeMax; } bool Device::supportsInputArea() const { #if HAVE_LIBINPUT_INPUT_AREA return true; #else return false; #endif } QRectF Device::inputArea() const { return m_inputArea; } void Device::setInputArea(const QRectF &inputArea) { if (m_inputArea != inputArea) { m_inputArea = inputArea; #if HAVE_LIBINPUT_INPUT_AREA const libinput_config_area_rectangle rect{ .x1 = m_inputArea.topLeft().x(), .y1 = m_inputArea.topLeft().y(), .x2 = m_inputArea.bottomRight().x(), .y2 = m_inputArea.bottomRight().y(), }; libinput_device_config_area_set_rectangle(m_device, &rect); #endif writeEntry(ConfigKey::InputArea, m_inputArea); Q_EMIT inputAreaChanged(); } } QRectF Device::defaultInputArea() const { return s_identityRect; } QString Device::serializeMatrix(const QMatrix4x4 &matrix) { QString result; for (int i = 0; i < 16; i++) { result.append(QString::number(matrix.constData()[i])); if (i != 15) { result.append(QLatin1Char(',')); } } return result; } QMatrix4x4 Device::deserializeMatrix(const QString &matrix) { const auto items = QStringView(matrix).split(QLatin1Char(',')); if (items.size() == 16) { QList data; data.reserve(16); std::ranges::transform(std::as_const(items), std::back_inserter(data), [](const QStringView &item) { return item.toFloat(); }); return QMatrix4x4{data.constData()}; } return QMatrix4x4{}; } } } #include "moc_device.cpp"