1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-08 06:28:27 -04:00

feat(settings): add keyboard configuration tab for Niri (#2937)

* feat(settings): add dedicated keyboard configuration tab

* chore(settings): configure default keyboard settings and clean up embedded niri config

* settings/keyboard: emit niri keyboard config only when set, dedup setup banner, index tab

---------

Co-authored-by: bbedward <bbedward@gmail.com>
This commit is contained in:
Huỳnh Thiện Lộc
2026-08-07 05:40:17 +07:00
committed by GitHub
parent 401872881e
commit dade416ca0
10 changed files with 1145 additions and 165 deletions
+10
View File
@@ -203,6 +203,16 @@ Singleton {
property bool touchpadTapAndDrag: true
property bool touchpadTapToClick: true
property string keyboardLayouts: ""
property string keyboardVariants: ""
property string keyboardModel: ""
property string keyboardOptions: ""
property string keyboardKeymapFile: ""
property string keyboardTrackLayout: ""
property int keyboardRepeatDelay: 0
property int keyboardRepeatRate: 0
property bool keyboardNumlock: false
property int firstDayOfWeek: -1
property bool showWeekNumber: false
property string calendarBackend: "auto"
@@ -57,6 +57,16 @@ var SPEC = {
touchpadTapAndDrag: { def: true, onChange: "updateCompositorInput" },
touchpadTapToClick: { def: true, onChange: "updateCompositorInput" },
keyboardLayouts: { def: "", onChange: "updateCompositorInput" },
keyboardVariants: { def: "", onChange: "updateCompositorInput" },
keyboardModel: { def: "", onChange: "updateCompositorInput" },
keyboardOptions: { def: "", onChange: "updateCompositorInput" },
keyboardKeymapFile: { def: "", onChange: "updateCompositorInput" },
keyboardTrackLayout: { def: "", onChange: "updateCompositorInput" },
keyboardRepeatDelay: { def: 0, onChange: "updateCompositorInput" },
keyboardRepeatRate: { def: 0, onChange: "updateCompositorInput" },
keyboardNumlock: { def: false, onChange: "updateCompositorInput" },
firstDayOfWeek: { def: -1 },
showWeekNumber: { def: false },
calendarBackend: { def: "auto" },
@@ -733,5 +733,20 @@ FocusScope {
Qt.callLater(() => item.forceActiveFocus());
}
}
Loader {
id: keyboardLoader
anchors.fill: parent
active: root.currentIndex === 45
visible: active
focus: active
sourceComponent: KeyboardTab {}
onActiveChanged: {
if (active && item)
Qt.callLater(() => item.forceActiveFocus());
}
}
}
}
@@ -326,6 +326,13 @@ Rectangle {
"tabIndex": 44,
"niriOnly": true
},
{
"id": "keyboard",
"text": I18n.tr("Keyboard"),
"icon": "keyboard",
"tabIndex": 45,
"niriOnly": true
},
{
"id": "locale",
"text": I18n.tr("Locale"),
+267
View File
@@ -0,0 +1,267 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Settings.Widgets
Item {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
DankFlickable {
anchors.fill: parent
clip: true
contentHeight: settingsColumn.height + Theme.spacingXL
contentWidth: width
Column {
id: settingsColumn
topPadding: 4
width: Math.min(550, parent.width - Theme.spacingL * 2)
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingXL
NiriInputSetupBanner {
width: parent.width
}
SettingsCard {
width: parent.width
tags: ["keyboard", "layout", "language", "input", "xkb"]
title: I18n.tr("Keyboard Layouts")
settingKey: "keyboardLayoutsSettings"
iconName: "keyboard"
Column {
width: parent?.width ?? 0
spacing: Theme.spacingS
StyledText {
width: parent.width
text: I18n.tr("Keyboard Layouts")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
width: parent.width
text: I18n.tr("Comma-separated list of layout names. Leave empty to use the system keyboard settings.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
DankTextField {
width: parent.width
text: SettingsData.keyboardLayouts
placeholderText: "us,de"
onTextEdited: SettingsData.set("keyboardLayouts", text)
}
}
SettingsDropdownRow {
tags: ["keyboard", "layout", "switch", "shortcut", "xkb"]
settingKey: "keyboardOptions"
text: I18n.tr("Switch Layout Shortcut")
description: I18n.tr("Choose a shortcut key to cycle between keyboard layouts")
options: [I18n.tr("Alt + Shift"), I18n.tr("Ctrl + Shift"), I18n.tr("Caps Lock"), I18n.tr("Super + Space"), I18n.tr("Custom / None")]
currentValue: {
const opt = SettingsData.keyboardOptions;
if (opt.includes("grp:alt_shift_toggle"))
return options[0];
if (opt.includes("grp:ctrl_shift_toggle"))
return options[1];
if (opt.includes("grp:caps_toggle"))
return options[2];
if (opt.includes("grp:win_space_toggle"))
return options[3];
return options[4];
}
onValueChanged: value => {
const idx = options.indexOf(value);
let opt = SettingsData.keyboardOptions.split(",").filter(o => !o.startsWith("grp:")).join(",");
let newGrp = "";
switch (idx) {
case 0:
newGrp = "grp:alt_shift_toggle";
break;
case 1:
newGrp = "grp:ctrl_shift_toggle";
break;
case 2:
newGrp = "grp:caps_toggle";
break;
case 3:
newGrp = "grp:win_space_toggle";
break;
}
if (newGrp)
opt = opt ? opt + "," + newGrp : newGrp;
SettingsData.set("keyboardOptions", opt);
}
}
Column {
width: parent?.width ?? 0
spacing: Theme.spacingS
StyledText {
width: parent.width
text: I18n.tr("XKB Options")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
width: parent.width
text: I18n.tr("Advanced comma-separated libxkbcommon options.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
DankTextField {
width: parent.width
text: SettingsData.keyboardOptions
placeholderText: "compose:ralt,ctrl:nocaps"
onTextEdited: SettingsData.set("keyboardOptions", text)
}
}
Column {
width: parent?.width ?? 0
spacing: Theme.spacingS
StyledText {
width: parent.width
text: I18n.tr("Keyboard Variant")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
DankTextField {
width: parent.width
text: SettingsData.keyboardVariants
placeholderText: "colemak"
onTextEdited: SettingsData.set("keyboardVariants", text)
}
}
Column {
width: parent?.width ?? 0
spacing: Theme.spacingS
StyledText {
width: parent.width
text: I18n.tr("Keyboard Model")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
DankTextField {
width: parent.width
text: SettingsData.keyboardModel
placeholderText: "pc104"
onTextEdited: SettingsData.set("keyboardModel", text)
}
}
Column {
width: parent?.width ?? 0
spacing: Theme.spacingS
StyledText {
width: parent.width
text: I18n.tr("Keymap File Path")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
width: parent.width
text: I18n.tr("Direct path to a .xkb keymap file. Overrides layouts/variants above.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
DankTextField {
width: parent.width
text: SettingsData.keyboardKeymapFile
placeholderText: "~/.config/keymap.xkb"
onTextEdited: SettingsData.set("keyboardKeymapFile", text)
}
}
}
SettingsCard {
width: parent.width
tags: ["keyboard", "repeat", "delay", "rate", "numlock", "behavior"]
title: I18n.tr("Keyboard Behavior")
settingKey: "keyboardBehaviorSettings"
iconName: "settings"
SettingsButtonGroupRow {
tags: ["keyboard", "track", "layout"]
settingKey: "keyboardTrackLayout"
text: I18n.tr("Remember Layout")
description: I18n.tr("How layout changes are remembered across apps")
model: [I18n.tr("Globally"), I18n.tr("Per Window")]
currentIndex: SettingsData.keyboardTrackLayout === "window" ? 1 : 0
onSelectionChanged: (index, selected) => {
if (!selected)
return;
SettingsData.set("keyboardTrackLayout", index === 1 ? "window" : "global");
}
}
SettingsToggleRow {
tags: ["keyboard", "numlock", "startup"]
settingKey: "keyboardNumlock"
text: I18n.tr("Enable Num Lock")
description: I18n.tr("Automatically turn on Num Lock at startup")
checked: SettingsData.keyboardNumlock
onToggled: checked => SettingsData.set("keyboardNumlock", checked)
}
SettingsSliderRow {
tags: ["keyboard", "repeat", "delay", "speed"]
settingKey: "keyboardRepeatDelay"
text: I18n.tr("Repeat Delay")
description: I18n.tr("Delay before characters start repeating")
value: SettingsData.keyboardRepeatDelay || 600
minimum: 100
maximum: 2000
step: 50
defaultValue: 600
unit: "ms"
onSliderValueChanged: newValue => SettingsData.set("keyboardRepeatDelay", newValue)
}
SettingsSliderRow {
tags: ["keyboard", "repeat", "rate", "speed"]
settingKey: "keyboardRepeatRate"
text: I18n.tr("Repeat Rate")
description: I18n.tr("Characters per second while holding down key")
value: SettingsData.keyboardRepeatRate || 25
minimum: 1
maximum: 100
step: 1
defaultValue: 25
unit: ""
onSliderValueChanged: newValue => SettingsData.set("keyboardRepeatRate", newValue)
}
}
}
}
}
+29 -163
View File
@@ -1,10 +1,8 @@
import QtCore
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Settings.Widgets
import "../../Common/ConfigIncludeResolve.js" as ConfigIncludeResolve
Item {
id: root
@@ -12,93 +10,6 @@ Item {
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property var inputIncludeStatus: ({
"exists": false,
"included": false,
"configFormat": "",
"readOnly": false
})
property bool checkingInclude: false
property bool fixingInclude: false
function getInputConfigPaths() {
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation));
if (CompositorService.compositor !== "niri")
return null;
return {
"configFile": configDir + "/niri/config.kdl",
"layoutFile": configDir + "/niri/dms/input.kdl",
"grepPattern": 'include.*"dms/input.kdl"',
"includeLine": 'include "dms/input.kdl"'
};
}
function checkInputIncludeStatus() {
if (CompositorService.compositor !== "niri") {
inputIncludeStatus = {
"exists": false,
"included": false,
"configFormat": "",
"readOnly": false
};
return;
}
checkingInclude = true;
Proc.runCommand("check-input-include", [Proc.dmsBin, "config", "resolve-include", "niri", "input.kdl"], (output, exitCode) => {
checkingInclude = false;
if (exitCode !== 0) {
inputIncludeStatus = {
"exists": false,
"included": false,
"configFormat": "",
"readOnly": false
};
return;
}
try {
inputIncludeStatus = JSON.parse(output.trim());
} catch (e) {
inputIncludeStatus = {
"exists": false,
"included": false,
"configFormat": "",
"readOnly": false
};
}
});
}
function fixInputInclude() {
const paths = getInputConfigPaths();
if (!paths)
return;
fixingInclude = true;
const unixTime = Math.floor(Date.now() / 1000);
const backupFile = paths.configFile + ".backup" + unixTime;
const script = ConfigIncludeResolve.buildRepairScript({
configFile: paths.configFile,
backupFile: backupFile,
fragmentFile: paths.layoutFile,
grepPattern: paths.grepPattern,
includeLine: paths.includeLine
});
Proc.runCommand("fix-input-include", ["sh", "-c", script], (output, exitCode) => {
fixingInclude = false;
if (exitCode !== 0)
return;
checkInputIncludeStatus();
SettingsData.updateCompositorInput();
});
}
Component.onCompleted: {
if (CompositorService.isNiri) {
checkInputIncludeStatus();
}
}
DankFlickable {
anchors.fill: parent
clip: true
@@ -113,67 +24,8 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingXL
StyledRect {
id: warningBox
NiriInputSetupBanner {
width: parent.width
height: warningContent.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
readonly property bool showSetup: !root.inputIncludeStatus.included
color: showSetup ? Theme.withAlpha(Theme.primary, 0.15) : Theme.withAlpha(Theme.primary, 0)
border.color: showSetup ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.primary, 0)
border.width: 1
visible: showSetup && !root.checkingInclude && CompositorService.isNiri
Row {
id: warningContent
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
DankIcon {
name: "warning"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - (fixButton.visible ? fixButton.width + Theme.spacingM : 0) - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("First Time Setup")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.primary
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
text: I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/input")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
DankButton {
id: fixButton
visible: warningBox.showSetup
text: root.fixingInclude ? I18n.tr("Setting up...") : I18n.tr("Setup")
backgroundColor: Theme.primary
textColor: Theme.primaryText
enabled: !root.fixingInclude
anchors.verticalCenter: parent.verticalCenter
onClicked: root.fixInputInclude()
}
}
}
SettingsCard {
@@ -212,12 +64,15 @@ Item {
description: I18n.tr("Flat uses constant speed; Adaptive scales with movement speed")
model: [I18n.tr("Default"), I18n.tr("Flat"), I18n.tr("Adaptive")]
currentIndex: {
if (SettingsData.mouseAccelProfile === "flat") return 1;
if (SettingsData.mouseAccelProfile === "adaptive") return 2;
if (SettingsData.mouseAccelProfile === "flat")
return 1;
if (SettingsData.mouseAccelProfile === "adaptive")
return 2;
return 0;
}
onSelectionChanged: (index, selected) => {
if (!selected) return;
if (!selected)
return;
const profiles = ["default", "flat", "adaptive"];
SettingsData.set("mouseAccelProfile", profiles[index]);
}
@@ -252,12 +107,15 @@ Item {
description: I18n.tr("Choose when to generate scrolling events")
model: [I18n.tr("Default"), I18n.tr("No Scroll"), I18n.tr("On Button Down")]
currentIndex: {
if (SettingsData.mouseScrollMethod === "no-scroll") return 1;
if (SettingsData.mouseScrollMethod === "on-button-down") return 2;
if (SettingsData.mouseScrollMethod === "no-scroll")
return 1;
if (SettingsData.mouseScrollMethod === "on-button-down")
return 2;
return 0;
}
onSelectionChanged: (index, selected) => {
if (!selected) return;
if (!selected)
return;
const methods = ["default", "no-scroll", "on-button-down"];
SettingsData.set("mouseScrollMethod", methods[index]);
}
@@ -327,12 +185,15 @@ Item {
description: I18n.tr("Flat uses constant speed; Adaptive scales with movement speed")
model: [I18n.tr("Default"), I18n.tr("Flat"), I18n.tr("Adaptive")]
currentIndex: {
if (SettingsData.touchpadAccelProfile === "flat") return 1;
if (SettingsData.touchpadAccelProfile === "adaptive") return 2;
if (SettingsData.touchpadAccelProfile === "flat")
return 1;
if (SettingsData.touchpadAccelProfile === "adaptive")
return 2;
return 0;
}
onSelectionChanged: (index, selected) => {
if (!selected) return;
if (!selected)
return;
const profiles = ["default", "flat", "adaptive"];
SettingsData.set("touchpadAccelProfile", profiles[index]);
}
@@ -367,14 +228,19 @@ Item {
description: I18n.tr("Choose when to generate scrolling events")
model: [I18n.tr("Default"), I18n.tr("Two Finger"), I18n.tr("Edge"), I18n.tr("No Scroll"), I18n.tr("On Button Down")]
currentIndex: {
if (SettingsData.touchpadScrollMethod === "two-finger") return 1;
if (SettingsData.touchpadScrollMethod === "edge") return 2;
if (SettingsData.touchpadScrollMethod === "no-scroll") return 3;
if (SettingsData.touchpadScrollMethod === "on-button-down") return 4;
if (SettingsData.touchpadScrollMethod === "two-finger")
return 1;
if (SettingsData.touchpadScrollMethod === "edge")
return 2;
if (SettingsData.touchpadScrollMethod === "no-scroll")
return 3;
if (SettingsData.touchpadScrollMethod === "on-button-down")
return 4;
return 0;
}
onSelectionChanged: (index, selected) => {
if (!selected) return;
if (!selected)
return;
const methods = ["default", "two-finger", "edge", "no-scroll", "on-button-down"];
SettingsData.set("touchpadScrollMethod", methods[index]);
}
@@ -0,0 +1,156 @@
import QtCore
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
import "../../../Common/ConfigIncludeResolve.js" as ConfigIncludeResolve
StyledRect {
id: root
property var includeStatus: ({
"exists": false,
"included": false,
"configFormat": "",
"readOnly": false
})
property bool checking: false
property bool fixing: false
readonly property bool showSetup: !includeStatus.included
function getInputConfigPaths() {
if (CompositorService.compositor !== "niri")
return null;
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation));
return {
"configFile": configDir + "/niri/config.kdl",
"layoutFile": configDir + "/niri/dms/input.kdl",
"grepPattern": 'include.*"dms/input.kdl"',
"includeLine": 'include "dms/input.kdl"'
};
}
function checkIncludeStatus() {
if (CompositorService.compositor !== "niri") {
includeStatus = {
"exists": false,
"included": false,
"configFormat": "",
"readOnly": false
};
return;
}
checking = true;
Proc.runCommand("check-input-include", [Proc.dmsBin, "config", "resolve-include", "niri", "input.kdl"], (output, exitCode) => {
checking = false;
if (exitCode !== 0) {
includeStatus = {
"exists": false,
"included": false,
"configFormat": "",
"readOnly": false
};
return;
}
try {
includeStatus = JSON.parse(output.trim());
} catch (e) {
includeStatus = {
"exists": false,
"included": false,
"configFormat": "",
"readOnly": false
};
}
});
}
function fixInclude() {
const paths = getInputConfigPaths();
if (!paths)
return;
fixing = true;
const unixTime = Math.floor(Date.now() / 1000);
const backupFile = paths.configFile + ".backup" + unixTime;
const script = ConfigIncludeResolve.buildRepairScript({
configFile: paths.configFile,
backupFile: backupFile,
fragmentFile: paths.layoutFile,
grepPattern: paths.grepPattern,
includeLine: paths.includeLine
});
Proc.runCommand("fix-input-include", ["sh", "-c", script], (output, exitCode) => {
fixing = false;
if (exitCode !== 0)
return;
checkIncludeStatus();
SettingsData.updateCompositorInput();
});
}
Component.onCompleted: {
if (CompositorService.isNiri) {
checkIncludeStatus();
}
}
height: warningContent.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: showSetup ? Theme.withAlpha(Theme.primary, 0.15) : Theme.withAlpha(Theme.primary, 0)
border.color: showSetup ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.primary, 0)
border.width: 1
visible: showSetup && !checking && CompositorService.isNiri
Row {
id: warningContent
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
DankIcon {
name: "warning"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - (fixButton.visible ? fixButton.width + Theme.spacingM : 0) - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("First Time Setup")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.primary
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
text: I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/input")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
DankButton {
id: fixButton
visible: root.showSetup
text: root.fixing ? I18n.tr("Setting up...") : I18n.tr("Setup")
backgroundColor: Theme.primary
textColor: Theme.primaryText
enabled: !root.fixing
anchors.verticalCenter: parent.verticalCenter
onClicked: root.fixInclude()
}
}
}
+51
View File
@@ -1674,6 +1674,16 @@ window-rule {
const touchpadTap = typeof SettingsData !== "undefined" ? SettingsData.touchpadTapToClick : defaultTouchpadTap;
const touchpadTapAndDrag = typeof SettingsData !== "undefined" ? SettingsData.touchpadTapAndDrag : defaultTouchpadTapAndDrag;
const keyboardLayouts = typeof SettingsData !== "undefined" ? SettingsData.keyboardLayouts : "";
const keyboardVariants = typeof SettingsData !== "undefined" ? SettingsData.keyboardVariants : "";
const keyboardModel = typeof SettingsData !== "undefined" ? SettingsData.keyboardModel : "";
const keyboardOptions = typeof SettingsData !== "undefined" ? SettingsData.keyboardOptions : "";
const keyboardKeymapFile = typeof SettingsData !== "undefined" ? SettingsData.keyboardKeymapFile : "";
const keyboardTrackLayout = typeof SettingsData !== "undefined" ? SettingsData.keyboardTrackLayout : "";
const keyboardRepeatDelay = typeof SettingsData !== "undefined" ? SettingsData.keyboardRepeatDelay : 0;
const keyboardRepeatRate = typeof SettingsData !== "undefined" ? SettingsData.keyboardRepeatRate : 0;
const keyboardNumlock = typeof SettingsData !== "undefined" ? SettingsData.keyboardNumlock : false;
const dmsWarning = `// ! DO NOT EDIT !
// ! AUTO-GENERATED BY DMS !
// ! CHANGES WILL BE OVERWRITTEN !
@@ -1741,6 +1751,47 @@ window-rule {
}
inputContent += " }\n";
// Keyboard block — emitted only when configured so untouched settings keep
// niri's locale1 fallback and any keyboard block in the user's own config.
const kdlString = value => `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
const keyboardConfigured = keyboardKeymapFile || keyboardLayouts || keyboardVariants || keyboardModel || keyboardOptions || keyboardTrackLayout || keyboardRepeatDelay > 0 || keyboardRepeatRate > 0 || keyboardNumlock;
if (keyboardConfigured) {
inputContent += " keyboard {\n";
if (keyboardKeymapFile) {
inputContent += " xkb {\n";
inputContent += ` file ${kdlString(keyboardKeymapFile)}\n`;
inputContent += " }\n";
} else if (keyboardLayouts || keyboardVariants || keyboardModel || keyboardOptions) {
inputContent += " xkb {\n";
if (keyboardLayouts) {
inputContent += ` layout ${kdlString(keyboardLayouts)}\n`;
}
if (keyboardVariants) {
inputContent += ` variant ${kdlString(keyboardVariants)}\n`;
}
if (keyboardModel) {
inputContent += ` model ${kdlString(keyboardModel)}\n`;
}
if (keyboardOptions) {
inputContent += ` options ${kdlString(keyboardOptions)}\n`;
}
inputContent += " }\n";
}
if (keyboardTrackLayout) {
inputContent += ` track-layout "${keyboardTrackLayout}"\n`;
}
if (keyboardRepeatDelay > 0) {
inputContent += ` repeat-delay ${keyboardRepeatDelay}\n`;
}
if (keyboardRepeatRate > 0) {
inputContent += ` repeat-rate ${keyboardRepeatRate}\n`;
}
if (keyboardNumlock) {
inputContent += " numlock\n";
}
inputContent += " }\n";
}
inputContent += "}\n";
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation));
@@ -135,10 +135,14 @@ TAB_INDEX_MAP = {
"UsersTab.qml": 35,
"AutoStartTab.qml": 36,
"BatteryTab.qml": 42,
"MouseTouchpadTab.qml": 44,
"KeyboardTab.qml": 45,
}
FILE_CONDITION_MAP = {
"GreeterTab.qml": "greeterAvailable",
"MouseTouchpadTab.qml": "isNiri",
"KeyboardTab.qml": "isNiri",
}
TAB_CATEGORY_MAP = {
@@ -185,6 +189,8 @@ TAB_CATEGORY_MAP = {
41: "Network",
42: "Power & Security",
43: "Dank Dash",
44: "System",
45: "System",
}
SEARCHABLE_COMPONENTS = [
@@ -10921,17 +10921,609 @@
],
"icon": "dashboard"
},
{
"section": "mouseAccelProfile",
"label": "Acceleration Profile",
"tabIndex": 44,
"category": "System",
"keywords": [
"acceleration",
"adaptive",
"constant",
"flat",
"linux",
"mouse",
"movement",
"os",
"profile",
"scales",
"speed",
"system",
"uses"
],
"description": "Flat uses constant speed; Adaptive scales with movement speed",
"conditionKey": "isNiri"
},
{
"section": "touchpadAccelProfile",
"label": "Acceleration Profile",
"tabIndex": 44,
"category": "System",
"keywords": [
"acceleration",
"adaptive",
"constant",
"flat",
"linux",
"movement",
"os",
"profile",
"scales",
"speed",
"system",
"touchpad",
"uses"
],
"description": "Flat uses constant speed; Adaptive scales with movement speed",
"conditionKey": "isNiri"
},
{
"section": "touchpadDisableWhileTyping",
"label": "Disable While Typing",
"tabIndex": 44,
"category": "System",
"keywords": [
"accidental",
"cursor",
"disable",
"dwt",
"jumps",
"linux",
"os",
"prevent",
"system",
"touchpad",
"typing",
"while"
],
"description": "Prevent accidental cursor jumps while typing",
"conditionKey": "isNiri"
},
{
"section": "touchpadDisableOnExternalMouse",
"label": "Disable on External Mouse",
"tabIndex": 44,
"category": "System",
"keywords": [
"connected",
"disable",
"external",
"linux",
"mouse",
"os",
"system",
"touchpad"
],
"description": "Disable touchpad when an external mouse is connected",
"conditionKey": "isNiri"
},
{
"section": "touchpadDragLock",
"label": "Drag Lock",
"tabIndex": 44,
"category": "System",
"keywords": [
"briefly",
"drag",
"dragging",
"finger",
"keep",
"lifted",
"linux",
"lock",
"os",
"system",
"touchpad"
],
"description": "Keep dragging when finger is briefly lifted",
"conditionKey": "isNiri"
},
{
"section": "mouseLeftHanded",
"label": "Left-Handed Mode",
"tabIndex": 44,
"category": "System",
"keywords": [
"button",
"buttons",
"handed",
"left",
"linux",
"mode",
"mouse",
"os",
"primary",
"secondary",
"swap",
"system"
],
"description": "Swap primary and secondary mouse buttons",
"conditionKey": "isNiri"
},
{
"section": "mouseMiddleEmulation",
"label": "Middle Click Emulation",
"tabIndex": 44,
"category": "System",
"keywords": [
"buttons",
"click",
"emulate",
"emulation",
"left",
"linux",
"middle",
"mouse",
"os",
"pressing",
"right",
"system"
],
"description": "Emulate middle click by pressing left and right buttons",
"conditionKey": "isNiri"
},
{
"section": "touchpadMiddleEmulation",
"label": "Middle Click Emulation",
"tabIndex": 44,
"category": "System",
"keywords": [
"buttons",
"click",
"emulate",
"emulation",
"left",
"linux",
"middle",
"os",
"pressing",
"right",
"system",
"touchpad"
],
"description": "Emulate middle click by pressing left and right buttons",
"conditionKey": "isNiri"
},
{
"section": "_tab_44",
"label": "Mouse & Touchpad",
"tabIndex": 44,
"category": "Settings",
"category": "System",
"keywords": [
"linux",
"mouse",
"settings",
"os",
"system",
"touchpad"
],
"icon": "mouse",
"conditionKey": "isNiri"
},
{
"section": "mouseSettings",
"label": "Mouse Settings",
"tabIndex": 44,
"category": "System",
"keywords": [
"acceleration",
"buttons",
"input",
"linux",
"mouse",
"os",
"pointer",
"primary",
"secondary",
"sensitivity",
"settings",
"swap",
"system"
],
"icon": "mouse",
"description": "Swap primary and secondary mouse buttons",
"conditionKey": "isNiri"
},
{
"section": "mouseNaturalScroll",
"label": "Natural Scrolling",
"tabIndex": 44,
"category": "System",
"keywords": [
"direction",
"linux",
"mouse",
"natural",
"os",
"reverse",
"scroll",
"scrolling",
"system",
"wheel"
],
"description": "Reverse mouse wheel scrolling direction",
"conditionKey": "isNiri"
},
{
"section": "touchpadNaturalScroll",
"label": "Natural Scrolling",
"tabIndex": 44,
"category": "System",
"keywords": [
"direction",
"finger",
"linux",
"natural",
"os",
"reverse",
"scroll",
"scrolling",
"system",
"touchpad"
],
"description": "Reverse two-finger scrolling direction",
"conditionKey": "isNiri"
},
{
"section": "mouseAccelSpeed",
"label": "Pointer Speed",
"tabIndex": 44,
"category": "System",
"keywords": [
"accel",
"adjust",
"linux",
"mouse",
"os",
"pointer",
"sensitivity",
"speed",
"system"
],
"description": "Adjust pointer sensitivity speed",
"conditionKey": "isNiri"
},
{
"section": "mouseScrollMethod",
"label": "Scroll Method",
"tabIndex": 44,
"category": "System",
"keywords": [
"choose",
"events",
"generate",
"linux",
"method",
"mouse",
"os",
"scroll",
"scrolling",
"system"
],
"description": "Choose when to generate scrolling events",
"conditionKey": "isNiri"
},
{
"section": "touchpadScrollMethod",
"label": "Scroll Method",
"tabIndex": 44,
"category": "System",
"keywords": [
"choose",
"events",
"generate",
"linux",
"method",
"os",
"scroll",
"scrolling",
"system",
"touchpad"
],
"description": "Choose when to generate scrolling events",
"conditionKey": "isNiri"
},
{
"section": "mouseScrollFactor",
"label": "Scrolling Speed",
"tabIndex": 44,
"category": "System",
"keywords": [
"adjust",
"factor",
"linux",
"mouse",
"multiplier",
"os",
"scroll",
"scrolling",
"sensitivity",
"speed",
"system"
],
"description": "Adjust scrolling sensitivity multiplier",
"conditionKey": "isNiri"
},
{
"section": "touchpadScrollFactor",
"label": "Scrolling Speed",
"tabIndex": 44,
"category": "System",
"keywords": [
"adjust",
"factor",
"linux",
"multiplier",
"os",
"scroll",
"scrolling",
"sensitivity",
"speed",
"system",
"touchpad"
],
"description": "Adjust scrolling sensitivity multiplier",
"conditionKey": "isNiri"
},
{
"section": "touchpadTapAndDrag",
"label": "Tap and Drag",
"tabIndex": 44,
"category": "System",
"keywords": [
"drag",
"items",
"linux",
"move",
"os",
"system",
"tap",
"touchpad"
],
"description": "Tap and drag on the touchpad to move items",
"conditionKey": "isNiri"
},
{
"section": "touchpadTapToClick",
"label": "Tap to Click",
"tabIndex": 44,
"category": "System",
"keywords": [
"click",
"clicks",
"left",
"linux",
"os",
"surface",
"system",
"tap",
"touchpad",
"trigger"
],
"description": "Tap the touchpad surface to trigger left click clicks",
"conditionKey": "isNiri"
},
{
"section": "touchpadSettings",
"label": "Touchpad Settings",
"tabIndex": 44,
"category": "System",
"keywords": [
"click",
"clicks",
"input",
"left",
"linux",
"natural",
"os",
"sensitivity",
"settings",
"surface",
"system",
"tap",
"touchpad",
"trigger"
],
"icon": "trackpad_input_2",
"description": "Tap the touchpad surface to trigger left click clicks",
"conditionKey": "isNiri"
},
{
"section": "touchpadAccelSpeed",
"label": "Touchpad Speed",
"tabIndex": 44,
"category": "System",
"keywords": [
"accel",
"adjust",
"linux",
"os",
"pointer",
"sensitivity",
"speed",
"system",
"touchpad"
],
"description": "Adjust touchpad pointer speed",
"conditionKey": "isNiri"
},
{
"section": "keyboardNumlock",
"label": "Enable Num Lock",
"tabIndex": 45,
"category": "System",
"keywords": [
"automatically",
"enable",
"keyboard",
"linux",
"lock",
"num",
"numlock",
"os",
"startup",
"system",
"turn"
],
"description": "Automatically turn on Num Lock at startup",
"conditionKey": "isNiri"
},
{
"section": "_tab_45",
"label": "Keyboard",
"tabIndex": 45,
"category": "System",
"keywords": [
"keyboard",
"linux",
"os",
"system"
],
"icon": "keyboard",
"conditionKey": "isNiri"
},
{
"section": "keyboardBehaviorSettings",
"label": "Keyboard Behavior",
"tabIndex": 45,
"category": "System",
"keywords": [
"across",
"apps",
"behavior",
"changes",
"delay",
"keyboard",
"layout",
"linux",
"numlock",
"os",
"rate",
"remembered",
"repeat",
"system"
],
"icon": "settings",
"description": "How layout changes are remembered across apps",
"conditionKey": "isNiri"
},
{
"section": "keyboardLayoutsSettings",
"label": "Keyboard Layouts",
"tabIndex": 45,
"category": "System",
"keywords": [
"between",
"choose",
"cycle",
"input",
"keyboard",
"language",
"layout",
"layouts",
"linux",
"os",
"shortcut",
"system",
"xkb"
],
"icon": "keyboard",
"description": "Choose a shortcut key to cycle between keyboard layouts",
"conditionKey": "isNiri"
},
{
"section": "keyboardTrackLayout",
"label": "Remember Layout",
"tabIndex": 45,
"category": "System",
"keywords": [
"across",
"apps",
"changes",
"keyboard",
"layout",
"linux",
"os",
"remember",
"remembered",
"system",
"track"
],
"description": "How layout changes are remembered across apps",
"conditionKey": "isNiri"
},
{
"section": "keyboardRepeatDelay",
"label": "Repeat Delay",
"tabIndex": 45,
"category": "System",
"keywords": [
"before",
"characters",
"delay",
"keyboard",
"linux",
"os",
"repeat",
"repeating",
"speed",
"start",
"system"
],
"description": "Delay before characters start repeating",
"conditionKey": "isNiri"
},
{
"section": "keyboardRepeatRate",
"label": "Repeat Rate",
"tabIndex": 45,
"category": "System",
"keywords": [
"characters",
"down",
"holding",
"keyboard",
"linux",
"os",
"rate",
"repeat",
"second",
"speed",
"system",
"while"
],
"description": "Characters per second while holding down key",
"conditionKey": "isNiri"
},
{
"section": "keyboardOptions",
"label": "Switch Layout Shortcut",
"tabIndex": 45,
"category": "System",
"keywords": [
"between",
"choose",
"cycle",
"keyboard",
"layout",
"layouts",
"linux",
"os",
"shortcut",
"switch",
"system",
"xkb"
],
"description": "Choose a shortcut key to cycle between keyboard layouts",
"conditionKey": "isNiri"
}
]