1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

Revert "wip binds"

This reverts commit 841e55d37f.
This commit is contained in:
purian23
2025-10-01 11:49:29 -04:00
parent 841e55d37f
commit 0a038fa3c9
7 changed files with 7 additions and 1897 deletions

View File

@@ -34,28 +34,27 @@ Item {
} }
Loader { Loader {
id: timeWeatherLoader id: timeLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 1 active: root.currentIndex === 1
visible: active visible: active
asynchronous: true asynchronous: true
sourceComponent: TimeWeatherTab { sourceComponent: TimeTab {
} }
} }
Loader { Loader {
id: keybindsLoader id: weatherLoader
anchors.fill: parent anchors.fill: parent
active: root.currentIndex === 2 active: root.currentIndex === 2
visible: active visible: active
asynchronous: true asynchronous: true
sourceComponent: KeybindsTab { sourceComponent: WeatherTab {
parentModal: root.parentModal
} }
} }

View File

@@ -12,11 +12,11 @@ Rectangle {
"text": "Personalization", "text": "Personalization",
"icon": "person" "icon": "person"
}, { }, {
"text": "Time & Weather", "text": "Time & Date",
"icon": "schedule" "icon": "schedule"
}, { }, {
"text": "Key Bindings", "text": "Weather",
"icon": "keyboard" "icon": "cloud"
}, { }, {
"text": "Dank Bar", "text": "Dank Bar",
"icon": "toolbar" "icon": "toolbar"

View File

@@ -1,181 +0,0 @@
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()
}
}

View File

@@ -1,717 +0,0 @@
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
}
}
}
}
}
}

View File

@@ -1,141 +0,0 @@
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()
}
}
}
}
}

View File

@@ -1,207 +0,0 @@
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()
}
}
}

View File

@@ -1,643 +0,0 @@
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
}
}
}
}
}
}