mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
Compare commits
2 Commits
8161fd6acb
...
v0.0.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
841e55d37f | ||
|
|
287dda5675 |
@@ -34,27 +34,28 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: timeLoader
|
id: timeWeatherLoader
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.currentIndex === 1
|
active: root.currentIndex === 1
|
||||||
visible: active
|
visible: active
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
sourceComponent: TimeTab {
|
sourceComponent: TimeWeatherTab {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: weatherLoader
|
id: keybindsLoader
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.currentIndex === 2
|
active: root.currentIndex === 2
|
||||||
visible: active
|
visible: active
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
sourceComponent: WeatherTab {
|
sourceComponent: KeybindsTab {
|
||||||
|
parentModal: root.parentModal
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ Rectangle {
|
|||||||
"text": "Personalization",
|
"text": "Personalization",
|
||||||
"icon": "person"
|
"icon": "person"
|
||||||
}, {
|
}, {
|
||||||
"text": "Time & Date",
|
"text": "Time & Weather",
|
||||||
"icon": "schedule"
|
"icon": "schedule"
|
||||||
}, {
|
}, {
|
||||||
"text": "Weather",
|
"text": "Key Bindings",
|
||||||
"icon": "cloud"
|
"icon": "keyboard"
|
||||||
}, {
|
}, {
|
||||||
"text": "Dank Bar",
|
"text": "Dank Bar",
|
||||||
"icon": "toolbar"
|
"icon": "toolbar"
|
||||||
|
|||||||
181
Modules/Settings/KeybindsTab.qml
Normal file
181
Modules/Settings/KeybindsTab.qml
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: keybindsTab
|
||||||
|
|
||||||
|
property var parentModal: null
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: bindingsModel
|
||||||
|
}
|
||||||
|
|
||||||
|
function addBinding() {
|
||||||
|
console.log("Adding binding, current count:", bindingsModel.count)
|
||||||
|
bindingsModel.append({
|
||||||
|
token: "",
|
||||||
|
configType: "dms",
|
||||||
|
actionName: "",
|
||||||
|
args: [],
|
||||||
|
shellCmd: "",
|
||||||
|
repeatEnabled: true,
|
||||||
|
cooldownMs: 0,
|
||||||
|
allowWhenLocked: false,
|
||||||
|
overlayTitle: ""
|
||||||
|
})
|
||||||
|
console.log("After append, count:", bindingsModel.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBinding(index) {
|
||||||
|
bindingsModel.remove(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportBindings() {
|
||||||
|
const lines = []
|
||||||
|
for (let i = 0; i < bindingsRepeater.count; i++) {
|
||||||
|
const item = bindingsRepeater.itemAt(i)
|
||||||
|
if (item) {
|
||||||
|
const line = item.toConfigLine()
|
||||||
|
if (line) {
|
||||||
|
const existingToken = line.split(" {")[0].trim()
|
||||||
|
const duplicate = lines.some(l => l.split(" {")[0].trim() === existingToken)
|
||||||
|
if (duplicate) {
|
||||||
|
console.warn("Duplicate binding found:", existingToken)
|
||||||
|
}
|
||||||
|
lines.push(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const block = "binds {\n " + lines.join("\n ") + "\n}\n"
|
||||||
|
console.log("Exported bindings block:")
|
||||||
|
console.log(block)
|
||||||
|
return block
|
||||||
|
}
|
||||||
|
|
||||||
|
DankFlickable {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: Theme.spacingL
|
||||||
|
clip: true
|
||||||
|
contentHeight: mainColumn.height
|
||||||
|
contentWidth: width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: mainColumn
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: headerSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: headerSection
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "keyboard"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - Theme.spacingM - addButton.width - Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Key Bindings Editor"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Configure keyboard shortcuts, mouse bindings, and scroll gestures"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
id: addButton
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
circular: false
|
||||||
|
iconName: "add"
|
||||||
|
iconSize: Theme.iconSize
|
||||||
|
iconColor: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onClicked: keybindsTab.addBinding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Bindings count: " + bindingsModel.count
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
visible: bindingsModel.count === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: bindingsRepeater
|
||||||
|
model: bindingsModel
|
||||||
|
|
||||||
|
delegate: KeybindRow {
|
||||||
|
width: parent.width
|
||||||
|
token: model.token || ""
|
||||||
|
configType: model.configType || "dms"
|
||||||
|
actionName: model.actionName || ""
|
||||||
|
args: model.args || []
|
||||||
|
shellCmd: model.shellCmd || ""
|
||||||
|
repeatEnabled: model.repeatEnabled ?? true
|
||||||
|
cooldownMs: model.cooldownMs || 0
|
||||||
|
allowWhenLocked: model.allowWhenLocked ?? false
|
||||||
|
overlayTitle: model.overlayTitle || ""
|
||||||
|
panelWindow: keybindsTab.parentModal
|
||||||
|
onRemoveRequested: keybindsTab.removeBinding(index)
|
||||||
|
onChanged: {
|
||||||
|
bindingsModel.set(index, {
|
||||||
|
token: token,
|
||||||
|
configType: configType,
|
||||||
|
actionName: actionName,
|
||||||
|
args: args,
|
||||||
|
shellCmd: shellCmd,
|
||||||
|
repeatEnabled: repeatEnabled,
|
||||||
|
cooldownMs: cooldownMs,
|
||||||
|
allowWhenLocked: allowWhenLocked,
|
||||||
|
overlayTitle: overlayTitle
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: Theme.spacingXL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
addBinding()
|
||||||
|
}
|
||||||
|
}
|
||||||
717
Modules/Settings/TimeWeatherTab.qml
Normal file
717
Modules/Settings/TimeWeatherTab.qml
Normal file
@@ -0,0 +1,717 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: timeWeatherTab
|
||||||
|
|
||||||
|
DankFlickable {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: Theme.spacingL
|
||||||
|
clip: true
|
||||||
|
contentHeight: mainColumn.height
|
||||||
|
contentWidth: width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: mainColumn
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: timeSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: timeSection
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "schedule"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - Theme.spacingM - toggle.width - Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "24-Hour Format"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Use 24-hour time format instead of 12-hour AM/PM"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
id: toggle
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
checked: SettingsData.use24HourClock
|
||||||
|
onToggled: checked => {
|
||||||
|
return SettingsData.setClockFormat(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: dateSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: dateSection
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "calendar_today"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Date Format"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
width: parent.width
|
||||||
|
height: 50
|
||||||
|
text: "Top Bar Format"
|
||||||
|
description: "Preview: " + (SettingsData.clockDateFormat ? new Date().toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) : new Date().toLocaleDateString(Qt.locale(), "ddd d"))
|
||||||
|
currentValue: {
|
||||||
|
if (!SettingsData.clockDateFormat || SettingsData.clockDateFormat.length === 0) {
|
||||||
|
return "System Default"
|
||||||
|
}
|
||||||
|
const presets = [{
|
||||||
|
"format": "ddd d",
|
||||||
|
"label": "Day Date"
|
||||||
|
}, {
|
||||||
|
"format": "ddd MMM d",
|
||||||
|
"label": "Day Month Date"
|
||||||
|
}, {
|
||||||
|
"format": "MMM d",
|
||||||
|
"label": "Month Date"
|
||||||
|
}, {
|
||||||
|
"format": "M/d",
|
||||||
|
"label": "Numeric (M/D)"
|
||||||
|
}, {
|
||||||
|
"format": "d/M",
|
||||||
|
"label": "Numeric (D/M)"
|
||||||
|
}, {
|
||||||
|
"format": "ddd d MMM yyyy",
|
||||||
|
"label": "Full with Year"
|
||||||
|
}, {
|
||||||
|
"format": "yyyy-MM-dd",
|
||||||
|
"label": "ISO Date"
|
||||||
|
}, {
|
||||||
|
"format": "dddd, MMMM d",
|
||||||
|
"label": "Full Day & Month"
|
||||||
|
}]
|
||||||
|
const match = presets.find(p => {
|
||||||
|
return p.format === SettingsData.clockDateFormat
|
||||||
|
})
|
||||||
|
return match ? match.label : "Custom: " + SettingsData.clockDateFormat
|
||||||
|
}
|
||||||
|
options: ["System Default", "Day Date", "Day Month Date", "Month Date", "Numeric (M/D)", "Numeric (D/M)", "Full with Year", "ISO Date", "Full Day & Month", "Custom..."]
|
||||||
|
onValueChanged: value => {
|
||||||
|
const formatMap = {
|
||||||
|
"System Default": "",
|
||||||
|
"Day Date": "ddd d",
|
||||||
|
"Day Month Date": "ddd MMM d",
|
||||||
|
"Month Date": "MMM d",
|
||||||
|
"Numeric (M/D)": "M/d",
|
||||||
|
"Numeric (D/M)": "d/M",
|
||||||
|
"Full with Year": "ddd d MMM yyyy",
|
||||||
|
"ISO Date": "yyyy-MM-dd",
|
||||||
|
"Full Day & Month": "dddd, MMMM d"
|
||||||
|
}
|
||||||
|
if (value === "Custom...") {
|
||||||
|
customFormatInput.visible = true
|
||||||
|
} else {
|
||||||
|
customFormatInput.visible = false
|
||||||
|
SettingsData.setClockDateFormat(formatMap[value])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
width: parent.width
|
||||||
|
height: 50
|
||||||
|
text: "Lock Screen Format"
|
||||||
|
description: "Preview: " + (SettingsData.lockDateFormat ? new Date().toLocaleDateString(Qt.locale(), SettingsData.lockDateFormat) : new Date().toLocaleDateString(Qt.locale(), Locale.LongFormat))
|
||||||
|
currentValue: {
|
||||||
|
if (!SettingsData.lockDateFormat || SettingsData.lockDateFormat.length === 0) {
|
||||||
|
return "System Default"
|
||||||
|
}
|
||||||
|
const presets = [{
|
||||||
|
"format": "ddd d",
|
||||||
|
"label": "Day Date"
|
||||||
|
}, {
|
||||||
|
"format": "ddd MMM d",
|
||||||
|
"label": "Day Month Date"
|
||||||
|
}, {
|
||||||
|
"format": "MMM d",
|
||||||
|
"label": "Month Date"
|
||||||
|
}, {
|
||||||
|
"format": "M/d",
|
||||||
|
"label": "Numeric (M/D)"
|
||||||
|
}, {
|
||||||
|
"format": "d/M",
|
||||||
|
"label": "Numeric (D/M)"
|
||||||
|
}, {
|
||||||
|
"format": "ddd d MMM yyyy",
|
||||||
|
"label": "Full with Year"
|
||||||
|
}, {
|
||||||
|
"format": "yyyy-MM-dd",
|
||||||
|
"label": "ISO Date"
|
||||||
|
}, {
|
||||||
|
"format": "dddd, MMMM d",
|
||||||
|
"label": "Full Day & Month"
|
||||||
|
}]
|
||||||
|
const match = presets.find(p => {
|
||||||
|
return p.format === SettingsData.lockDateFormat
|
||||||
|
})
|
||||||
|
return match ? match.label : "Custom: " + SettingsData.lockDateFormat
|
||||||
|
}
|
||||||
|
options: ["System Default", "Day Date", "Day Month Date", "Month Date", "Numeric (M/D)", "Numeric (D/M)", "Full with Year", "ISO Date", "Full Day & Month", "Custom..."]
|
||||||
|
onValueChanged: value => {
|
||||||
|
const formatMap = {
|
||||||
|
"System Default": "",
|
||||||
|
"Day Date": "ddd d",
|
||||||
|
"Day Month Date": "ddd MMM d",
|
||||||
|
"Month Date": "MMM d",
|
||||||
|
"Numeric (M/D)": "M/d",
|
||||||
|
"Numeric (D/M)": "d/M",
|
||||||
|
"Full with Year": "ddd d MMM yyyy",
|
||||||
|
"ISO Date": "yyyy-MM-dd",
|
||||||
|
"Full Day & Month": "dddd, MMMM d"
|
||||||
|
}
|
||||||
|
if (value === "Custom...") {
|
||||||
|
customLockFormatInput.visible = true
|
||||||
|
} else {
|
||||||
|
customLockFormatInput.visible = false
|
||||||
|
SettingsData.setLockDateFormat(formatMap[value])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: customFormatInput
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
visible: false
|
||||||
|
placeholderText: "Enter custom top bar format (e.g., ddd MMM d)"
|
||||||
|
text: SettingsData.clockDateFormat
|
||||||
|
onTextChanged: {
|
||||||
|
if (visible && text)
|
||||||
|
SettingsData.setClockDateFormat(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: customLockFormatInput
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
visible: false
|
||||||
|
placeholderText: "Enter custom lock screen format (e.g., dddd, MMMM d)"
|
||||||
|
text: SettingsData.lockDateFormat
|
||||||
|
onTextChanged: {
|
||||||
|
if (visible && text)
|
||||||
|
SettingsData.setLockDateFormat(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: formatHelp.implicitHeight + Theme.spacingM * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: formatHelp
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Format Legend"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.primary
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: (parent.width - Theme.spacingL) / 2
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "• d - Day (1-31)"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "• dd - Day (01-31)"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "• ddd - Day name (Mon)"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "• dddd - Day name (Monday)"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "• M - Month (1-12)"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: (parent.width - Theme.spacingL) / 2
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "• MM - Month (01-12)"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "• MMM - Month (Jan)"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "• MMMM - Month (January)"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "• yy - Year (24)"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "• yyyy - Year (2024)"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: enableWeatherSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: enableWeatherSection
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "cloud"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - Theme.spacingM - enableToggle.width - Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Enable Weather"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Show weather information in top bar and control center"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
id: enableToggle
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
checked: SettingsData.weatherEnabled
|
||||||
|
onToggled: checked => {
|
||||||
|
return SettingsData.setWeatherEnabled(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: temperatureSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
border.width: 0
|
||||||
|
visible: SettingsData.weatherEnabled
|
||||||
|
opacity: visible ? 1 : 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: temperatureSection
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "thermostat"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - Theme.spacingM - temperatureToggle.width - Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Use Fahrenheit"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Use Fahrenheit instead of Celsius for temperature"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
id: temperatureToggle
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
checked: SettingsData.useFahrenheit
|
||||||
|
onToggled: checked => {
|
||||||
|
return SettingsData.setTemperatureUnit(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
width: parent.width
|
||||||
|
height: locationSection.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
border.width: 0
|
||||||
|
visible: SettingsData.weatherEnabled
|
||||||
|
opacity: visible ? 1 : 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: locationSection
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "location_on"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - Theme.spacingM - autoLocationToggle.width - Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Auto Location"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Automatically determine your location using your IP address"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
id: autoLocationToggle
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
checked: SettingsData.useAutoLocation
|
||||||
|
onToggled: checked => {
|
||||||
|
return SettingsData.setAutoLocation(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: !SettingsData.useAutoLocation
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.outline
|
||||||
|
opacity: 0.2
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Custom Location"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Latitude"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: latitudeInput
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
placeholderText: "40.7128"
|
||||||
|
backgroundColor: Theme.surfaceVariant
|
||||||
|
normalBorderColor: Theme.primarySelected
|
||||||
|
focusedBorderColor: Theme.primary
|
||||||
|
keyNavigationTab: longitudeInput
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (SettingsData.weatherCoordinates) {
|
||||||
|
const coords = SettingsData.weatherCoordinates.split(',')
|
||||||
|
if (coords.length > 0) {
|
||||||
|
text = coords[0].trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onWeatherCoordinatesChanged() {
|
||||||
|
if (SettingsData.weatherCoordinates) {
|
||||||
|
const coords = SettingsData.weatherCoordinates.split(',')
|
||||||
|
if (coords.length > 0) {
|
||||||
|
latitudeInput.text = coords[0].trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTextEdited: {
|
||||||
|
if (text && longitudeInput.text) {
|
||||||
|
const coords = text + "," + longitudeInput.text
|
||||||
|
SettingsData.weatherCoordinates = coords
|
||||||
|
SettingsData.saveSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Longitude"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: longitudeInput
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
placeholderText: "-74.0060"
|
||||||
|
backgroundColor: Theme.surfaceVariant
|
||||||
|
normalBorderColor: Theme.primarySelected
|
||||||
|
focusedBorderColor: Theme.primary
|
||||||
|
keyNavigationTab: locationSearchInput
|
||||||
|
keyNavigationBacktab: latitudeInput
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (SettingsData.weatherCoordinates) {
|
||||||
|
const coords = SettingsData.weatherCoordinates.split(',')
|
||||||
|
if (coords.length > 1) {
|
||||||
|
text = coords[1].trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onWeatherCoordinatesChanged() {
|
||||||
|
if (SettingsData.weatherCoordinates) {
|
||||||
|
const coords = SettingsData.weatherCoordinates.split(',')
|
||||||
|
if (coords.length > 1) {
|
||||||
|
longitudeInput.text = coords[1].trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTextEdited: {
|
||||||
|
if (text && latitudeInput.text) {
|
||||||
|
const coords = latitudeInput.text + "," + text
|
||||||
|
SettingsData.weatherCoordinates = coords
|
||||||
|
SettingsData.saveSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Location Search"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankLocationSearch {
|
||||||
|
id: locationSearchInput
|
||||||
|
width: parent.width
|
||||||
|
currentLocation: ""
|
||||||
|
placeholderText: "New York, NY"
|
||||||
|
keyNavigationBacktab: longitudeInput
|
||||||
|
onLocationSelected: (displayName, coordinates) => {
|
||||||
|
SettingsData.setWeatherLocation(displayName, coordinates)
|
||||||
|
|
||||||
|
const coords = coordinates.split(',')
|
||||||
|
if (coords.length >= 2) {
|
||||||
|
latitudeInput.text = coords[0].trim()
|
||||||
|
longitudeInput.text = coords[1].trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,6 +82,7 @@ Singleton {
|
|||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
detectCompositor()
|
detectCompositor()
|
||||||
|
NiriService.generateNiriLayoutConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterCurrentWorkspace(toplevels, screen) {
|
function filterCurrentWorkspace(toplevels, screen) {
|
||||||
@@ -192,6 +193,7 @@ Singleton {
|
|||||||
root.isHyprland = false
|
root.isHyprland = false
|
||||||
root.compositor = "niri"
|
root.compositor = "niri"
|
||||||
console.log("CompositorService: Detected Niri with socket:", root.niriSocket)
|
console.log("CompositorService: Detected Niri with socket:", root.niriSocket)
|
||||||
|
NiriService.generateNiriBinds()
|
||||||
} else {
|
} else {
|
||||||
root.isHyprland = false
|
root.isHyprland = false
|
||||||
root.isNiri = true
|
root.isNiri = true
|
||||||
@@ -200,4 +202,4 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,12 @@ pragma Singleton
|
|||||||
|
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -31,6 +33,7 @@ Singleton {
|
|||||||
property bool suppressConfigToast: true
|
property bool suppressConfigToast: true
|
||||||
property bool suppressNextConfigToast: false
|
property bool suppressNextConfigToast: false
|
||||||
property bool matugenSuppression: false
|
property bool matugenSuppression: false
|
||||||
|
property bool configGenerationPending: false
|
||||||
|
|
||||||
readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
|
readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
|
||||||
|
|
||||||
@@ -412,13 +415,13 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function doScreenTransition() {
|
function doScreenTransition() {
|
||||||
send({
|
return send({
|
||||||
"Action": {
|
"Action": {
|
||||||
"DoScreenTransition": {
|
"DoScreenTransition": {
|
||||||
"delay_ms": 0,
|
"delay_ms": 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchToWorkspace(workspaceIndex) {
|
function switchToWorkspace(workspaceIndex) {
|
||||||
@@ -652,6 +655,7 @@ Singleton {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: suppressToastTimer
|
id: suppressToastTimer
|
||||||
interval: 3000
|
interval: 3000
|
||||||
@@ -663,4 +667,101 @@ Singleton {
|
|||||||
interval: 2000
|
interval: 2000
|
||||||
onTriggered: root.matugenSuppression = false
|
onTriggered: root.matugenSuppression = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: configGenerationDebounce
|
||||||
|
interval: 100
|
||||||
|
onTriggered: root.doGenerateNiriLayoutConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNiriLayoutConfig() {
|
||||||
|
const niriSocket = Quickshell.env("NIRI_SOCKET")
|
||||||
|
if (!niriSocket || niriSocket.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configGenerationPending) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configGenerationPending = true
|
||||||
|
configGenerationDebounce.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
function doGenerateNiriLayoutConfig() {
|
||||||
|
console.log("NiriService: Generating layout config...")
|
||||||
|
|
||||||
|
const cornerRadius = typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12
|
||||||
|
const gaps = typeof SettingsData !== "undefined" ? Math.max(4, SettingsData.dankBarSpacing) : 4
|
||||||
|
|
||||||
|
const configContent = `layout {
|
||||||
|
gaps ${gaps}
|
||||||
|
|
||||||
|
border {
|
||||||
|
width 2
|
||||||
|
}
|
||||||
|
|
||||||
|
focus-ring {
|
||||||
|
width 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window-rule {
|
||||||
|
geometry-corner-radius ${cornerRadius}
|
||||||
|
clip-to-geometry true
|
||||||
|
tiled-state true
|
||||||
|
draw-border-with-background false
|
||||||
|
}`
|
||||||
|
|
||||||
|
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
|
||||||
|
const niriDmsDir = configDir + "/niri/dms"
|
||||||
|
const configPath = niriDmsDir + "/layout.kdl"
|
||||||
|
|
||||||
|
writeConfigProcess.configContent = configContent
|
||||||
|
writeConfigProcess.configPath = configPath
|
||||||
|
writeConfigProcess.command = ["sh", "-c", `mkdir -p "${niriDmsDir}" && cat > "${configPath}" << 'EOF'\n${configContent}\nEOF`]
|
||||||
|
writeConfigProcess.running = true
|
||||||
|
configGenerationPending = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNiriBinds() {
|
||||||
|
console.log("NiriService: Generating binds config...")
|
||||||
|
|
||||||
|
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation))
|
||||||
|
const niriDmsDir = configDir + "/niri/dms"
|
||||||
|
const bindsPath = niriDmsDir + "/binds.kdl"
|
||||||
|
const sourceBindsPath = Paths.strip(Qt.resolvedUrl("niri-binds.kdl"))
|
||||||
|
|
||||||
|
writeBindsProcess.bindsPath = bindsPath
|
||||||
|
writeBindsProcess.command = ["sh", "-c", `mkdir -p "${niriDmsDir}" && cp "${sourceBindsPath}" "${bindsPath}"`]
|
||||||
|
writeBindsProcess.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: writeConfigProcess
|
||||||
|
property string configContent: ""
|
||||||
|
property string configPath: ""
|
||||||
|
|
||||||
|
onExited: exitCode => {
|
||||||
|
if (exitCode === 0) {
|
||||||
|
console.log("NiriService: Generated layout config at", configPath)
|
||||||
|
} else {
|
||||||
|
console.warn("NiriService: Failed to write layout config, exit code:", exitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: writeBindsProcess
|
||||||
|
property string bindsPath: ""
|
||||||
|
|
||||||
|
onExited: exitCode => {
|
||||||
|
if (exitCode === 0) {
|
||||||
|
console.log("NiriService: Generated binds config at", bindsPath)
|
||||||
|
} else {
|
||||||
|
console.warn("NiriService: Failed to write binds config, exit code:", exitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
55
Services/niri-binds.kdl
Normal file
55
Services/niri-binds.kdl
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
binds {
|
||||||
|
Mod+Space hotkey-overlay-title="Application Launcher" {
|
||||||
|
spawn "dms" "ipc" "call" "spotlight" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod+V hotkey-overlay-title="Clipboard Manager" {
|
||||||
|
spawn "dms" "ipc" "call" "clipboard" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod+M hotkey-overlay-title="Task Manager" {
|
||||||
|
spawn "dms" "ipc" "call" "processlist" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod+Comma hotkey-overlay-title="Settings" {
|
||||||
|
spawn "dms" "ipc" "call" "settings" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod+N hotkey-overlay-title="Notification Center" {
|
||||||
|
spawn "dms" "ipc" "call" "notifications" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod+Shift+N hotkey-overlay-title="Notepad" {
|
||||||
|
spawn "dms" "ipc" "call" "notepad" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod+Alt+L hotkey-overlay-title="Lock Screen" {
|
||||||
|
spawn "dms" "ipc" "call" "lock" "lock";
|
||||||
|
}
|
||||||
|
|
||||||
|
Ctrl+Alt+Delete hotkey-overlay-title="Task Manager" {
|
||||||
|
spawn "dms" "ipc" "call" "processlist" "toggle";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
XF86AudioRaiseVolume allow-when-locked=true {
|
||||||
|
spawn "dms" "ipc" "call" "audio" "increment" "3";
|
||||||
|
}
|
||||||
|
XF86AudioLowerVolume allow-when-locked=true {
|
||||||
|
spawn "dms" "ipc" "call" "audio" "decrement" "3";
|
||||||
|
}
|
||||||
|
XF86AudioMute allow-when-locked=true {
|
||||||
|
spawn "dms" "ipc" "call" "audio" "mute";
|
||||||
|
}
|
||||||
|
XF86AudioMicMute allow-when-locked=true {
|
||||||
|
spawn "dms" "ipc" "call" "audio" "micmute";
|
||||||
|
}
|
||||||
|
|
||||||
|
// BL
|
||||||
|
XF86MonBrightnessUp allow-when-locked=true {
|
||||||
|
spawn "dms" "ipc" "call" "brightness" "increment" "5" "";
|
||||||
|
}
|
||||||
|
XF86MonBrightnessDown allow-when-locked=true {
|
||||||
|
spawn "dms" "ipc" "call" "brightness" "decrement" "5" "";
|
||||||
|
}
|
||||||
|
}
|
||||||
141
Widgets/ActionEditor.qml
Normal file
141
Widgets/ActionEditor.qml
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string actionName: ""
|
||||||
|
property var args: []
|
||||||
|
property string shellCmd: ""
|
||||||
|
|
||||||
|
signal changed()
|
||||||
|
|
||||||
|
implicitWidth: actionRow.implicitWidth
|
||||||
|
implicitHeight: actionRow.implicitHeight
|
||||||
|
|
||||||
|
readonly property var niriActions: [
|
||||||
|
"spawn",
|
||||||
|
"spawn-sh",
|
||||||
|
"quit",
|
||||||
|
"close-window",
|
||||||
|
"focus-workspace-down",
|
||||||
|
"focus-workspace-up",
|
||||||
|
"focus-window-down",
|
||||||
|
"focus-window-up",
|
||||||
|
"focus-window-left",
|
||||||
|
"focus-window-right",
|
||||||
|
"move-window-down",
|
||||||
|
"move-window-up",
|
||||||
|
"move-window-left",
|
||||||
|
"move-window-right",
|
||||||
|
"fullscreen-window",
|
||||||
|
"toggle-window-floating",
|
||||||
|
"screenshot",
|
||||||
|
"screenshot-screen",
|
||||||
|
"screenshot-window",
|
||||||
|
"do-screen-transition",
|
||||||
|
"toggle-keyboard-shortcuts-inhibit"
|
||||||
|
]
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: actionRow
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
id: actionBox
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
text: "Action"
|
||||||
|
currentValue: root.actionName || "Select action..."
|
||||||
|
options: root.niriActions
|
||||||
|
onValueChanged: value => {
|
||||||
|
root.actionName = value
|
||||||
|
if (value === "spawn") {
|
||||||
|
if (root.args.length === 0) {
|
||||||
|
root.args = [""]
|
||||||
|
}
|
||||||
|
} else if (value !== "spawn-sh") {
|
||||||
|
root.args = []
|
||||||
|
root.shellCmd = ""
|
||||||
|
}
|
||||||
|
root.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: root.actionName === "spawn" && root.args.length > 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.actionName === "spawn" ? root.args.length : 0
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
placeholderText: index === 0 ? "program/binary" : "arg " + index
|
||||||
|
text: root.args[index] || ""
|
||||||
|
onTextChanged: {
|
||||||
|
if (root.args[index] !== text) {
|
||||||
|
root.args[index] = text
|
||||||
|
root.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
width: 36
|
||||||
|
height: 36
|
||||||
|
circular: false
|
||||||
|
iconName: "add"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.primary
|
||||||
|
onClicked: {
|
||||||
|
root.args.push("")
|
||||||
|
root.args = root.args
|
||||||
|
root.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
width: 36
|
||||||
|
height: 36
|
||||||
|
circular: false
|
||||||
|
iconName: "remove"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.error
|
||||||
|
enabled: root.args.length > 1
|
||||||
|
onClicked: {
|
||||||
|
if (root.args.length > 1) {
|
||||||
|
root.args.pop()
|
||||||
|
root.args = root.args
|
||||||
|
root.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
visible: root.actionName === "spawn-sh"
|
||||||
|
placeholderText: "shell command"
|
||||||
|
text: root.shellCmd
|
||||||
|
onTextChanged: {
|
||||||
|
if (root.shellCmd !== text) {
|
||||||
|
root.shellCmd = text
|
||||||
|
root.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
207
Widgets/KeyCaptureField.qml
Normal file
207
Widgets/KeyCaptureField.qml
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool recording: false
|
||||||
|
property string capturedToken: ""
|
||||||
|
|
||||||
|
signal captured(string xkbToken)
|
||||||
|
|
||||||
|
implicitWidth: 320
|
||||||
|
implicitHeight: captureRow.implicitHeight
|
||||||
|
|
||||||
|
function startRecording() {
|
||||||
|
root.recording = true
|
||||||
|
captureField.forceActiveFocus()
|
||||||
|
root.capturedToken = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopRecording(cancel) {
|
||||||
|
root.recording = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function modsFromEvent(mods) {
|
||||||
|
const result = []
|
||||||
|
if (mods & Qt.ControlModifier) result.push("Ctrl")
|
||||||
|
if (mods & Qt.ShiftModifier) result.push("Shift")
|
||||||
|
if (mods & Qt.AltModifier) result.push("Alt")
|
||||||
|
if (mods & Qt.MetaModifier) result.push("Mod")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function xkbKeyFromQtKey(qk) {
|
||||||
|
if (qk >= Qt.Key_A && qk <= Qt.Key_Z) {
|
||||||
|
return String.fromCharCode(qk)
|
||||||
|
}
|
||||||
|
if (qk >= Qt.Key_0 && qk <= Qt.Key_9) {
|
||||||
|
return String.fromCharCode(qk)
|
||||||
|
}
|
||||||
|
|
||||||
|
const map = {
|
||||||
|
[Qt.Key_Left]: "Left",
|
||||||
|
[Qt.Key_Right]: "Right",
|
||||||
|
[Qt.Key_Up]: "Up",
|
||||||
|
[Qt.Key_Down]: "Down",
|
||||||
|
[Qt.Key_Comma]: "Comma",
|
||||||
|
[Qt.Key_Period]: "Period",
|
||||||
|
[Qt.Key_Slash]: "Slash",
|
||||||
|
[Qt.Key_Semicolon]: "Semicolon",
|
||||||
|
[Qt.Key_Apostrophe]: "Apostrophe",
|
||||||
|
[Qt.Key_BracketLeft]: "BracketLeft",
|
||||||
|
[Qt.Key_BracketRight]: "BracketRight",
|
||||||
|
[Qt.Key_Backslash]: "Backslash",
|
||||||
|
[Qt.Key_Minus]: "Minus",
|
||||||
|
[Qt.Key_Equal]: "Equal",
|
||||||
|
[Qt.Key_QuoteLeft]: "grave",
|
||||||
|
[Qt.Key_Space]: "space",
|
||||||
|
[Qt.Key_Print]: "Print",
|
||||||
|
[Qt.Key_Return]: "Return",
|
||||||
|
[Qt.Key_Enter]: "Return",
|
||||||
|
[Qt.Key_Tab]: "Tab",
|
||||||
|
[Qt.Key_Backspace]: "BackSpace",
|
||||||
|
[Qt.Key_Delete]: "Delete",
|
||||||
|
[Qt.Key_Insert]: "Insert",
|
||||||
|
[Qt.Key_Home]: "Home",
|
||||||
|
[Qt.Key_End]: "End",
|
||||||
|
[Qt.Key_PageUp]: "Page_Up",
|
||||||
|
[Qt.Key_PageDown]: "Page_Down"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qk >= Qt.Key_F1 && qk <= Qt.Key_F35) {
|
||||||
|
return "F" + (qk - Qt.Key_F1 + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[qk] || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatToken(mods, key) {
|
||||||
|
return (mods.length ? mods.join("+") + "+" : "") + key
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: captureRow
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
FocusScope {
|
||||||
|
id: captureField
|
||||||
|
width: parent.width - recordButton.width - Theme.spacingM
|
||||||
|
height: 48
|
||||||
|
focus: root.recording
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: captureBackground
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: root.recording ? Theme.primaryContainer : Theme.surfaceVariant
|
||||||
|
border.color: root.recording ? Theme.primary : Theme.primarySelected
|
||||||
|
border.width: root.recording ? 2 : 1
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
text: root.capturedToken || (root.recording ? "Press keys / click / scroll... (Esc cancels)" : "Click Record to capture")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: root.capturedToken ? Theme.surfaceText : Theme.surfaceVariantText
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on border.color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
if (!root.recording) return
|
||||||
|
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
root.stopRecording(true)
|
||||||
|
event.accepted = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const onlyModifier = [Qt.Key_Control, Qt.Key_Shift, Qt.Key_Alt, Qt.Key_Meta].includes(event.key)
|
||||||
|
if (onlyModifier) {
|
||||||
|
event.accepted = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const mods = root.modsFromEvent(event.modifiers)
|
||||||
|
const key = root.xkbKeyFromQtKey(event.key)
|
||||||
|
if (key) {
|
||||||
|
const token = root.formatToken(mods, key)
|
||||||
|
root.capturedToken = token
|
||||||
|
root.captured(token)
|
||||||
|
root.stopRecording(false)
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.AllButtons
|
||||||
|
enabled: root.recording
|
||||||
|
onTapped: (eventPoint, button) => {
|
||||||
|
const mods = root.modsFromEvent(eventPoint.modifiers)
|
||||||
|
let key = ""
|
||||||
|
if (button === Qt.LeftButton) key = "MouseLeft"
|
||||||
|
else if (button === Qt.RightButton) key = "MouseRight"
|
||||||
|
else if (button === Qt.MiddleButton) key = "MouseMiddle"
|
||||||
|
else if (button === Qt.BackButton) key = "MouseBack"
|
||||||
|
else if (button === Qt.ForwardButton) key = "MouseForward"
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
const token = root.formatToken(mods, key)
|
||||||
|
root.capturedToken = token
|
||||||
|
root.captured(token)
|
||||||
|
root.stopRecording(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WheelHandler {
|
||||||
|
enabled: root.recording
|
||||||
|
onWheel: (event) => {
|
||||||
|
const mods = root.modsFromEvent(event.modifiers)
|
||||||
|
let key = ""
|
||||||
|
if (Math.abs(event.angleDelta.y) >= Math.abs(event.angleDelta.x)) {
|
||||||
|
key = event.angleDelta.y < 0 ? "WheelScrollDown" : "WheelScrollUp"
|
||||||
|
} else {
|
||||||
|
key = event.angleDelta.x < 0 ? "WheelScrollRight" : "WheelScrollLeft"
|
||||||
|
}
|
||||||
|
const token = root.formatToken(mods, key)
|
||||||
|
root.capturedToken = token
|
||||||
|
root.captured(token)
|
||||||
|
root.stopRecording(false)
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
id: recordButton
|
||||||
|
width: 48
|
||||||
|
height: 48
|
||||||
|
circular: false
|
||||||
|
iconName: recording ? "close" : "radio_button_checked"
|
||||||
|
iconSize: Theme.iconSize
|
||||||
|
iconColor: recording ? Theme.error : Theme.primary
|
||||||
|
onClicked: recording ? root.stopRecording(true) : root.startRecording()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
643
Widgets/KeybindRow.qml
Normal file
643
Widgets/KeybindRow.qml
Normal file
@@ -0,0 +1,643 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: row
|
||||||
|
|
||||||
|
property string token: ""
|
||||||
|
property string configType: "dms"
|
||||||
|
property string actionName: ""
|
||||||
|
property var args: []
|
||||||
|
property string shellCmd: ""
|
||||||
|
property bool repeatEnabled: true
|
||||||
|
property int cooldownMs: 0
|
||||||
|
property bool allowWhenLocked: false
|
||||||
|
property string overlayTitle: ""
|
||||||
|
property bool expanded: false
|
||||||
|
property bool recording: false
|
||||||
|
property var panelWindow: null
|
||||||
|
|
||||||
|
signal removeRequested()
|
||||||
|
signal changed()
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: mainContent.height
|
||||||
|
|
||||||
|
function startRecording() {
|
||||||
|
recording = true
|
||||||
|
captureScope.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopRecording() {
|
||||||
|
recording = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function modsFromEvent(mods) {
|
||||||
|
const result = []
|
||||||
|
if (mods & Qt.ControlModifier) result.push("Ctrl")
|
||||||
|
if (mods & Qt.ShiftModifier) result.push("Shift")
|
||||||
|
if (mods & Qt.AltModifier) result.push("Alt")
|
||||||
|
if (mods & Qt.MetaModifier) result.push("Mod")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function xkbKeyFromQtKey(qk) {
|
||||||
|
if (qk >= Qt.Key_A && qk <= Qt.Key_Z) {
|
||||||
|
return String.fromCharCode(qk)
|
||||||
|
}
|
||||||
|
if (qk >= Qt.Key_0 && qk <= Qt.Key_9) {
|
||||||
|
return String.fromCharCode(qk)
|
||||||
|
}
|
||||||
|
|
||||||
|
const map = {
|
||||||
|
[Qt.Key_Left]: "Left",
|
||||||
|
[Qt.Key_Right]: "Right",
|
||||||
|
[Qt.Key_Up]: "Up",
|
||||||
|
[Qt.Key_Down]: "Down",
|
||||||
|
[Qt.Key_Comma]: "Comma",
|
||||||
|
[Qt.Key_Period]: "Period",
|
||||||
|
[Qt.Key_Slash]: "Slash",
|
||||||
|
[Qt.Key_Semicolon]: "Semicolon",
|
||||||
|
[Qt.Key_Apostrophe]: "Apostrophe",
|
||||||
|
[Qt.Key_BracketLeft]: "BracketLeft",
|
||||||
|
[Qt.Key_BracketRight]: "BracketRight",
|
||||||
|
[Qt.Key_Backslash]: "Backslash",
|
||||||
|
[Qt.Key_Minus]: "Minus",
|
||||||
|
[Qt.Key_Equal]: "Equal",
|
||||||
|
[Qt.Key_QuoteLeft]: "grave",
|
||||||
|
[Qt.Key_Space]: "space",
|
||||||
|
[Qt.Key_Print]: "Print",
|
||||||
|
[Qt.Key_Return]: "Return",
|
||||||
|
[Qt.Key_Enter]: "Return",
|
||||||
|
[Qt.Key_Tab]: "Tab",
|
||||||
|
[Qt.Key_Backspace]: "BackSpace",
|
||||||
|
[Qt.Key_Delete]: "Delete",
|
||||||
|
[Qt.Key_Insert]: "Insert",
|
||||||
|
[Qt.Key_Home]: "Home",
|
||||||
|
[Qt.Key_End]: "End",
|
||||||
|
[Qt.Key_PageUp]: "Page_Up",
|
||||||
|
[Qt.Key_PageDown]: "Page_Down"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qk >= Qt.Key_F1 && qk <= Qt.Key_F35) {
|
||||||
|
return "F" + (qk - Qt.Key_F1 + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[qk] || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatToken(mods, key) {
|
||||||
|
return (mods.length ? mods.join("+") + "+" : "") + key
|
||||||
|
}
|
||||||
|
|
||||||
|
function toConfigLine() {
|
||||||
|
if (!row.token || !row.actionName) return ""
|
||||||
|
|
||||||
|
const flags = []
|
||||||
|
if (!row.repeatEnabled) flags.push("repeat=false")
|
||||||
|
if (row.cooldownMs > 0) flags.push(`cooldown-ms=${row.cooldownMs}`)
|
||||||
|
if (row.allowWhenLocked) flags.push("allow-when-locked=true")
|
||||||
|
if (row.overlayTitle === "null") flags.push("hotkey-overlay-title=null")
|
||||||
|
else if (row.overlayTitle.length > 0) {
|
||||||
|
const escaped = row.overlayTitle.replace(/"/g, '\\"')
|
||||||
|
flags.push(`hotkey-overlay-title="${escaped}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let actionStr = ""
|
||||||
|
if (row.actionName === "spawn") {
|
||||||
|
const parts = (row.args || []).filter(s => s && s.length > 0)
|
||||||
|
.map(s => `"${s.replace(/"/g, '\\"')}"`).join(" ")
|
||||||
|
actionStr = `{ spawn ${parts}; }`
|
||||||
|
} else if (row.actionName === "spawn-sh") {
|
||||||
|
const escaped = (row.shellCmd || "").replace(/"/g, '\\"')
|
||||||
|
actionStr = `{ spawn-sh "${escaped}"; }`
|
||||||
|
} else {
|
||||||
|
actionStr = `{ ${row.actionName}; }`
|
||||||
|
}
|
||||||
|
|
||||||
|
const head = flags.length ? `${row.token} ${flags.join(" ")} ` : `${row.token} `
|
||||||
|
return head + actionStr
|
||||||
|
}
|
||||||
|
|
||||||
|
ShortcutsInhibitor {
|
||||||
|
window: row.panelWindow
|
||||||
|
enabled: row.recording
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
id: mainContent
|
||||||
|
width: parent.width
|
||||||
|
height: mainColumn.height
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: mainColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
height: 60
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
padding: Theme.spacingM
|
||||||
|
|
||||||
|
FocusScope {
|
||||||
|
id: captureScope
|
||||||
|
width: 180
|
||||||
|
height: 48
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
focus: row.recording
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: row.recording ? Theme.primaryContainer : Theme.surfaceVariant
|
||||||
|
border.color: row.recording ? Theme.primary : Theme.primarySelected
|
||||||
|
border.width: row.recording ? 2 : 1
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
text: row.token || (row.recording ? "Press combo..." : "Not set")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: row.token ? Theme.surfaceText : Theme.surfaceVariantText
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on border.color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
if (!row.recording) return
|
||||||
|
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
row.stopRecording()
|
||||||
|
event.accepted = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const onlyModifier = [Qt.Key_Control, Qt.Key_Shift, Qt.Key_Alt, Qt.Key_Meta].includes(event.key)
|
||||||
|
if (onlyModifier) {
|
||||||
|
event.accepted = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const mods = row.modsFromEvent(event.modifiers)
|
||||||
|
const key = row.xkbKeyFromQtKey(event.key)
|
||||||
|
if (key) {
|
||||||
|
const token = row.formatToken(mods, key)
|
||||||
|
row.token = token
|
||||||
|
row.changed()
|
||||||
|
row.stopRecording()
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.AllButtons
|
||||||
|
enabled: row.recording
|
||||||
|
onTapped: (eventPoint, button) => {
|
||||||
|
const mods = row.modsFromEvent(eventPoint.modifiers)
|
||||||
|
let key = ""
|
||||||
|
if (button === Qt.LeftButton) key = "MouseLeft"
|
||||||
|
else if (button === Qt.RightButton) key = "MouseRight"
|
||||||
|
else if (button === Qt.MiddleButton) key = "MouseMiddle"
|
||||||
|
else if (button === Qt.BackButton) key = "MouseBack"
|
||||||
|
else if (button === Qt.ForwardButton) key = "MouseForward"
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
const token = row.formatToken(mods, key)
|
||||||
|
row.token = token
|
||||||
|
row.changed()
|
||||||
|
row.stopRecording()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WheelHandler {
|
||||||
|
enabled: row.recording
|
||||||
|
onWheel: (event) => {
|
||||||
|
const mods = row.modsFromEvent(event.modifiers)
|
||||||
|
let key = ""
|
||||||
|
if (Math.abs(event.angleDelta.y) >= Math.abs(event.angleDelta.x)) {
|
||||||
|
key = event.angleDelta.y < 0 ? "WheelScrollDown" : "WheelScrollUp"
|
||||||
|
} else {
|
||||||
|
key = event.angleDelta.x < 0 ? "WheelScrollRight" : "WheelScrollLeft"
|
||||||
|
}
|
||||||
|
const token = row.formatToken(mods, key)
|
||||||
|
row.token = token
|
||||||
|
row.changed()
|
||||||
|
row.stopRecording()
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
id: recordButton
|
||||||
|
width: 36
|
||||||
|
height: 36
|
||||||
|
circular: false
|
||||||
|
iconName: recording ? "close" : "radio_button_checked"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: recording ? Theme.error : Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onClicked: recording ? row.stopRecording() : row.startRecording()
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - captureScope.width - recordButton.width - expandButton.width - deleteButton.width - parent.padding * 2 - Theme.spacingM * 4
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (row.configType === "dms") {
|
||||||
|
return row.actionName || "DMS Action..."
|
||||||
|
} else if (row.configType === "compositor") {
|
||||||
|
return row.actionName || "Compositor Action..."
|
||||||
|
} else {
|
||||||
|
if (row.args.length > 0 && row.args[0]) {
|
||||||
|
return row.args[0]
|
||||||
|
}
|
||||||
|
return "Custom Command..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (row.configType === "dms") {
|
||||||
|
return "DMS"
|
||||||
|
} else if (row.configType === "compositor") {
|
||||||
|
return "Compositor"
|
||||||
|
} else {
|
||||||
|
return "Custom Command"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
id: expandButton
|
||||||
|
width: 36
|
||||||
|
height: 36
|
||||||
|
circular: false
|
||||||
|
iconName: row.expanded ? "expand_less" : "expand_more"
|
||||||
|
iconSize: Theme.iconSize
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onClicked: row.expanded = !row.expanded
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
id: deleteButton
|
||||||
|
width: 36
|
||||||
|
height: 36
|
||||||
|
circular: false
|
||||||
|
iconName: "delete"
|
||||||
|
iconSize: Theme.iconSize
|
||||||
|
iconColor: Theme.error
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onClicked: row.removeRequested()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
padding: Theme.spacingM
|
||||||
|
visible: row.expanded
|
||||||
|
opacity: row.expanded ? 1 : 0
|
||||||
|
|
||||||
|
DankButtonGroup {
|
||||||
|
width: parent.width - parent.padding * 2
|
||||||
|
model: ["dms", "compositor", "command"]
|
||||||
|
currentIndex: {
|
||||||
|
if (row.configType === "dms") return 0
|
||||||
|
if (row.configType === "compositor") return 1
|
||||||
|
if (row.configType === "command") return 2
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (selected) {
|
||||||
|
if (index === 0) row.configType = "dms"
|
||||||
|
else if (index === 1) row.configType = "compositor"
|
||||||
|
else if (index === 2) row.configType = "command"
|
||||||
|
row.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - parent.padding * 2
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: row.configType === "dms"
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
text: "DMS Action"
|
||||||
|
currentValue: row.actionName || "Select action..."
|
||||||
|
options: [
|
||||||
|
"Spotlight Launcher",
|
||||||
|
"App Drawer",
|
||||||
|
"Control Center",
|
||||||
|
"Notifications",
|
||||||
|
"Settings",
|
||||||
|
"Power Menu",
|
||||||
|
"Lock Screen",
|
||||||
|
"Screenshot"
|
||||||
|
]
|
||||||
|
onValueChanged: value => {
|
||||||
|
row.actionName = value
|
||||||
|
row.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - parent.padding * 2
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: row.configType === "compositor"
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
text: "Compositor Action"
|
||||||
|
currentValue: row.actionName || "Select action..."
|
||||||
|
options: [
|
||||||
|
"quit",
|
||||||
|
"close-window",
|
||||||
|
"focus-workspace-down",
|
||||||
|
"focus-workspace-up",
|
||||||
|
"focus-window-down",
|
||||||
|
"focus-window-up",
|
||||||
|
"focus-window-left",
|
||||||
|
"focus-window-right",
|
||||||
|
"move-window-down",
|
||||||
|
"move-window-up",
|
||||||
|
"move-window-left",
|
||||||
|
"move-window-right",
|
||||||
|
"fullscreen-window",
|
||||||
|
"toggle-window-floating",
|
||||||
|
"screenshot",
|
||||||
|
"screenshot-screen",
|
||||||
|
"screenshot-window",
|
||||||
|
"do-screen-transition",
|
||||||
|
"toggle-keyboard-shortcuts-inhibit"
|
||||||
|
]
|
||||||
|
onValueChanged: value => {
|
||||||
|
row.actionName = value
|
||||||
|
row.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - parent.padding * 2
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: row.configType === "command"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Command"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
text: row.args.length > 0 ? row.args[0] : ""
|
||||||
|
placeholderText: "Enter command (e.g., kitty, firefox)"
|
||||||
|
onTextChanged: {
|
||||||
|
if (row.args.length === 0) {
|
||||||
|
row.args = [text]
|
||||||
|
} else {
|
||||||
|
row.args[0] = text
|
||||||
|
}
|
||||||
|
row.actionName = "spawn"
|
||||||
|
row.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Arguments (optional)"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Math.max(1, row.args.length - 1)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: parent.width - removeArgButton.width - Theme.spacingS
|
||||||
|
height: 40
|
||||||
|
text: row.args.length > index + 1 ? row.args[index + 1] : ""
|
||||||
|
placeholderText: "Argument " + (index + 1)
|
||||||
|
onTextChanged: {
|
||||||
|
if (row.args.length > index + 1) {
|
||||||
|
row.args[index + 1] = text
|
||||||
|
} else {
|
||||||
|
row.args.push(text)
|
||||||
|
}
|
||||||
|
row.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
id: removeArgButton
|
||||||
|
width: 36
|
||||||
|
height: 36
|
||||||
|
circular: false
|
||||||
|
iconName: "remove"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.error
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onClicked: {
|
||||||
|
row.args.splice(index + 1, 1)
|
||||||
|
row.args = row.args.slice()
|
||||||
|
row.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
width: parent.width
|
||||||
|
height: 36
|
||||||
|
circular: false
|
||||||
|
iconName: "add"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.primary
|
||||||
|
onClicked: {
|
||||||
|
row.args.push("")
|
||||||
|
row.args = row.args.slice()
|
||||||
|
row.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width - parent.padding * 2
|
||||||
|
height: 1
|
||||||
|
color: Theme.outline
|
||||||
|
opacity: 0.2
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - parent.padding * 2
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Options"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
id: repeatToggle
|
||||||
|
checked: row.repeatEnabled
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onToggled: checked => {
|
||||||
|
row.repeatEnabled = checked
|
||||||
|
row.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Allow repeat"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Cooldown (ms)"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: 120
|
||||||
|
height: 40
|
||||||
|
text: row.cooldownMs.toString()
|
||||||
|
placeholderText: "0"
|
||||||
|
onTextChanged: {
|
||||||
|
const val = parseInt(text) || 0
|
||||||
|
if (row.cooldownMs !== val) {
|
||||||
|
row.cooldownMs = val
|
||||||
|
row.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
id: lockedToggle
|
||||||
|
checked: row.allowWhenLocked
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onToggled: checked => {
|
||||||
|
row.allowWhenLocked = checked
|
||||||
|
row.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Allow when locked"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Hotkey overlay title"
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: parent.width
|
||||||
|
height: 40
|
||||||
|
text: row.overlayTitle
|
||||||
|
placeholderText: ""
|
||||||
|
onTextChanged: {
|
||||||
|
if (row.overlayTitle !== text) {
|
||||||
|
row.overlayTitle = text
|
||||||
|
row.changed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
[templates.niri]
|
[templates.niri]
|
||||||
input_path = './matugen/templates/niri-colors.kdl'
|
input_path = './matugen/templates/niri-colors.kdl'
|
||||||
output_path = '~/.config/niri/dankshell-colors.kdl'
|
output_path = '~/.config/niri/dms/colors.kdl'
|
||||||
Reference in New Issue
Block a user