/*SPDX-FileCopyrightText: 2017 Roman Gilg <subdiff@gmail.com>SPDX-FileCopyrightText: 2018 Furkan Tokac <furkantokac34@gmail.com>SPDX-FileCopyrightText: 2025 Jakob Petsovits <jpetso@petsovits.com>SPDX-License-Identifier: GPL-2.0-or-later*/import QtQuickimport QtQuick.Controls as QQC2import QtQuick.Layoutsimport org.kde.kcmutils as KCMimport org.kde.kirigami as Kirigamiimport org.kde.touchpad.kcmimport org.kde.plasma.private.kcm_touchpad as TouchpadKCM.SimpleKCM {id: rootspacing: Kirigami.Units.smallSpacingproperty alias deviceIndex: deviceSelector.currentIndexproperty Touchpad.InputDevice device: backend.inputDevices[deviceIndex] ?? nullsignal changeSignal()LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeftLayoutMirroring.childrenInherit: trueheaderPaddingEnabled: false // Let the InlineMessage touch the edgesheader: Kirigami.InlineMessage {id: inlineMessageposition: Kirigami.InlineMessage.Position.Header}Connections {target: KCMTouchpadfunction onShowMessage(message, type) {if (!backend.inputDevices?.length) {return}if (message.length !== 0) {inlineMessage.text = messageinlineMessage.type = typeinlineMessage.visible = true} else {inlineMessage.visible = false}}}Kirigami.PlaceholderMessage {icon.name: "input-touchpad"text: i18nd("kcm_touchpad", "No touchpad found")explanation: i18nd("kcm_touchpad", "Connect an external touchpad");anchors.centerIn: parentvisible: !backend.inputDevices?.lengthwidth: parent.width - (Kirigami.Units.largeSpacing * 4)}Kirigami.FormLayout {id: formLayoutvisible: backend.inputDevices?.length > 0enabled: visible// DeviceQQC2.ComboBox {Kirigami.FormData.label: i18nd("kcm_touchpad", "Device:")id: deviceSelectorvisible: count > 1Layout.fillWidth: truemodel: backend.inputDevicestextRole: "name"Connections {target: backendfunction onDeviceRemoved(index) {if (index < deviceSelector.currentIndex) {--deviceSelector.currentIndex;}}}}Item {Kirigami.FormData.isSection: false}// General settingsQQC2.CheckBox {id: deviceEnabledKirigami.FormData.label: i18nd("kcm_touchpad", "General:")text: i18nd("kcm_touchpad", "Device enabled")enabled: root.device?.supportsDisableEvents ?? falsechecked: root.device && (!root.device.supportsDisableEvents || root.device.enabled)onToggled: {if (root.device) {root.device.enabled = checkedroot.changeSignal()}}}QQC2.CheckBox {id: disableEventsOnExternalMousetext: i18ndc("kcm_touchpad", "@option:check", "Disable while mouse is connected")leftPadding: Application.layoutDirection === Qt.LeftToRight ?deviceEnabled.contentItem.leftPadding : disableEventsOnExternalMouse.paddingrightPadding: Application.layoutDirection === Qt.RightToLeft ?deviceEnabled.contentItem.rightPadding : disableEventsOnExternalMouse.paddingenabled: root.device?.supportsDisableEventsOnExternalMouse ?? falsechecked: enabled && (root.device?.disableEventsOnExternalMouse ?? false)onToggled: {if (root.device) {root.device.disableEventsOnExternalMouse = checkedroot.changeSignal()}}}ColumnLayout {Kirigami.FormData.buddyFor: dwtspacing: 0QQC2.CheckBox {id: dwttext: i18nd("kcm_touchpad", "Disable while typing")Accessible.description: dwtInfoLabel.textleftPadding: Application.layoutDirection === Qt.LeftToRight ?deviceEnabled.contentItem.leftPadding : disableEventsOnExternalMouse.paddingrightPadding: Application.layoutDirection === Qt.RightToLeft ?deviceEnabled.contentItem.rightPadding : disableEventsOnExternalMouse.paddingenabled: root.device?.supportsDisableWhileTyping ?? falsechecked: enabled && (root.device?.disableWhileTyping ?? false)onToggled: {if (root.device) {root.device.disableWhileTyping = checkedroot.changeSignal()}}}QQC2.Label {id: dwtInfoLabelLayout.fillWidth: trueleftPadding: Application.layoutDirection === Qt.LeftToRight ?dwt.leftPadding + dwt.contentItem.leftPadding : dwt.paddingrightPadding: Application.layoutDirection === Qt.RightToLeft ?dwt.rightPadding + dwt.contentItem.rightPadding : dwt.paddingtext: i18ndc("kcm_touchpad", "@label 'this' refers to the 'disable touchpad while typing' feature", "This can interfere with video games.")textFormat: Text.PlainTextelide: Text.ElideRightfont: Kirigami.Theme.smallFontenabled: dwt.enabled}}QQC2.CheckBox {id: leftHandedtext: i18nd("kcm_touchpad", "Left-handed mode")enabled: root.device?.supportsLeftHanded ?? falsechecked: enabled && (root.device?.leftHanded ?? false)onToggled: {if (root.device) {root.device.leftHanded = checkedroot.changeSignal()}}}RowLayout {spacing: Kirigami.Units.smallSpacingQQC2.CheckBox {id: middleEmulationtext: i18ndc("kcm_touchpad", "@option:check", "Press left and right buttons to middle-click")enabled: root.device?.supportsMiddleEmulation ?? falsechecked: enabled && (root.device?.middleEmulation ?? false)onToggled: {if (root.device) {root.device.middleEmulation = checkedroot.changeSignal()}}}Kirigami.ContextualHelpButton {toolTipText: i18ndc("kcm_touchpad", "@info:tooltip from ContextualHelpButton", "Activating this setting increases click latency by 50ms. The extra delay is needed to correctly detect simultaneous left and right button presses.")}}Item {Kirigami.FormData.isSection: false}// AccelerationRowLayout {Kirigami.FormData.label: i18nd("kcm_touchpad", "Pointer speed:")id: accelSpeedLayout.fillWidth: truefunction onAccelSpeedChanged(val) {if ((val / 100) !== root.device.pointerAcceleration) {root.device.pointerAcceleration = val / 100root.changeSignal()}}QQC2.Slider {id: accelSpeedSliderLayout.fillWidth: truefrom: 1to: 11stepSize: 1enabled: root.device?.supportsPointerAcceleration ?? false// convert libinput pointer acceleration range [-1, 1] to slider range [1, 11]value: enabled ? Math.round(6 + root.device.pointerAcceleration / 0.2) : 0onMoved: {if (root.device) {// convert slider range [1, 11] to accelSpeedValue range [-100, 100]const accelSpeedValue = Math.round(((value - 6) * 0.2) * 100)accelSpeed.onAccelSpeedChanged(accelSpeedValue)}}}QQC2.SpinBox {id: accelSpeedSpinboxLayout.minimumWidth: Kirigami.Units.gridUnit * 4from: -100to: 100stepSize: 1editable: trueenabled: root.device?.supportsPointerAcceleration ?? false// if existing configuration or another application set a value with more than 2 decimals// we reduce the precision to 2value: enabled ? Math.round(root.device.pointerAcceleration * 100) : 0validator: DoubleValidator {bottom: accelSpeedSpinbox.fromtop: accelSpeedSpinbox.to}onValueModified: {if (root.device) {accelSpeed.onAccelSpeedChanged(value)// Keyboard input breaks SpinBox value bindings with current Qt.// Restore the binding so clicking "Reset" will update it correctly.value = Qt.binding(() => accelSpeedSpinbox.enabled && root.device? Math.round(root.device.pointerAcceleration * 100): 0);}}textFromValue: function(val, locale) {return Number(val / 100).toLocaleString(locale, "f", 2)}valueFromText: function(text, locale) {return Number.fromLocaleString(locale, text) * 100}}}RowLayout {Kirigami.FormData.buddyFor: accelProfileEnabledspacing: Kirigami.Units.smallSpacingQQC2.CheckBox {id: accelProfileEnabledtext: i18nd("kcm_touchpad", "Enable pointer acceleration")enabled: root.device?.supportsPointerAccelerationProfileAdaptive ?? falsevisible: enabledchecked: enabled && !(root.device?.pointerAccelerationProfileFlat ?? false)onToggled: {if (root.device) {root.device.pointerAccelerationProfileFlat = !checkedroot.device.pointerAccelerationProfileAdaptive = checkedroot.changeSignal()}}}Kirigami.ContextualHelpButton {toolTipText: i18ndc("kcm_touchpad", "@info:tooltip from ContextualHelpButton", "When enabled, pointer travel distance increases with faster movement speed.")}}Item {Kirigami.FormData.isSection: false}// Scroll Speed aka scroll FactorGridLayout {Kirigami.FormData.label: i18nd("kcm_touchpad", "Scrolling speed:")Kirigami.FormData.buddyFor: scrollFactorLayout.fillWidth: truevisible: root.device?.supportsScrollFactor ?? falsecolumns: 3QQC2.Slider {id: scrollFactorLayout.fillWidth: trueLayout.columnSpan: 3from: 0to: 14stepSize: 1enabled: root.devicereadonly property list<real> values: [0.1,0.3,0.5,0.75,1, // default1.5,2,3,4,5,7,9,12,15,20]function indexOf(val) {const index = values.indexOf(val)return index === -1 ? values.indexOf(1) : index}value: indexOf(root.device?.scrollFactor ?? 1)onMoved: {if (root.device) {root.device.scrollFactor = values[value]root.changeSignal()}}}//row 2QQC2.Label {text: i18ndc("kcm_touchpad", "Slower Scroll", "Slower")textFormat: Text.PlainText}Item {Layout.fillWidth: true}QQC2.Label {text: i18ndc("kcm_touchpad", "Faster Scroll Speed", "Faster")textFormat: Text.PlainText}}// ScrollingColumnLayout {id: scrollMethodKirigami.FormData.label: i18nd("kcm_touchpad", "Scrolling:")Kirigami.FormData.buddyFor: scrollMethodTwoFingersvisible: scrollMethodTwoFingers.enabled || scrollMethodTouchpadEdges.enabledspacing: Kirigami.Units.smallSpacingQQC2.ButtonGroup {buttons: [scrollMethodTwoFingers, scrollMethodTouchpadEdges]onClicked: {if (root.device) {root.device.scrollTwoFinger = scrollMethodTwoFingers.checkedroot.device.scrollEdge = scrollMethodTouchpadEdges.checkedroot.changeSignal()}}}QQC2.RadioButton {id: scrollMethodTwoFingerstext: i18nd("kcm_touchpad", "Two fingers")enabled: root.device?.supportsScrollTwoFinger ?? falsechecked: root.device?.scrollTwoFinger ?? false}QQC2.RadioButton {id: scrollMethodTouchpadEdgestext: i18nd("kcm_touchpad", "Touchpad edges")enabled: root.device?.supportsScrollEdge ?? falsechecked: root.device?.scrollEdge ?? false}}QQC2.CheckBox {id: naturalScrolltext: i18nd("kcm_touchpad", "Invert scroll direction (Natural scrolling)")enabled: root.device?.supportsNaturalScroll ?? falsechecked: enabled && (root.device?.naturalScroll ?? false)onToggled: {if (root.device) {root.device.naturalScroll = checkedroot.changeSignal()}}}QQC2.CheckBox {id: disableHorizontalScrollingtext: i18nd("kcm_touchpad", "Disable horizontal scrolling")visible: root.device?.supportsHorizontalScrolling ?? falseenabled: root.device?.supportsHorizontalScrolling ?? falsechecked: enabled && !(root.device?.horizontalScrolling ?? true)onToggled: {if (root.device) {root.device.horizontalScrolling = !checkedroot.changeSignal()}}}Item {Kirigami.FormData.isSection: false}// TappingQQC2.CheckBox {id: tapToClickKirigami.FormData.label: i18ndc("kcm_touchpad", "@label for checkbox, tap-to-click", "Tapping:")text: i18ndc("kcm_touchpad", "@option:check", "Tap-to-click")enabled: root.device?.tapFingerCount > 0checked: enabled && (root.device?.tapToClick ?? false)onToggled: {if (root.device) {root.device.tapToClick = checkedroot.changeSignal()}}}RowLayout {spacing: Kirigami.Units.smallSpacingQQC2.CheckBox {id: tapAndDragtext: i18nd("kcm_touchpad", "Tap-and-drag")enabled: root.device?.tapFingerCount > 0 && tapToClick.checkedchecked: enabled && (root.device?.tapAndDrag ?? false)onToggled: {if (root.device) {root.device.tapAndDrag = checkedroot.changeSignal()}}}Kirigami.ContextualHelpButton {toolTipText: i18ndc("kcm_touchpad", "@info:tooltip from ContextualHelpButton", "Tap, then tap and immediately slide finger over the touchpad to drag. Lift finger to drop.")}}QQC2.CheckBox {id: tapAndDragLocktext: i18nd("kcm_touchpad", "Allow briefly lifting finger during tap-and-drag")enabled: root.device?.tapFingerCount > 0 && tapAndDrag.checkedchecked: enabled && (root.device?.tapDragLock ?? false)onToggled: {if (root.device) {root.device.tapDragLock = checkedroot.changeSignal()}}}ColumnLayout {id: multiTapKirigami.FormData.label: i18nd("kcm_touchpad", "Two-finger tap:")Kirigami.FormData.buddyFor: multiTapRightClickvisible: root.device?.supportsLmrTapButtonMapenabled: root.device?.supportsLmrTapButtonMap && tapToClick.checkedspacing: Kirigami.Units.smallSpacingQQC2.ButtonGroup {buttons: [multiTapRightClick, multiTapMiddleClick]onClicked: {if (root.device) {root.device.lmrTapButtonMap = multiTapMiddleClick.checkedroot.changeSignal()}}}QQC2.RadioButton {id: multiTapRightClicktext: (root.device?.tapFingerCount > 2? i18nd("kcm_touchpad", "Right-click (three-finger tap to middle-click)"): i18nd("kcm_touchpad", "Right-click"))checked: multiTap.enabled && !(root.device?.lmrTapButtonMap ?? false)}QQC2.RadioButton {id: multiTapMiddleClicktext: (root.device?.tapFingerCount > 2? i18nd("kcm_touchpad", "Middle-click (three-finger tap to right-click)"): i18nd("kcm_touchpad", "Middle-click"))checked: multiTap.enabled && (root.device?.lmrTapButtonMap ?? false)}}Item {Kirigami.FormData.isSection: false}ColumnLayout {id: rightClickMethodKirigami.FormData.label: i18ndc("kcm_touchpad", "@label for radiobutton group, configure right-click with touch-pad integrated button (pressing into the touchpad)", "Integrated right-click:")Kirigami.FormData.buddyFor: rightClickMethodAreasenabled: (root.device?.supportsClickMethodAreas && root.device?.supportsClickMethodClickfinger) ?? falsevisible: (root.device?.supportsClickMethodAreas || root.device?.supportsClickMethodClickfinger) ?? false// spacing only on top of radio buttons, not between radio and help text labelspacing: 0QQC2.ButtonGroup {buttons: [rightClickMethodAreas, rightClickMethodClickfinger]onClicked: {if (root.device) {root.device.clickMethodAreas = rightClickMethodAreas.checked && rightClickMethodAreas.visibleroot.device.clickMethodClickfinger = rightClickMethodClickfinger.checked && rightClickMethodClickfinger.visibleroot.changeSignal()}}}QQC2.RadioButton {id: rightClickMethodAreastext: i18ndc("kcm_touchpad", "@option:radio touchpad integrated right-click", "Press bottom-right corner")enabled: root.device?.supportsClickMethodAreas ?? falsechecked: enabled && (root.device?.clickMethodAreas ?? false)Accessible.description: rightClickMethodAreasInfoLabel.visible ? rightClickMethodAreasInfoLabel.text : ""}QQC2.Label {id: rightClickMethodAreasInfoLabelLayout.fillWidth: truevisible: !middleClickMethod.visibleleftPadding: Application.layoutDirection === Qt.LeftToRight ?rightClickMethodAreas.contentItem.leftPadding : rightClickMethodAreas.paddingrightPadding: Application.layoutDirection === Qt.RightToLeft ?rightClickMethodAreas.contentItem.rightPadding : rightClickMethodAreas.paddingtext: middleEmulation.checked? i18ndc("kcm_touchpad", "@info shown below radio button", "Middle-click by pressing both bottom corners."): i18ndc("kcm_touchpad", "@info shown below radio button", "Middle-click by pressing bottom center.")textFormat: Text.PlainTextelide: Text.ElideRightfont: Kirigami.Theme.smallFont}QQC2.RadioButton {id: rightClickMethodClickfingertext: i18ndc("kcm_touchpad", "@option:radio touchpad integrated right-click", "Press touchpad with two fingers")Accessible.description: rightClickMethodClickfingerInfoLabel.visible ? rightClickMethodClickfingerInfoLabel.text : ""topPadding: Kirigami.Units.smallSpacing // in lieu of rightClickMethod.spacingenabled: root.device?.supportsClickMethodClickfinger ?? falsechecked: enabled && (root.device?.clickMethodClickfinger ?? false)}QQC2.Label {id: rightClickMethodClickfingerInfoLabelLayout.fillWidth: truevisible: !middleClickMethod.visibleleftPadding: Application.layoutDirection === Qt.LeftToRight ?rightClickMethodClickfinger.contentItem.leftPadding : rightClickMethodClickfinger.paddingrightPadding: Application.layoutDirection === Qt.RightToLeft ?rightClickMethodClickfinger.contentItem.rightPadding : rightClickMethodClickfinger.paddingtext: i18ndc("kcm_touchpad", "@info shown below radio button", "Middle-click by pressing with three fingers.")textFormat: Text.PlainTextelide: Text.ElideRightfont: Kirigami.Theme.smallFont}}ColumnLayout {id: middleClickMethodKirigami.FormData.label: i18ndc("kcm_touchpad", "@label for radiobutton group, configure middle-click with touch-pad integrated button (pressing into the touchpad)", "Integrated middle-click:")Kirigami.FormData.buddyFor: middleSoftwareEmulationenabled: root.device?.supportsMiddleEmulation ?? falsespacing: Kirigami.Units.smallSpacingvisible: noMiddleSoftwareEmulation.visible ||middleSoftwareEmulation.visible ||clickfingerMiddleInfoBox.visibleQQC2.ButtonGroup {buttons: [noMiddleSoftwareEmulation, middleSoftwareEmulation]onClicked: {if (root.device) {root.device.middleEmulation = middleSoftwareEmulation.checked && middleSoftwareEmulation.visibleroot.changeSignal()}}}QQC2.RadioButton {id: noMiddleSoftwareEmulationtext: i18ndc("kcm_touchpad", "@option:radio touchpad integrated middle-click", "Press bottom middle edge")visible: rightClickMethodAreas.checkedchecked: middleClickMethod.enabled && !(root.device?.middleEmulation ?? false)}QQC2.RadioButton {id: middleSoftwareEmulationtext: i18ndc("kcm_touchpad", "@option:radio touchpad integrated middle-click", "Press bottom left and bottom right corners")visible: rightClickMethodAreas.checkedchecked: middleClickMethod.enabled && (root.device?.middleEmulation ?? false)}QQC2.CheckBox {id: clickfingerMiddleInfoBoxtext: i18ndc("kcm_touchpad", "@option:check touchpad integrated middle-click", "Press touchpad with three fingers")checked: trueenabled: falsevisible: rightClickMethodClickfinger.checked}}}}