Files
Panama/config/dot/quickshell/modules/settings/ShortcutsPage.qml
T

245 lines
9.7 KiB
QML

// Input & Shortcuts.
//
// The shortcut list is generated from `hyprctl binds -j` rather than typed out
// here. The previous version was a hand-maintained array of nineteen entries
// against a real keymap of a hundred and thirteen: it could not show the rest,
// and it went stale the moment a bind changed. Every bind now carries its own
// description in hypr/keybinds.lua, and this page just groups and renders them.
//
// The hardware settings above the list are real controls. Keyboard layout,
// repeat behaviour, and pointer response are Hyprland's, so Panama owns them;
// device-specific configuration stays with GNOME.
import QtQuick
import qs.config
import qs.services
SettingsPage {
id: root
// The chord of the bind currently being re-recorded, in the Lua form; empty
// when nothing is being captured. Held here rather than per row so that
// starting a new capture cancels any other.
property string capturingChord: ""
readonly property string storedXkbOptions: String(DesktopPreferences.get("keyboardOptions") ?? "")
function xkbOptions(): var {
return root.storedXkbOptions
.split(",")
.map(option => option.trim())
.filter(option => option !== "");
}
function currentXkbOption(prefix: string): string {
return root.xkbOptions().find(option => option.indexOf(prefix) === 0) ?? "";
}
function setXkbOption(prefix: string, option: string): void {
const options = root.xkbOptions().filter(option => option.indexOf(prefix) !== 0);
if (option !== "")
options.push(option);
SystemSettings.commitPreference("keyboardOptions", options.join(","));
}
title: "Input & Shortcuts"
lede: "The Forge mental model, carried forward into native tiling."
SettingsCard {
title: "Keyboard"
// These were read-only text, on the grounds that a layout change needed
// a compositor reload. It does not: setting input:kb_variant through
// hl.config re-keymaps attached keyboards immediately -- verified by
// watching active_keymap on a real keyboard change and change back. So
// they are real controls.
TextEntryRow { setting: "keyboardLayout"; placeholder: "us" }
TextEntryRow { setting: "keyboardVariant"; placeholder: "none" }
ChoiceGrid {
width: parent.width
label: "Caps Lock"
detail: "Keep it conventional, or turn a prime keyboard position into Escape or Control"
current: root.currentXkbOption("caps:")
options: [
{ value: "", label: "Standard" },
{ value: "caps:escape_shifted_capslock", label: "Esc · Shift for Caps" },
{ value: "caps:escape", label: "Escape" },
{ value: "caps:ctrl_modifier", label: "Control" }
]
onPicked: value => root.setXkbOption("caps:", value)
}
ChoiceGrid {
width: parent.width
label: "Compose key"
detail: "Type accented characters and symbols with memorable key sequences"
current: root.currentXkbOption("compose:")
options: [
{ value: "", label: "Off" },
{ value: "compose:ralt", label: "Right Alt" },
{ value: "compose:rwin", label: "Right Super" },
{ value: "compose:menu", label: "Menu" }
]
onPicked: value => root.setXkbOption("compose:", value)
}
ChoiceGrid {
width: parent.width
label: "Layout switching"
detail: "Used when Keyboard layout contains more than one comma-separated layout"
current: root.currentXkbOption("grp:")
options: [
{ value: "", label: "Off" },
{ value: "grp:win_space_toggle", label: "Super + Space" },
{ value: "grp:alt_shift_toggle", label: "Alt + Shift" },
{ value: "grp:ctrl_shift_toggle", label: "Ctrl + Shift" },
{ value: "grp:caps_toggle", label: "Caps Lock" }
]
onPicked: value => root.setXkbOption("grp:", value)
}
// Presets preserve every option outside their own category. The raw
// value remains visible for less common xkeyboard-config features.
TextEntryRow { setting: "keyboardOptions"; placeholder: "compose:ralt" }
SliderRow { setting: "keyRepeatDelay" }
SliderRow { setting: "keyRepeatRate" }
ToggleRow { setting: "numlockByDefault"; divider: false }
}
SettingsCard {
title: "Hardware input"
ActionRow {
label: "Mouse, touchpad, and keyboard devices"
detail: "Device-specific settings stay with Fedora's hardware-backed panels"
action: "Open keyboard"
divider: false
onTriggered: SystemSettings.openGnomePanel("keyboard")
}
}
// One card per group, built from what the compositor actually has bound.
//
// Both Repeaters address their model through an explicit id. Inside a
// SettingsCard the surrounding `parent` is the card's internal Column, not
// the card, so `parent.modelData` is undefined there and the rows silently
// never appear -- the cards render with their heading and nothing under it.
Repeater {
model: Keybinds.grouped()
SettingsCard {
id: groupCard
required property var modelData
title: groupCard.modelData.name
subtitle: groupCard.modelData.binds.length === 1
? "1 shortcut"
: `${groupCard.modelData.binds.length} shortcuts`
Repeater {
id: bindRows
model: groupCard.modelData.binds
SettingRow {
id: bindRow
required property var modelData
required property int index
readonly property bool capturing: root.capturingChord === bindRow.modelData.luaChord
readonly property bool overridden: Keybinds.isOverridden(bindRow.modelData.luaChord)
label: bindRow.modelData.description
detail: bindRow.overridden
? "Moved from " + Keybinds.shippedChordFor(bindRow.modelData.luaChord)
: ""
controlWidth: 300
divider: bindRow.index < bindRows.count - 1
Item {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: 300
height: 30
ShortcutCapture {
anchors.right: parent.right
width: 230
height: 30
visible: bindRow.capturing
focus: bindRow.capturing
onCaptured: chord => {
Keybinds.rebind(bindRow.modelData.luaChord, chord);
root.capturingChord = "";
}
onCancelled: root.capturingChord = ""
}
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
visible: !bindRow.capturing
spacing: 8
Text {
anchors.verticalCenter: parent.verticalCenter
text: bindRow.modelData.chord
color: bindRow.overridden ? Theme.accent : Theme.fgDim
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSize
}
SettingsButton {
anchors.verticalCenter: parent.verticalCenter
text: "Change"
enabled: !Keybinds.reloading && !bindRow.modelData.mouse
onClicked: root.capturingChord = bindRow.modelData.luaChord
}
SettingsButton {
anchors.verticalCenter: parent.verticalCenter
visible: bindRow.overridden
text: "Reset"
enabled: !Keybinds.reloading
onClicked: Keybinds.resetBind(bindRow.modelData.luaChord)
}
}
}
}
}
}
}
SettingsCard {
visible: Object.keys(Keybinds.overrides).length > 0
title: "Changed shortcuts"
subtitle: "Rebinding stores only the new chord; what a shortcut does always comes from the desktop's configuration."
ActionRow {
label: "Restore every shipped shortcut"
detail: Object.keys(Keybinds.overrides).length
+ (Object.keys(Keybinds.overrides).length === 1 ? " shortcut moved" : " shortcuts moved")
action: "Restore all"
divider: false
enabled: !Keybinds.reloading
onTriggered: Keybinds.resetAll()
}
}
SettingsCard {
visible: Keybinds.lastError !== ""
title: "Shortcuts unavailable"
subtitle: Keybinds.lastError
ActionRow {
label: "Read the keymap again"
detail: "Shortcuts are read from the running compositor"
action: "Retry"
divider: false
onTriggered: Keybinds.refresh()
}
}
}