/*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*/pragma ComponentBehavior: Boundimport QtQuickimport QtQuick.Controls as QQC2import QtQuick.Layoutsimport org.kde.config as KConfig // KAuthorizedimport org.kde.kcmutils as KCMUtilsimport org.kde.kirigami as Kirigamiimport org.kde.plasma.private.kcm_touchpad as TouchpadKCMUtils.SimpleKCM {id: rootreadonly property Touchpad.TouchpadBackend backend: KCMUtils.ConfigModule.backendspacing: Kirigami.Units.smallSpacingproperty Touchpad.InputDevice device: root.backend.inputDevices[KCMUtils.ConfigModule.currentDeviceIndex] ?? nullLayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeftLayoutMirroring.childrenInherit: trueheaderPaddingEnabled: false // Let the InlineMessage touch the edgesheader: ColumnLayout {spacing: 0Message {Layout.fillWidth: truemessage: root.KCMUtils.ConfigModule.saveLoadMessage}Message {Layout.fillWidth: truemessage: root.KCMUtils.ConfigModule.hotplugMessageshowCloseButton: true}}component Message : Kirigami.InlineMessage {required property Touchpad.message messageposition: Kirigami.InlineMessage.Position.Headervisible: message.type !== Touchpad.MessageType.Nonetext: message.texttype: {switch (message.type) {case Touchpad.MessageType.Error:return Kirigami.MessageType.Error;case Touchpad.MessageType.Information:case Touchpad.MessageType.None:default:return Kirigami.MessageType.Information;}}}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: !root.backend.inputDevices?.lengthwidth: parent.width - (Kirigami.Units.largeSpacing * 4)}Kirigami.Form {id: formLayoutvisible: root.backend.inputDevices?.length > 0enabled: visibleKirigami.FormGroup {// DeviceKirigami.FormEntry {visible: deviceSelector.count > 1contentItem: QQC2.ComboBox {Kirigami.FormData.label: i18ndc("kcm_touchpad", "@label:listbox device selection", "Device:")id: deviceSelectorLayout.fillWidth: truemodel: root.backend.inputDevicestextRole: "name"Component.onCompleted: {currentIndex = Qt.binding(() => root.KCMUtils.ConfigModule.currentDeviceIndex);}onActivated: {root.KCMUtils.ConfigModule.currentDeviceIndex = currentIndex;}}}Kirigami.FormSeparator {}// General settingsKirigami.FormEntry {contentItem: QQC2.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 = checked}}}}Kirigami.FormEntry {contentItem: QQC2.CheckBox {id: disableEventsOnExternalMousetext: i18ndc("kcm_touchpad", "@option:check", "Disable while mouse is connected")leftPadding: mirrored ? disableEventsOnExternalMouse.padding : (deviceEnabled.indicator.width + deviceEnabled.spacing)rightPadding: mirrored ? (deviceEnabled.indicator.width + deviceEnabled.spacing) : disableEventsOnExternalMouse.paddingenabled: root.device?.supportsDisableEventsOnExternalMouse ?? falsechecked: enabled && (root.device?.disableEventsOnExternalMouse ?? false)onToggled: {if (root.device) {root.device.disableEventsOnExternalMouse = checked}}}}Kirigami.FormEntry {subtitle: dwt.Accessible.descriptioncontentItem: QQC2.CheckBox {id: dwttext: i18ndc("kcm_touchpad", "option:check", "Disable while typing")Accessible.description: i18ndc("kcm_touchpad", "@label 'this' refers to the 'disable touchpad while typing' feature", "This can interfere with video games.")leftPadding: mirrored ? disableEventsOnExternalMouse.padding : (deviceEnabled.indicator.width + deviceEnabled.spacing)rightPadding: mirrored ? (deviceEnabled.indicator.width + deviceEnabled.spacing) : disableEventsOnExternalMouse.paddingenabled: root.device?.supportsDisableWhileTyping ?? falsechecked: enabled && (root.device?.disableWhileTyping ?? false)onToggled: {if (root.device) {root.device.disableWhileTyping = checked}}}}Kirigami.FormSeparator {}Kirigami.FormEntry {contentItem: 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 = checked}}}}Kirigami.FormEntry {id: middleEmulationFormEntryvisible: root.device?.supportsMiddleEmulation ?? falsecontentItem: QQC2.CheckBox {id: middleEmulationtext: i18ndc("kcm_touchpad", "@option:check 'both' refers to the left and right buttons'", "Press both simultaneously to middle-click")enabled: middleEmulationFormEntry.visible && (root.device?.clickMethodAreas ?? false)checked: enabled && (root.device?.middleEmulation ?? false)onToggled: {if (root.device) {root.device.middleEmulation = checked}}}trailingItems: Kirigami.ContextualHelpButton {toolTipText: middleEmulation.enabled? 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."): i18ndc("kcm_touchpad", "@info:tooltip from ContextualHelpButton", "This setting is not available while βRight-click by pressing anywhere with two fingersβ is selected.")}}Kirigami.FormSeparator {}// AccelerationKirigami.FormEntry {contentItem: RowLayout {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 / 100}}QQC2.Slider {id: accelSpeedSliderLayout.fillWidth: truefrom: 1to: 11stepSize: 1Kirigami.StyleHints.tickMarkStepSize: 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}}}}Kirigami.FormEntry {enabled: root.device?.supportsPointerAccelerationProfileAdaptive ?? falsevisible: enabledcontentItem: QQC2.CheckBox {id: accelProfileEnabledtext: i18ndc("kcm_touchpad", "@option:check", "Enable pointer acceleration")checked: enabled && !(root.device?.pointerAccelerationProfileFlat ?? false)onToggled: {if (root.device) {root.device.pointerAccelerationProfileFlat = !checkedroot.device.pointerAccelerationProfileAdaptive = checked}}}trailingItems: Kirigami.ContextualHelpButton {toolTipText: i18ndc("kcm_touchpad", "@info:tooltip from ContextualHelpButton", "When enabled, pointer travel distance increases with faster movement speed.")}}}// Scrolling sectionKirigami.FormGroup {// Scroll Speed aka scroll FactorKirigami.FormEntry {contentItem: GridLayout {id: scrollProfileKirigami.FormData.label: i18ndc("kcm_touchpad", "@label:slider", "Scrolling speed:")Kirigami.FormData.buddyFor: scrollFactorLayout.fillWidth: truevisible: root.device?.supportsScrollFactor ?? falsecolumns: 4readonly property list<real> values: [0.1,0.3,0.5,0.75,1, // default1.5,2,3,4,5,7,9,12,15,20]QQC2.Slider {id: scrollFactorLayout.fillWidth: trueLayout.columnSpan: 3from: 0to: 14stepSize: 1Kirigami.StyleHints.tickMarkStepSize: 1enabled: root.devicefunction indexOf(val) {const index = scrollProfile.values.indexOf(val)return index === -1 ? scrollProfile.values.indexOf(1) : index}value: indexOf(root.device?.scrollFactor ?? 1)onMoved: {if (root.device) {root.device.scrollFactor = scrollProfile.values[value]}}}//row 2QQC2.SpinBox {id: scrollFactorSpinBoxLayout.minimumWidth: Kirigami.Units.gridUnit * 4from: 0to: 14stepSize: 1function indexOf(val: real): int {const index = scrollProfile.values.indexOf(val)return index === -1 ? scrollProfile.values.indexOf(1) : index}value: indexOf(root.device?.scrollFactor ?? 1)onValueChanged: {if (root.device) {root.device.scrollFactor = scrollProfile.values[value]}}textFromValue: function(val, locale) {return Number(val + 1).toLocaleString(locale, "d", 0)}valueFromText: function(text, locale) {return Number.fromLocaleString(locale, text) - 1}}//row 3QQC2.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}}}Kirigami.FormEntry {title: i18ndc("kcm_touchpad", "@title group begins the sentence 'Scroll by moving two fingers anywhere/moving one finger on edge'", "Scroll by:")contentItem: 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.ButtonGroup {buttons: [scrollMethodTwoFingers, scrollMethodTouchpadEdges]onClicked: {if (root.device) {root.device.scrollTwoFinger = scrollMethodTwoFingers.checkedroot.device.scrollEdge = scrollMethodTouchpadEdges.checked}}}}Kirigami.FormEntry {contentItem: 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}}Kirigami.FormEntry {contentItem: 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 = checked}}}}Kirigami.FormEntry {visible: root.device?.supportsHorizontalScrolling ?? falseenabled: root.device?.supportsHorizontalScrolling ?? falsecontentItem: QQC2.CheckBox {id: disableHorizontalScrollingtext: i18ndc("kcm_touchpad", "@option:check", "Disable horizontal scrolling")checked: enabled && !(root.device?.horizontalScrolling ?? true)onToggled: {if (root.device) {root.device.horizontalScrolling = !checked}}}}}// Right-click/middle-click sectionKirigami.FormGroup {Kirigami.FormEntry {title: 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:")subtitle: rightClickMethodAreas.Accessible.descriptioncontentItem: QQC2.RadioButton {id: rightClickMethodAreastext: (root.device?.leftHanded ?? false? i18ndc("kcm_touchpad", "@option:radio Completes the sentence 'right-click by pressing bottom-left corner'", "Pressing bottom-left corner"): 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: (root.device?.middleEmulation ?? false? 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."))}enabled: (root.device?.supportsClickMethodAreas && root.device?.supportsClickMethodClickfinger) ?? falsevisible: (root.device?.supportsClickMethodAreas || root.device?.supportsClickMethodClickfinger) ?? falseQQC2.ButtonGroup {buttons: [rightClickMethodAreas, rightClickMethodClickfinger]onClicked: {if (root.device) {root.device.clickMethodAreas = rightClickMethodAreas.checked && rightClickMethodAreas.visibleroot.device.clickMethodClickfinger = rightClickMethodClickfinger.checked && rightClickMethodClickfinger.visible}}}}Kirigami.FormEntry {subtitle: rightClickMethodClickfinger.Accessible.descriptioncontentItem: 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: i18ndc("kcm_touchpad", "@info shown below radio button", "Middle-click by pressing anywhere with three fingers.")topPadding: Kirigami.Units.smallSpacing // in lieu of rightClickMethod.spacingenabled: root.device?.supportsClickMethodClickfinger ?? falsechecked: enabled && (root.device?.clickMethodClickfinger ?? false)}enabled: (root.device?.supportsClickMethodAreas && root.device?.supportsClickMethodClickfinger) ?? falsevisible: (root.device?.supportsClickMethodAreas || root.device?.supportsClickMethodClickfinger) ?? false}}// Tap-to-click sectionKirigami.FormGroup {Kirigami.FormEntry {contentItem: 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 = checked}}}}Kirigami.FormEntry {contentItem: QQC2.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 = checked}}}trailingItems: 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.")}}Kirigami.FormEntry {contentItem: 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 = checked}}}}Kirigami.FormEntry {title: i18ndc("kcm_touchpad", "@title:group", "Two-finger tap:")visible: root.device?.supportsLmrTapButtonMapenabled: root.device?.supportsLmrTapButtonMap && tapToClick.checkedcontentItem: QQC2.RadioButton {id: multiTapRightClicktext: i18ndc("kcm_touchpad", "@option:radio", "Right-click")Accessible.description: (root.device?.tapFingerCount > 2? i18ndc("kcm_touchpad", "@info:usagetip shown below radio button", "Middle-click by tapping anywhere with three fingers."): "")checked: enabled && !(root.device?.lmrTapButtonMap ?? false)}subtitle: multiTapRightClick.Accessible.descriptionQQC2.ButtonGroup {buttons: [multiTapRightClick, multiTapMiddleClick]onClicked: {if (root.device) {root.device.lmrTapButtonMap = multiTapMiddleClick.checked}}}}Kirigami.FormEntry {visible: root.device?.supportsLmrTapButtonMapenabled: root.device?.supportsLmrTapButtonMap && tapToClick.checkedcontentItem: QQC2.RadioButton {id: multiTapMiddleClicktext: i18ndc("kcm_touchpad", "@option:radio", "Middle-click")Accessible.description: (root.device?.tapFingerCount > 2? i18ndc("kcm_touchpad", "@info:usagetip shown below radio button", "Right-click by tapping anywhere with three fingers."): "")checked: enabled && (root.device?.lmrTapButtonMap ?? false)}subtitle: multiTapMiddleClick.Accessible.description}}}}