Warning, /plasma/plasma-desktop/kcms/mouse/kcm/libinput/main.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com> 0003 SPDX-FileCopyrightText: 2018 Furkan Tokac <furkantokac34@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 import QtQuick 0009 import QtQuick.Controls as QQC2 0010 import QtQuick.Layouts 0011 0012 import org.kde.kirigami as Kirigami 0013 import org.kde.kquickcontrols 0014 0015 Kirigami.ApplicationItem { 0016 id: root 0017 0018 pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.None 0019 pageStack.columnView.columnResizeMode: Kirigami.ColumnView.SingleColumn 0020 pageStack.defaultColumnWidth: Kirigami.Units.gridUnit * 20 0021 0022 property alias deviceIndex: deviceSelector.currentIndex 0023 signal changeSignal() 0024 0025 property QtObject device 0026 property int deviceCount: backend.deviceCount 0027 0028 property bool loading: false 0029 0030 enabled: deviceCount > 0 0031 0032 function resetModel(index) { 0033 deviceCount = backend.deviceCount 0034 formLayout.enabled = deviceCount 0035 deviceSelector.enabled = deviceCount > 1 0036 0037 loading = true 0038 if (deviceCount) { 0039 device = deviceModel[index] 0040 deviceSelector.model = deviceModel 0041 deviceSelector.currentIndex = index 0042 } else { 0043 deviceSelector.model = [""] 0044 } 0045 loading = false 0046 } 0047 0048 function syncValuesFromBackend() { 0049 loading = true 0050 0051 deviceEnabled.load() 0052 leftHanded.load() 0053 middleEmulation.load() 0054 accelSpeedSpinbox.load() 0055 accelSpeedSlider.load() 0056 accelProfile.load() 0057 naturalScroll.load() 0058 scrollFactor.load() 0059 buttonMappings.load() 0060 0061 loading = false 0062 } 0063 0064 pageStack.initialPage: Kirigami.ScrollablePage { 0065 spacing: Kirigami.Units.smallSpacing 0066 0067 Kirigami.FormLayout { 0068 id: formLayout 0069 enabled: deviceCount 0070 0071 // Device 0072 QQC2.ComboBox { 0073 id: deviceSelector 0074 Kirigami.FormData.label: i18nd("kcmmouse", "Device:") 0075 enabled: deviceCount > 1 0076 Layout.fillWidth: true 0077 model: deviceModel 0078 textRole: "name" 0079 0080 onCurrentIndexChanged: { 0081 if (deviceCount) { 0082 device = deviceModel[currentIndex] 0083 if (!loading) { 0084 changeSignal() 0085 } 0086 } 0087 root.syncValuesFromBackend() 0088 } 0089 } 0090 0091 Item { 0092 Kirigami.FormData.isSection: false 0093 } 0094 0095 // General 0096 QQC2.CheckBox { 0097 id: deviceEnabled 0098 Kirigami.FormData.label: i18nd("kcmmouse", "General:") 0099 text: i18nd("kcmmouse", "Device enabled") 0100 0101 function load() { 0102 if (!formLayout.enabled) { 0103 checked = false 0104 return 0105 } 0106 enabled = device.supportsDisableEvents 0107 checked = enabled && device.enabled 0108 } 0109 0110 onCheckedChanged: { 0111 if (enabled && !root.loading) { 0112 device.enabled = checked 0113 root.changeSignal() 0114 } 0115 } 0116 0117 QQC2.ToolTip.delay: 1000 0118 QQC2.ToolTip.visible: hovered 0119 QQC2.ToolTip.text: i18nd("kcmmouse", "Accept input through this device.") 0120 } 0121 0122 QQC2.CheckBox { 0123 id: leftHanded 0124 text: i18nd("kcmmouse", "Left handed mode") 0125 0126 function load() { 0127 if (!formLayout.enabled) { 0128 checked = false 0129 return 0130 } 0131 enabled = device.supportsLeftHanded 0132 checked = enabled && device.leftHanded 0133 } 0134 0135 onCheckedChanged: { 0136 if (enabled && !root.loading) { 0137 device.leftHanded = checked 0138 root.changeSignal() 0139 } 0140 } 0141 0142 QQC2.ToolTip.delay: 1000 0143 QQC2.ToolTip.visible: hovered 0144 QQC2.ToolTip.text: i18nd("kcmmouse", "Swap left and right buttons.") 0145 } 0146 0147 QQC2.CheckBox { 0148 id: middleEmulation 0149 text: i18nd("kcmmouse", "Press left and right buttons for middle-click") 0150 0151 function load() { 0152 if (!formLayout.enabled) { 0153 checked = false 0154 return 0155 } 0156 enabled = device.supportsMiddleEmulation 0157 checked = enabled && device.middleEmulation 0158 } 0159 0160 onCheckedChanged: { 0161 if (enabled && !root.loading) { 0162 device.middleEmulation = checked 0163 root.changeSignal() 0164 } 0165 } 0166 0167 QQC2.ToolTip.delay: 1000 0168 QQC2.ToolTip.visible: hovered 0169 QQC2.ToolTip.text: i18nd("kcmmouse", "Clicking left and right button simultaneously sends middle button click.") 0170 } 0171 0172 Item { 0173 Kirigami.FormData.isSection: false 0174 } 0175 0176 // Acceleration 0177 RowLayout { 0178 Kirigami.FormData.label: i18nd("kcmmouse", "Pointer speed:") 0179 id: accelSpeed 0180 Layout.fillWidth: true 0181 0182 function onAccelSpeedChanged(val) { 0183 // check slider 0184 if (val != accelSpeedSlider.accelSpeedValue) { 0185 accelSpeedSlider.accelSpeedValue = val 0186 accelSpeedSlider.value = Math.round(6 + (val / 100) / 0.2) 0187 } 0188 0189 // check spinbox 0190 if (val != accelSpeedSpinbox.value) { 0191 accelSpeedSpinbox.value = val 0192 } 0193 0194 // check libinput accelspeed 0195 if ((val / 1000) != device.pointerAcceleration) { 0196 device.pointerAcceleration = val / 100 0197 root.changeSignal() 0198 } 0199 } 0200 0201 QQC2.Slider { 0202 id: accelSpeedSlider 0203 Layout.fillWidth: true 0204 0205 from: 1 0206 to: 11 0207 stepSize: 1 0208 property int accelSpeedValue: 0 // [-100, 100] 0209 0210 function load() { 0211 enabled = device.supportsPointerAcceleration 0212 if (!enabled) { 0213 return 0214 } 0215 0216 accelSpeedValue = Math.round(device.pointerAcceleration * 100) 0217 0218 // convert libinput pointer acceleration range [-1, 1] to slider range [1, 11] 0219 value = Math.round(6 + device.pointerAcceleration / 0.2) 0220 } 0221 0222 onValueChanged: { 0223 if (device != undefined && enabled && !root.loading) { 0224 // convert slider range [1, 11] to accelSpeedValue range [-100, 100] 0225 accelSpeedValue = Math.round(((value - 6) * 0.2) * 100) 0226 0227 accelSpeed.onAccelSpeedChanged(accelSpeedValue) 0228 } 0229 } 0230 } 0231 0232 QQC2.SpinBox { 0233 id: accelSpeedSpinbox 0234 0235 Layout.minimumWidth: Kirigami.Units.gridUnit * 5 0236 0237 from: -100 0238 to: 100 0239 stepSize: 1 0240 editable: true 0241 0242 validator: DoubleValidator { 0243 bottom: accelSpeedSpinbox.from 0244 top: accelSpeedSpinbox.to 0245 } 0246 0247 function load() { 0248 enabled = device.supportsPointerAcceleration 0249 if (!enabled) { 0250 return 0251 } 0252 0253 // if existing configuration or another application set a value with more than 2 decimals 0254 // we reduce the precision to 2 0255 value = Math.round(device.pointerAcceleration * 100) 0256 } 0257 0258 onValueChanged: { 0259 if (device != undefined && enabled && !root.loading) { 0260 accelSpeed.onAccelSpeedChanged(value) 0261 } 0262 } 0263 0264 textFromValue: function(val, locale) { 0265 return Number(val / 100).toLocaleString(locale, "f", 2) 0266 } 0267 0268 valueFromText: function(text, locale) { 0269 return Number.fromLocaleString(locale, text) * 100 0270 } 0271 } 0272 } 0273 0274 ColumnLayout { 0275 id: accelProfile 0276 spacing: Kirigami.Units.smallSpacing 0277 Kirigami.FormData.label: i18nd("kcmmouse", "Pointer acceleration:") 0278 Kirigami.FormData.buddyFor: accelProfileFlat 0279 0280 function load() { 0281 enabled = device.supportsPointerAccelerationProfileAdaptive 0282 0283 if (!enabled) { 0284 accelProfileAdaptive.checked = false 0285 accelProfileFlat.checked = false 0286 return 0287 } 0288 0289 if(device.pointerAccelerationProfileAdaptive) { 0290 accelProfileAdaptive.checked = true 0291 accelProfileFlat.checked = false 0292 } else { 0293 accelProfileAdaptive.checked = false 0294 accelProfileFlat.checked = true 0295 } 0296 } 0297 0298 function syncCurrent() { 0299 if (enabled && !root.loading) { 0300 device.pointerAccelerationProfileFlat = accelProfileFlat.checked 0301 device.pointerAccelerationProfileAdaptive = accelProfileAdaptive.checked 0302 root.changeSignal() 0303 } 0304 } 0305 0306 QQC2.RadioButton { 0307 id: accelProfileFlat 0308 text: i18nd("kcmmouse", "None") 0309 0310 QQC2.ToolTip.delay: 1000 0311 QQC2.ToolTip.visible: hovered 0312 QQC2.ToolTip.text: i18nd("kcmmouse", "Cursor moves the same distance as the mouse movement.") 0313 onCheckedChanged: accelProfile.syncCurrent() 0314 } 0315 0316 QQC2.RadioButton { 0317 id: accelProfileAdaptive 0318 text: i18nd("kcmmouse", "Standard") 0319 0320 QQC2.ToolTip.delay: 1000 0321 QQC2.ToolTip.visible: hovered 0322 QQC2.ToolTip.text: i18nd("kcmmouse", "Cursor travel distance depends on the mouse movement speed.") 0323 onCheckedChanged: accelProfile.syncCurrent() 0324 } 0325 } 0326 0327 Item { 0328 Kirigami.FormData.isSection: false 0329 } 0330 0331 // Scrolling 0332 QQC2.CheckBox { 0333 id: naturalScroll 0334 Kirigami.FormData.label: i18nd("kcmmouse", "Scrolling:") 0335 text: i18nd("kcmmouse", "Invert scroll direction") 0336 0337 function load() { 0338 enabled = device.supportsNaturalScroll 0339 checked = enabled && device.naturalScroll 0340 } 0341 0342 onCheckedChanged: { 0343 if (enabled && !root.loading) { 0344 device.naturalScroll = checked 0345 root.changeSignal() 0346 } 0347 } 0348 0349 QQC2.ToolTip.delay: 1000 0350 QQC2.ToolTip.visible: hovered 0351 QQC2.ToolTip.text: i18nd("kcmmouse", "Touchscreen like scrolling.") 0352 } 0353 0354 // Scroll Speed aka scroll Factor 0355 GridLayout { 0356 Kirigami.FormData.label: i18nd("kcm_touchpad", "Scrolling speed:") 0357 Kirigami.FormData.buddyFor: scrollFactor 0358 Layout.fillWidth: true 0359 0360 columns: 3 0361 0362 QQC2.Slider { 0363 id: scrollFactor 0364 Layout.fillWidth: true 0365 0366 from: 0 0367 to: 14 0368 stepSize: 1 0369 0370 readonly property list<real> values: [ 0371 0.1, 0372 0.3, 0373 0.5, 0374 0.75, 0375 1, // default 0376 1.5, 0377 2, 0378 3, 0379 4, 0380 5, 0381 7, 0382 9, 0383 12, 0384 15, 0385 20 0386 ] 0387 0388 Layout.columnSpan: 3 0389 0390 function load() { 0391 let index = values.indexOf(device.scrollFactor) 0392 if (index === -1) { 0393 index = values.indexOf(1); 0394 } 0395 value = index 0396 } 0397 0398 onMoved: { 0399 device.scrollFactor = values[value] 0400 root.changeSignal() 0401 } 0402 } 0403 0404 //row 2 0405 QQC2.Label { 0406 text: i18ndc("kcmmouse", "Slower Scroll", "Slower") 0407 textFormat: Text.PlainText 0408 } 0409 Item { 0410 Layout.fillWidth: true 0411 } 0412 QQC2.Label { 0413 text: i18ndc("kcmmouse", "Faster Scroll Speed", "Faster") 0414 textFormat: Text.PlainText 0415 } 0416 0417 } 0418 0419 Item { 0420 Kirigami.FormData.isSection: true 0421 } 0422 0423 QQC2.Button { 0424 text: i18ndc("kcmmouse", "@action:button", "Re-bind Additional Mouse Buttons…") 0425 visible: buttonMappings.model.length > 0 || Array.prototype.some.call(deviceModel, device => device.supportedButtons & ~(Qt.LeftButton | Qt.RightButton | Qt.MiddleButton)) 0426 onClicked: root.pageStack.push(buttonPage) 0427 } 0428 } 0429 } 0430 0431 Kirigami.ScrollablePage { 0432 id: buttonPage 0433 visible: false 0434 0435 MouseArea { 0436 // Deliberately using MouseArea on the page instead of a TapHandler on the button, so we can capture clicks anywhere 0437 id: buttonCapture 0438 property var lastButton: {} 0439 0440 anchors.fill: parent 0441 enabled: newBinding.checked 0442 preventStealing: true 0443 acceptedButtons: Qt.AllButtons & ~(Qt.LeftButton | Qt.RightButton | Qt.MiddleButton) 0444 onClicked: { 0445 lastButton = buttonMappings.extraButtons.find(entry => Qt[entry.buttonName] === mouse.button) 0446 newBinding.visible = false 0447 newKeySequenceItem.visible = true 0448 newKeySequenceItem.startCapturing() 0449 } 0450 } 0451 0452 ColumnLayout { 0453 Kirigami.FormLayout { 0454 id: buttonLayout 0455 twinFormLayouts: otherLayout 0456 Repeater { 0457 id: buttonMappings 0458 0459 readonly property var extraButtons: Array.from({length: 24}, (value, index) => ({ 0460 buttonName: "ExtraButton" + (index + 1), 0461 button: Qt["ExtraButton" + (index + 1)], 0462 label: i18ndc("kcmmouse", "@label for assigning an action to a numbered button", "Extra Button %1:", index + 1) 0463 })) 0464 0465 function load() { 0466 model = Qt.binding(() => extraButtons.filter(entry => backend.buttonMapping.hasOwnProperty(entry.buttonName))) 0467 } 0468 0469 delegate: KeySequenceItem { 0470 Kirigami.FormData.label: modelData.label 0471 0472 keySequence: backend.buttonMapping[modelData.buttonName] 0473 0474 modifierlessAllowed: true 0475 multiKeyShortcutsAllowed: false 0476 checkForConflictsAgainst: ShortcutType.None 0477 0478 onCaptureFinished: { 0479 const copy = backend.buttonMapping; 0480 copy[modelData.buttonName] = keySequence 0481 backend.buttonMapping = copy 0482 root.changeSignal() 0483 } 0484 } 0485 } 0486 } 0487 0488 Kirigami.InlineMessage { 0489 id: explanationLabel 0490 Layout.fillWidth: true 0491 visible: newBinding.checked || newKeySequenceItem.visible 0492 text: newBinding.visible ? i18ndc("kcmmouse","@action:button", "Press the mouse button for which you want to add a key binding") : 0493 i18ndc("kcmmouse","@action:button, %1 is the translation of 'Extra Button %1' from above", "Enter the new key combination for %1", buttonCapture.lastButton.label) 0494 actions: [ 0495 Kirigami.Action { 0496 icon.name: "dialog-cancel" 0497 text: i18ndc("kcmmouse", "@action:button", "Cancel") 0498 onTriggered: { 0499 newKeySequenceItem.visible = false; 0500 newBinding.visible = true 0501 newBinding.checked = false 0502 } 0503 } 0504 ] 0505 } 0506 0507 Kirigami.FormLayout { 0508 id: otherLayout 0509 twinFormLayouts: buttonLayout 0510 0511 QQC2.Button { 0512 id: newBinding 0513 checkable: true 0514 text: checked ? i18ndc("kcmmouse", "@action:button", "Press a mouse button ") : 0515 i18ndc("kcmmouse", "@action:button, Bind a mousebutton to keyboard key(s)", "Add Binding…") 0516 icon.name: "list-add" 0517 } 0518 KeySequenceItem { 0519 id: newKeySequenceItem 0520 visible: false 0521 0522 modifierlessAllowed: true 0523 multiKeyShortcutsAllowed: false 0524 checkForConflictsAgainst: ShortcutType.None 0525 0526 onCaptureFinished: { 0527 visible = false 0528 newBinding.visible = true 0529 newBinding.checked = false 0530 const copy = backend.buttonMapping; 0531 copy[buttonCapture.lastButton.buttonName] = keySequence 0532 backend.buttonMapping = copy 0533 root.changeSignal() 0534 } 0535 } 0536 0537 0538 Item { 0539 Kirigami.FormData.isSection: true 0540 } 0541 0542 QQC2.Button { 0543 onClicked: root.pageStack.pop() 0544 text: i18ndc("kcmmouse", "@action:button", "Go back") 0545 icon.name: "go-previous" 0546 } 0547 } 0548 } 0549 } 0550 }