/*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: 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: i18ndc("kcm_touchpad", "@info:status placeholdermessage text", "No touchpad found")explanation: i18ndc("kcm_touchpad", "@info:usagetip placeholdermessage explanation", "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: i18ndc("kcm_touchpad", "@label:listbox device selection", "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: i18ndc("kcm_touchpad", "@title:group prefix for checkbox group", "General:")text: i18ndc("kcm_touchpad", "@option:check", "Enable device")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: i18ndc("kcm_touchpad", "option:check", "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}}Item {Kirigami.FormData.isSection: false}QQC2.CheckBox {id: leftHandedKirigami.FormData.label: i18ndc("kcm_touchpad", "@title:group", "Left and right buttons:")text: i18ndc("kcm_touchpad", "@option:check", "Swap (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 'both' refers to the left and right buttons'", "Press both simultaneously 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.")}}// Speed sectionKirigami.Separator {Layout.fillWidth: trueKirigami.FormData.isSection: true}// AccelerationRowLayout {Kirigami.FormData.label: i18ndc("kcm_touchpad", "@label:slider and @label:spinbox", "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: i18ndc("kcm_touchpad", "@option:check", "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.")}}// Scrolling sectionKirigami.Separator {Layout.fillWidth: trueKirigami.FormData.isSection: true}// Scroll Speed aka scroll FactorGridLayout {Kirigami.FormData.label: i18ndc("kcm_touchpad", "@label:slider", "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", "@item:inrange Slower Scroll", "Slower")textFormat: Text.PlainText}Item {Layout.fillWidth: true}QQC2.Label {text: i18ndc("kcm_touchpad", "@item:inrange Faster Scroll Speed", "Faster")textFormat: Text.PlainText}}ColumnLayout {id: scrollMethodKirigami.FormData.label: i18ndc("kcm_touchpad", "@title group begins the sentence 'Scroll by moving two fingers anywhere/moving one finger on edge'", "Scroll by:")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: i18ndc("kcm_touchpad", "@option:radio completes the sentence 'Scroll by moving two fingers anywhere'", "Moving two fingers anywhere")enabled: root.device?.supportsScrollTwoFinger ?? falsechecked: root.device?.scrollTwoFinger ?? false}QQC2.RadioButton {id: scrollMethodTouchpadEdgestext: i18ndc("kcm_touchpad", "@option:radio completes the sentence 'Scroll by moving one finger on edge'", "Moving one finger on edge")enabled: root.device?.supportsScrollEdge ?? falsechecked: root.device?.scrollEdge ?? false}}QQC2.CheckBox {id: naturalScrolltext: i18ndc("kcm_touchpad", "@option:check", "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: i18ndc("kcm_touchpad", "@option:check", "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()}}}// Right-click/middle-click sectionKirigami.Separator {Layout.fillWidth: trueKirigami.FormData.isSection: true}ColumnLayout {id: rightClickMethodKirigami.FormData.label: i18ndc("kcm_touchpad", "@title:group for radiobutton group, begins the sentence 'right-click by pressing bottom-right corner/pressing with two fingers'", "Right-click by:")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 Completes the sentence 'right-click by pressing bottom-right corner'", "Pressing 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:usagetip shown below radio button", "Middle-click by pressing both bottom corners."): i18ndc("kcm_touchpad", "@info:usagetip 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 Completes the sentence 'right-click by pressing with two fingers'", "Pressing anywhere 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 anywhere with three fingers.")textFormat: Text.PlainTextelide: Text.ElideRightfont: Kirigami.Theme.smallFont}}ColumnLayout {id: middleClickMethodKirigami.FormData.label: i18ndc("kcm_touchpad", "@label for radiobutton group, begins the sentence 'middle-click by pressing bottom-middle edge/by pressing anywhere with three fingers'", "Middle-click by:")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 completes the sentence 'middle-click by pressing bottom-middle edge'", "Pressing bottom-middle edge")visible: rightClickMethodAreas.checkedchecked: middleClickMethod.enabled && !(root.device?.middleEmulation ?? false)}QQC2.RadioButton {id: middleSoftwareEmulationtext: i18ndc("kcm_touchpad", "@option:radio completes the sentence 'middle-click by pressing bottom-left and bottom-right corners'", "Pressing bottom-left and bottom-right corners")visible: rightClickMethodAreas.checkedchecked: middleClickMethod.enabled && (root.device?.middleEmulation ?? false)}QQC2.CheckBox {id: clickfingerMiddleInfoBoxtext: i18ndc("kcm_touchpad", "@option:check completes the sentence 'middle-click by pressing anywhere with three fingers'", "Pressing anywhere with three fingers")checked: trueenabled: falsevisible: rightClickMethodClickfinger.checked}}// Tap-to-click sectionKirigami.Separator {Layout.fillWidth: trueKirigami.FormData.isSection: true}QQC2.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: i18ndc("kcm_touchpad", "@option:check", "Allow dragging after tapping")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: i18ndc("kcm_touchpad", "@option:check", "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: i18ndc("kcm_touchpad", "@title:group", "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? i18ndc("kcm_touchpad", "@option:radio", "Right-click (three-finger tap to middle-click)"): i18ndc("kcm_touchpad", "@option:radio", "Right-click"))checked: multiTap.enabled && !(root.device?.lmrTapButtonMap ?? false)}QQC2.RadioButton {id: multiTapMiddleClicktext: (root.device?.tapFingerCount > 2? i18ndc("kcm_touchpad", "@option:radio", "Middle-click (three-finger tap to right-click)"): i18ndc("kcm_touchpad", "@option:radio", "Middle-click"))checked: multiTap.enabled && (root.device?.lmrTapButtonMap ?? false)}}}}