// Mouse & Touchpad. // // GNOME splits pointing devices out of Keyboard, and so does this: the settings // are unrelated, and burying pointer speed under a page called "Shortcuts" is // where it was before. These all reach Hyprland's input section, which until now // could only be changed by editing hypr/input.lua by hand. // // The touchpad card renders only when a touchpad is attached. On a desktop it // would be worse than useless -- every switch on it appears to work, because the // preference is stored and the compositor accepts an option for a device class // with no members, so the user would be toggling settings that can never affect // anything with nothing to say so. // // The gestures that were a card of their own now sit at the bottom of the // touchpad card. They are touchpad settings -- a three-finger swipe has nowhere // else to happen -- and a card holding two sliders was a heading standing in // for a section. import QtQuick import qs.config import qs.services SettingsPage { id: root title: "Mouse & Touchpad" lede: InputDevices.hasTouchpad ? "Pointer behavior for your mouse and touchpad." : "Pointer behavior. Touchpad settings appear when a touchpad is attached." // The name-filtered device lists InputDevices publishes. Defaulted here so // the card stays a short honest list even before the service has read the // compositor for the first time. readonly property var realKeyboards: InputDevices.realKeyboards ?? [] readonly property var realMice: InputDevices.realMice ?? [] // Keyboards, then mice, then the touchpad if there is one. Built as one // list so the card can draw dividers between rows without each Repeater // having to know what follows it. readonly property var devices: { const out = []; for (const keyboard of root.realKeyboards) out.push({ glyph: "\u{F030C}", // md-keyboard label: String(keyboard.pretty ?? keyboard.name ?? ""), detail: InputDevices.mainKeyboardLayout === "" ? "Keyboard" : "Keyboard · " + InputDevices.mainKeyboardLayout }); for (const mouse of root.realMice) out.push({ glyph: "\u{F037D}", // md-mouse label: String(mouse.pretty ?? mouse.name ?? ""), detail: "Mouse" }); if (InputDevices.hasTouchpad) out.push({ glyph: "\u{F0621}", // md-gesture-tap label: "Touchpad", detail: "Its own card above, because libinput keeps it separate" }); return out; } readonly property var scrollMethodSpec: PreferenceSchema.spec("scrollMethod") // Scroll button only means something while the method is the held-button // one, and only if the schema still offers that method at all -- the value // comes from what Hyprland's own description of input:scroll_method // accepts, so this asks the schema rather than assuming. readonly property bool heldButtonScrolling: { const options = root.scrollMethodSpec && root.scrollMethodSpec.options ? root.scrollMethodSpec.options : []; if (!options.some(option => option.value === "on_button_down")) return false; return DesktopPreferences.get("scrollMethod") === "on_button_down"; } SettingsCard { title: "Mouse" subtitle: "Applied to every pointing device that is not a touchpad." SliderRow { setting: "pointerSensitivity" } OptionPickerRow { id: accelRow readonly property var spec: PreferenceSchema.spec("accelProfile") label: accelRow.spec ? accelRow.spec.label : "Acceleration" detail: accelRow.spec ? accelRow.spec.detail : "" options: accelRow.spec && accelRow.spec.options ? accelRow.spec.options : [] current: DesktopPreferences.get("accelProfile") onPicked: value => SystemSettings.commitPreference("accelProfile", value) } SliderRow { setting: "scrollFactor" } ToggleRow { setting: "naturalScroll" } ToggleRow { setting: "leftHanded" } ToggleRow { setting: "middleClickPaste" detail: "Paste the primary selection in GTK and native Wayland applications; individual apps may choose not to support it" } OptionPickerRow { id: scrollMethodRow label: root.scrollMethodSpec ? root.scrollMethodSpec.label : "Scroll method" detail: root.scrollMethodSpec ? root.scrollMethodSpec.detail : "" options: root.scrollMethodSpec && root.scrollMethodSpec.options ? root.scrollMethodSpec.options : [] current: DesktopPreferences.get("scrollMethod") divider: scrollButtonRow.visible onPicked: value => SystemSettings.commitPreference("scrollMethod", value) } // Hidden on every other method: the number stays valid and stored, but // a control that governs nothing is exactly what this page exists to // stop shipping. SliderRow { id: scrollButtonRow setting: "scrollButton" visible: root.heldButtonScrolling divider: false } } SettingsCard { visible: InputDevices.hasTouchpad title: "Touchpad" subtitle: "Separate from the mouse on purpose: libinput keeps them apart, and a touchpad and a mouse usually want to scroll in opposite directions." ToggleRow { setting: "touchpadTapToClick" } ToggleRow { setting: "touchpadClickfinger" } ToggleRow { setting: "touchpadTapAndDrag" } ChoiceRow { setting: "touchpadDragLock" } ToggleRow { setting: "touchpadNaturalScroll" } SliderRow { setting: "touchpadScrollFactor" } ToggleRow { setting: "touchpadDisableWhileTyping" } ToggleRow { setting: "touchpadMiddleButtonEmulation" } // Which gestures exist is fixed by the compositor at startup -- three // fingers sideways moves between workspaces, up opens the overview. // What they feel like is here. SliderRow { setting: "swipeDistance" } ToggleRow { setting: "swipeInvert"; divider: false } } SettingsCard { title: "Pointer behavior" OptionPickerRow { id: followMouseRow readonly property var spec: PreferenceSchema.spec("followMouse") label: followMouseRow.spec ? followMouseRow.spec.label : "Pointer focus" detail: followMouseRow.spec ? followMouseRow.spec.detail : "" options: followMouseRow.spec && followMouseRow.spec.options ? followMouseRow.spec.options : [] current: DesktopPreferences.get("followMouse") onPicked: value => SystemSettings.commitPreference("followMouse", value) } OptionPickerRow { id: focusOnCloseRow readonly property var spec: PreferenceSchema.spec("focusOnClose") label: focusOnCloseRow.spec ? focusOnCloseRow.spec.label : "When a window closes" detail: focusOnCloseRow.spec ? focusOnCloseRow.spec.detail : "" options: focusOnCloseRow.spec && focusOnCloseRow.spec.options ? focusOnCloseRow.spec.options : [] current: DesktopPreferences.get("focusOnClose") onPicked: value => SystemSettings.commitPreference("focusOnClose", value) } ToggleRow { setting: "cursorHideWhileTyping" } SliderRow { setting: "cursorInactiveTimeout"; zeroLabel: "Never" } ToggleRow { setting: "cursorWarpOnWorkspaceChange" } SliderRow { setting: "cursorSize"; divider: false } } SettingsCard { title: "Try it" subtitle: "Every setting above applies live — draw and scroll to feel them. Nothing here is saved." InputTestArea {} } SettingsCard { title: "Connected devices" subtitle: "What the compositor sees, minus the phantoms: transceiver siblings, virtual keyboards and consumer-control endpoints that are not devices anybody types on." Repeater { model: root.devices TextRow { id: deviceRow required property var modelData required property int index width: parent ? parent.width : 620 icon: String(deviceRow.modelData.glyph) label: String(deviceRow.modelData.label) detail: String(deviceRow.modelData.detail) divider: deviceRow.index < root.devices.length - 1 } } TextRow { visible: root.devices.length === 0 label: "No input devices reported" detail: "The compositor answered with nothing this page could name" divider: false } } }