1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

keybinds/cheasheet: support all providers

This commit is contained in:
bbedward
2025-11-09 16:26:15 -05:00
parent 47c5320d67
commit bba21408ea
6 changed files with 343 additions and 350 deletions

View File

@@ -513,11 +513,11 @@ Item {
active: false
HyprKeybindsModal {
id: hyprKeybindsModal
KeybindsModal {
id: keybindsModal
Component.onCompleted: {
PopoutService.hyprKeybindsModal = hyprKeybindsModal
PopoutService.hyprKeybindsModal = keybindsModal
}
}
}

View File

@@ -306,11 +306,58 @@ Item {
target: "mpris"
}
IpcHandler {
function toggle(provider: string): string {
if (!provider) {
return "ERROR: No provider specified"
}
KeybindsService.loadProvider(provider)
root.hyprKeybindsModalLoader.active = true
if (root.hyprKeybindsModalLoader.item) {
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) {
root.hyprKeybindsModalLoader.item.close()
} else {
root.hyprKeybindsModalLoader.item.open()
}
return `KEYBINDS_TOGGLE_SUCCESS: ${provider}`
}
return `KEYBINDS_TOGGLE_FAILED: ${provider}`
}
function open(provider: string): string {
if (!provider) {
return "ERROR: No provider specified"
}
KeybindsService.loadProvider(provider)
root.hyprKeybindsModalLoader.active = true
if (root.hyprKeybindsModalLoader.item) {
root.hyprKeybindsModalLoader.item.open()
return `KEYBINDS_OPEN_SUCCESS: ${provider}`
}
return `KEYBINDS_OPEN_FAILED: ${provider}`
}
function close(): string {
if (root.hyprKeybindsModalLoader.item) {
root.hyprKeybindsModalLoader.item.close()
return "KEYBINDS_CLOSE_SUCCESS"
}
return "KEYBINDS_CLOSE_FAILED"
}
target: "keybinds"
}
IpcHandler {
function openBinds(): string {
if (!CompositorService.isHyprland) {
return "HYPR_NOT_AVAILABLE"
}
KeybindsService.loadProvider("hyprland")
root.hyprKeybindsModalLoader.active = true
if (root.hyprKeybindsModalLoader.item) {
root.hyprKeybindsModalLoader.item.open()
@@ -334,6 +381,7 @@ Item {
if (!CompositorService.isHyprland) {
return "HYPR_NOT_AVAILABLE"
}
KeybindsService.loadProvider("hyprland")
root.hyprKeybindsModalLoader.active = true
if (root.hyprKeybindsModalLoader.item) {
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) {

View File

@@ -1,297 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
layerNamespace: "dms:hyprkeybinds"
property real scrollStep: 60
property var activeFlickable: null
property real _maxW: Math.min(Screen.width * 0.92, 1200)
property real _maxH: Math.min(Screen.height * 0.92, 900)
width: _maxW
height: _maxH
onBackgroundClicked: close()
function scrollDown() {
if (!root.activeFlickable) return
let newY = root.activeFlickable.contentY + scrollStep
newY = Math.min(newY, root.activeFlickable.contentHeight - root.activeFlickable.height)
root.activeFlickable.contentY = newY
}
function scrollUp() {
if (!root.activeFlickable) return
let newY = root.activeFlickable.contentY - root.scrollStep
newY = Math.max(0, newY)
root.activeFlickable.contentY = newY
}
Shortcut { sequence: "Ctrl+j"; onActivated: root.scrollDown() }
Shortcut { sequence: "Down"; onActivated: root.scrollDown() }
Shortcut { sequence: "Ctrl+k"; onActivated: root.scrollUp() }
Shortcut { sequence: "Up"; onActivated: root.scrollUp() }
Shortcut { sequence: "Esc"; onActivated: root.close() }
function categorizeKeybinds() {
const categories = {
"Workspace": [],
"Window": [],
"Monitor": [],
"Execute": [],
"System": [],
"Other": []
}
function addKeybind(keybind) {
const dispatcher = keybind.dispatcher || ""
if (dispatcher.includes("workspace")) {
categories["Workspace"].push(keybind)
} else if (dispatcher.includes("monitor")) {
categories["Monitor"].push(keybind)
} else if (dispatcher.includes("window") || dispatcher.includes("focus") || dispatcher.includes("move") || dispatcher.includes("swap") || dispatcher.includes("resize") || dispatcher === "killactive" || dispatcher === "fullscreen" || dispatcher === "togglefloating") {
categories["Window"].push(keybind)
} else if (dispatcher === "exec") {
categories["Execute"].push(keybind)
} else if (dispatcher === "exit" || dispatcher.includes("dpms")) {
categories["System"].push(keybind)
} else {
categories["Other"].push(keybind)
}
}
const allKeybinds = HyprKeybindsService.keybinds.keybinds || []
for (let i = 0; i < allKeybinds.length; i++) {
addKeybind(allKeybinds[i])
}
const children = HyprKeybindsService.keybinds.children || []
for (let i = 0; i < children.length; i++) {
const child = children[i]
const childKeybinds = child.keybinds || []
for (let j = 0; j < childKeybinds.length; j++) {
addKeybind(childKeybinds[j])
}
}
categories["Workspace"].sort((a, b) => {
const dispA = a.dispatcher || ""
const dispB = b.dispatcher || ""
return dispA.localeCompare(dispB)
})
categories["Window"].sort((a, b) => {
const dispA = a.dispatcher || ""
const dispB = b.dispatcher || ""
return dispA.localeCompare(dispB)
})
categories["Monitor"].sort((a, b) => {
const dispA = a.dispatcher || ""
const dispB = b.dispatcher || ""
return dispA.localeCompare(dispB)
})
categories["Execute"].sort((a, b) => {
const modsA = a.mods || []
const keyA = a.key || ""
const bindA = [...modsA, keyA].join("+")
const modsB = b.mods || []
const keyB = b.key || ""
const bindB = [...modsB, keyB].join("+")
return bindA.localeCompare(bindB)
})
return categories
}
content: Component {
Item {
anchors.fill: parent
DankFlickable {
id: mainFlickable
anchors.fill: parent
anchors.margins: Theme.spacingL
contentWidth: rowLayout.implicitWidth
contentHeight: rowLayout.implicitHeight
clip: true
Component.onCompleted: root.activeFlickable = mainFlickable
Component.onDestruction: root.activeFlickable = null
Row {
id: rowLayout
spacing: Theme.spacingM
property var categories: root.categorizeKeybinds()
property real columnWidth: (mainFlickable.width - spacing * 2) / 3
Column {
width: rowLayout.columnWidth
spacing: Theme.spacingXS
StyledText {
text: "Window / Monitor"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.primary
}
Rectangle {
width: parent.width
height: 1
color: Theme.primary
opacity: 0.3
}
Item { width: 1; height: Theme.spacingXS }
Column {
width: parent.width
spacing: Theme.spacingXS
Repeater {
model: [...(rowLayout.categories["Window"] || []), ...(rowLayout.categories["Monitor"] || [])]
Row {
width: parent.width
spacing: Theme.spacingS
StyledRect {
width: Math.min(140, parent.width * 0.42)
height: 22
radius: 4
opacity: 0.9
StyledText {
anchors.centerIn: parent
anchors.margins: 2
width: parent.width - 4
color: Theme.secondary
text: {
const mods = modelData.mods || []
const key = modelData.key || ""
const parts = [...mods, key]
return parts.join("+")
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
isMonospace: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
}
StyledText {
width: parent.width - 150
text: {
const comment = modelData.comment || ""
if (comment) return comment
const dispatcher = modelData.dispatcher || ""
const params = modelData.params || ""
return params ? `${dispatcher} ${params}` : dispatcher
}
font.pixelSize: Theme.fontSizeSmall
opacity: 0.9
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
Repeater {
model: ["Workspace", "Execute"]
Column {
width: rowLayout.columnWidth
spacing: Theme.spacingXS
StyledText {
text: modelData
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.primary
}
Rectangle {
width: parent.width
height: 1
color: Theme.primary
opacity: 0.3
}
Item { width: 1; height: Theme.spacingXS }
Column {
width: parent.width
spacing: Theme.spacingXS
Repeater {
model: rowLayout.categories[modelData] || []
Row {
width: parent.width
spacing: Theme.spacingS
StyledRect {
width: Math.min(140, parent.width * 0.42)
height: 22
radius: 4
opacity: 0.9
StyledText {
anchors.centerIn: parent
anchors.margins: 2
width: parent.width - 4
color: Theme.secondary
text: {
const mods = modelData.mods || []
const key = modelData.key || ""
const parts = [...mods, key]
return parts.join("+")
}
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
isMonospace: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
}
StyledText {
width: parent.width - 150
text: {
const comment = modelData.comment || ""
if (comment) return comment
const dispatcher = modelData.dispatcher || ""
const params = modelData.params || ""
return params ? `${dispatcher} ${params}` : dispatcher
}
font.pixelSize: Theme.fontSizeSmall
opacity: 0.9
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
}
}
}
}
}
}

232
Modals/KeybindsModal.qml Normal file
View File

@@ -0,0 +1,232 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
layerNamespace: "dms:keybinds"
property real scrollStep: 60
property var activeFlickable: null
property real _maxW: Math.min(Screen.width * 0.92, 1200)
property real _maxH: Math.min(Screen.height * 0.92, 900)
width: _maxW
height: _maxH
onBackgroundClicked: close()
function scrollDown() {
if (!root.activeFlickable) return
let newY = root.activeFlickable.contentY + scrollStep
newY = Math.min(newY, root.activeFlickable.contentHeight - root.activeFlickable.height)
root.activeFlickable.contentY = newY
}
function scrollUp() {
if (!root.activeFlickable) return
let newY = root.activeFlickable.contentY - root.scrollStep
newY = Math.max(0, newY)
root.activeFlickable.contentY = newY
}
Shortcut { sequence: "Ctrl+j"; onActivated: root.scrollDown() }
Shortcut { sequence: "Down"; onActivated: root.scrollDown() }
Shortcut { sequence: "Ctrl+k"; onActivated: root.scrollUp() }
Shortcut { sequence: "Up"; onActivated: root.scrollUp() }
Shortcut { sequence: "Esc"; onActivated: root.close() }
content: Component {
Item {
anchors.fill: parent
Column {
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingL
StyledText {
text: KeybindsService.keybinds.title || "Keybinds"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.primary
}
DankFlickable {
id: mainFlickable
width: parent.width
height: parent.height - parent.spacing - 40
contentWidth: rowLayout.implicitWidth
contentHeight: rowLayout.implicitHeight
clip: true
Component.onCompleted: root.activeFlickable = mainFlickable
property var rawBinds: KeybindsService.keybinds.binds || {}
property var categories: {
const processed = {}
for (const cat in rawBinds) {
const binds = rawBinds[cat]
const subcats = {}
let hasSubcats = false
for (let i = 0; i < binds.length; i++) {
const bind = binds[i]
if (bind.subcat) {
hasSubcats = true
if (!subcats[bind.subcat]) {
subcats[bind.subcat] = []
}
subcats[bind.subcat].push(bind)
} else {
if (!subcats["_root"]) {
subcats["_root"] = []
}
subcats["_root"].push(bind)
}
}
processed[cat] = {
hasSubcats: hasSubcats,
subcats: subcats,
subcatKeys: Object.keys(subcats)
}
}
return processed
}
property var categoryKeys: Object.keys(categories)
function distributeCategories(cols) {
const columns = []
for (let i = 0; i < cols; i++) {
columns.push([])
}
for (let i = 0; i < categoryKeys.length; i++) {
columns[i % cols].push(categoryKeys[i])
}
return columns
}
Row {
id: rowLayout
width: mainFlickable.width
spacing: Theme.spacingM
property int numColumns: Math.max(1, Math.min(3, Math.floor(width / 350)))
property var columnCategories: mainFlickable.distributeCategories(numColumns)
Repeater {
model: rowLayout.numColumns
Column {
id: masonryColumn
width: (rowLayout.width - rowLayout.spacing * (rowLayout.numColumns - 1)) / rowLayout.numColumns
spacing: Theme.spacingM
Repeater {
model: rowLayout.columnCategories[index] || []
Column {
id: categoryColumn
width: parent.width
spacing: Theme.spacingXS
property string catName: modelData
property var catData: mainFlickable.categories[catName]
StyledText {
text: categoryColumn.catName
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.primary
}
Rectangle {
width: parent.width
height: 1
color: Theme.primary
opacity: 0.3
}
Item { width: 1; height: Theme.spacingXS }
Column {
width: parent.width
spacing: Theme.spacingM
Repeater {
model: categoryColumn.catData?.subcatKeys || []
Column {
width: parent.width
spacing: Theme.spacingXS
property string subcatName: modelData
property var subcatBinds: categoryColumn.catData?.subcats?.[subcatName] || []
StyledText {
visible: parent.subcatName !== "_root"
text: parent.subcatName
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
color: Theme.primary
opacity: 0.7
}
Column {
width: parent.width
spacing: Theme.spacingXS
Repeater {
model: parent.parent.subcatBinds
Row {
width: parent.width
spacing: Theme.spacingS
StyledRect {
width: Math.min(140, parent.width * 0.42)
height: 22
radius: 4
opacity: 0.9
StyledText {
anchors.centerIn: parent
anchors.margins: 2
width: parent.width - 4
color: Theme.secondary
text: modelData.key || ""
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
isMonospace: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
}
StyledText {
width: parent.width - 150
text: modelData.desc || ""
font.pixelSize: Theme.fontSizeSmall
opacity: 0.9
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}

View File

@@ -1,50 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import QtCore
import Quickshell
import Quickshell.Io
import qs.Common
Singleton {
id: root
readonly property string _configUrl: StandardPaths.writableLocation(StandardPaths.ConfigLocation)
readonly property string _configDir: Paths.strip(_configUrl)
property string hyprConfigPath: `${_configDir}/hypr`
property var keybinds: ({"children": [], "keybinds": []})
Process {
id: getKeybinds
running: false
command: ["dms", "hyprland", "keybinds", "--path", root.hyprConfigPath]
stdout: SplitParser {
onRead: data => {
try {
root.keybinds = JSON.parse(data)
} catch (e) {
console.error("[HyprKeybindsService] Error parsing keybinds:", e)
}
}
}
onExited: (code) => {
if (code !== 0) {
console.warn("[HyprKeybindsService] Process exited with code:", code)
}
}
}
Component.onCompleted: {
getKeybinds.running = true
}
function reload() {
getKeybinds.running = false
Qt.callLater(function() {
getKeybinds.running = true
})
}
}

View File

@@ -0,0 +1,60 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import QtCore
import Quickshell
import Quickshell.Io
import qs.Common
Singleton {
id: root
property string currentProvider: "hyprland"
property var keybinds: ({"title": "", "provider": "", "binds": []})
Process {
id: getKeybinds
running: false
command: ["dms", "keybinds", "show", root.currentProvider]
stdout: StdioCollector {
onStreamFinished: {
try {
root.keybinds = JSON.parse(text)
} catch (e) {
console.error("[KeybindsService] Error parsing keybinds:", e)
}
}
}
onExited: (code) => {
if (code !== 0 && code !== 15) {
console.warn("[KeybindsService] Process exited with code:", code)
}
}
}
Timer {
interval: 500
running: true
repeat: false
onTriggered: {
getKeybinds.running = true
}
}
function loadProvider(provider) {
root.currentProvider = provider
reload()
}
function reload() {
if (getKeybinds.running) {
getKeybinds.running = false
}
Qt.callLater(function() {
getKeybinds.running = true
})
}
}