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

keyboard shortcuts: comprehensive keyboard shortcut management interface

- niri only for now
- requires quickshell-git, hidden otherwise
- Add, Edit, Delete keybinds
- Large suite of pre-defined and custom actions
- Works with niri 25.11+ include feature
This commit is contained in:
bbedward
2025-12-02 23:08:23 -05:00
parent a679be68b1
commit f92dc6f71b
17 changed files with 4105 additions and 863 deletions

View File

@@ -329,79 +329,79 @@ Item {
IpcHandler {
function toggle(provider: string): string {
if (!provider) {
if (!provider)
return "ERROR: No provider specified";
}
KeybindsService.loadProvider(provider);
KeybindsService.currentProvider = provider;
KeybindsService.loadBinds();
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}`;
if (!root.hyprKeybindsModalLoader.item)
return `KEYBINDS_TOGGLE_FAILED: ${provider}`;
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) {
root.hyprKeybindsModalLoader.item.close();
} else {
root.hyprKeybindsModalLoader.item.open();
}
return `KEYBINDS_TOGGLE_FAILED: ${provider}`;
return `KEYBINDS_TOGGLE_SUCCESS: ${provider}`;
}
function toggleWithPath(provider: string, path: string): string {
if (!provider) {
if (!provider)
return "ERROR: No provider specified";
}
KeybindsService.loadProviderWithPath(provider, path);
KeybindsService.currentProvider = provider;
KeybindsService.loadBinds();
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} (${path})`;
if (!root.hyprKeybindsModalLoader.item)
return `KEYBINDS_TOGGLE_FAILED: ${provider}`;
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) {
root.hyprKeybindsModalLoader.item.close();
} else {
root.hyprKeybindsModalLoader.item.open();
}
return `KEYBINDS_TOGGLE_FAILED: ${provider}`;
return `KEYBINDS_TOGGLE_SUCCESS: ${provider} (${path})`;
}
function open(provider: string): string {
if (!provider) {
if (!provider)
return "ERROR: No provider specified";
}
KeybindsService.loadProvider(provider);
KeybindsService.currentProvider = provider;
KeybindsService.loadBinds();
root.hyprKeybindsModalLoader.active = true;
if (root.hyprKeybindsModalLoader.item) {
root.hyprKeybindsModalLoader.item.open();
return `KEYBINDS_OPEN_SUCCESS: ${provider}`;
}
return `KEYBINDS_OPEN_FAILED: ${provider}`;
if (!root.hyprKeybindsModalLoader.item)
return `KEYBINDS_OPEN_FAILED: ${provider}`;
root.hyprKeybindsModalLoader.item.open();
return `KEYBINDS_OPEN_SUCCESS: ${provider}`;
}
function openWithPath(provider: string, path: string): string {
if (!provider) {
if (!provider)
return "ERROR: No provider specified";
}
KeybindsService.loadProviderWithPath(provider, path);
KeybindsService.currentProvider = provider;
KeybindsService.loadBinds();
root.hyprKeybindsModalLoader.active = true;
if (root.hyprKeybindsModalLoader.item) {
root.hyprKeybindsModalLoader.item.open();
return `KEYBINDS_OPEN_SUCCESS: ${provider} (${path})`;
}
return `KEYBINDS_OPEN_FAILED: ${provider}`;
if (!root.hyprKeybindsModalLoader.item)
return `KEYBINDS_OPEN_FAILED: ${provider}`;
root.hyprKeybindsModalLoader.item.open();
return `KEYBINDS_OPEN_SUCCESS: ${provider} (${path})`;
}
function close(): string {
if (root.hyprKeybindsModalLoader.item) {
root.hyprKeybindsModalLoader.item.close();
return "KEYBINDS_CLOSE_SUCCESS";
}
return "KEYBINDS_CLOSE_FAILED";
if (!root.hyprKeybindsModalLoader.item)
return "KEYBINDS_CLOSE_FAILED";
root.hyprKeybindsModalLoader.item.close();
return "KEYBINDS_CLOSE_SUCCESS";
}
target: "keybinds"
@@ -409,44 +409,48 @@ Item {
IpcHandler {
function openBinds(): string {
if (!CompositorService.isHyprland) {
if (!CompositorService.isHyprland)
return "HYPR_NOT_AVAILABLE";
}
KeybindsService.loadProvider("hyprland");
KeybindsService.currentProvider = "hyprland";
KeybindsService.loadBinds();
root.hyprKeybindsModalLoader.active = true;
if (root.hyprKeybindsModalLoader.item) {
root.hyprKeybindsModalLoader.item.open();
return "HYPR_KEYBINDS_OPEN_SUCCESS";
}
return "HYPR_KEYBINDS_OPEN_FAILED";
if (!root.hyprKeybindsModalLoader.item)
return "HYPR_KEYBINDS_OPEN_FAILED";
root.hyprKeybindsModalLoader.item.open();
return "HYPR_KEYBINDS_OPEN_SUCCESS";
}
function closeBinds(): string {
if (!CompositorService.isHyprland) {
if (!CompositorService.isHyprland)
return "HYPR_NOT_AVAILABLE";
}
if (root.hyprKeybindsModalLoader.item) {
root.hyprKeybindsModalLoader.item.close();
return "HYPR_KEYBINDS_CLOSE_SUCCESS";
}
return "HYPR_KEYBINDS_CLOSE_FAILED";
if (!root.hyprKeybindsModalLoader.item)
return "HYPR_KEYBINDS_CLOSE_FAILED";
root.hyprKeybindsModalLoader.item.close();
return "HYPR_KEYBINDS_CLOSE_SUCCESS";
}
function toggleBinds(): string {
if (!CompositorService.isHyprland) {
if (!CompositorService.isHyprland)
return "HYPR_NOT_AVAILABLE";
}
KeybindsService.loadProvider("hyprland");
KeybindsService.currentProvider = "hyprland";
KeybindsService.loadBinds();
root.hyprKeybindsModalLoader.active = true;
if (root.hyprKeybindsModalLoader.item) {
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) {
root.hyprKeybindsModalLoader.item.close();
} else {
root.hyprKeybindsModalLoader.item.open();
}
return "HYPR_KEYBINDS_TOGGLE_SUCCESS";
if (!root.hyprKeybindsModalLoader.item)
return "HYPR_KEYBINDS_TOGGLE_FAILED";
if (root.hyprKeybindsModalLoader.item.shouldBeVisible) {
root.hyprKeybindsModalLoader.item.close();
} else {
root.hyprKeybindsModalLoader.item.open();
}
return "HYPR_KEYBINDS_TOGGLE_FAILED";
return "HYPR_KEYBINDS_TOGGLE_SUCCESS";
}
function toggleOverview(): string {
@@ -490,60 +494,108 @@ Item {
function getBarConfig(selector: string, value: string): var {
const barSelectors = ["id", "name", "index"];
if (!barSelectors.includes(selector)) return { error: "BAR_INVALID_SELECTOR" };
if (!barSelectors.includes(selector))
return {
error: "BAR_INVALID_SELECTOR"
};
const index = selector === "index" ? Number(value) : SettingsData.barConfigs.findIndex(bar => bar[selector] == value);
const barConfig = SettingsData.barConfigs?.[index];
if (!barConfig) return { error: "BAR_NOT_FOUND" };
return { barConfig };
if (!barConfig)
return {
error: "BAR_NOT_FOUND"
};
return {
barConfig
};
}
IpcHandler {
function reveal(selector: string, value: string): string {
const { barConfig, error } = getBarConfig(selector, value);
if (error) return error;
SettingsData.updateBarConfig(barConfig.id, {visible: true});
const {
barConfig,
error
} = getBarConfig(selector, value);
if (error)
return error;
SettingsData.updateBarConfig(barConfig.id, {
visible: true
});
return "BAR_SHOW_SUCCESS";
}
function hide(selector: string, value: string): string {
const { barConfig, error } = getBarConfig(selector, value);
if (error) return error;
SettingsData.updateBarConfig(barConfig.id, {visible: false});
const {
barConfig,
error
} = getBarConfig(selector, value);
if (error)
return error;
SettingsData.updateBarConfig(barConfig.id, {
visible: false
});
return "BAR_HIDE_SUCCESS";
}
function toggle(selector: string, value: string): string {
const { barConfig, error } = getBarConfig(selector, value);
if (error) return error;
SettingsData.updateBarConfig(barConfig.id, {visible: !barConfig.visible});
const {
barConfig,
error
} = getBarConfig(selector, value);
if (error)
return error;
SettingsData.updateBarConfig(barConfig.id, {
visible: !barConfig.visible
});
return !barConfig.visible ? "BAR_SHOW_SUCCESS" : "BAR_HIDE_SUCCESS";
}
function status(selector: string, value: string): string {
const { barConfig, error } = getBarConfig(selector, value);
if (error) return error;
const {
barConfig,
error
} = getBarConfig(selector, value);
if (error)
return error;
return barConfig.visible ? "visible" : "hidden";
}
function autoHide(selector: string, value: string): string {
const { barConfig, error } = getBarConfig(selector, value);
if (error) return error;
SettingsData.updateBarConfig(barConfig.id, {autoHide: true});
const {
barConfig,
error
} = getBarConfig(selector, value);
if (error)
return error;
SettingsData.updateBarConfig(barConfig.id, {
autoHide: true
});
return "BAR_AUTO_HIDE_SUCCESS";
}
function manualHide(selector: string, value: string): string {
const { barConfig, error } = getBarConfig(selector, value);
if (error) return error;
SettingsData.updateBarConfig(barConfig.id, {autoHide: false});
const {
barConfig,
error
} = getBarConfig(selector, value);
if (error)
return error;
SettingsData.updateBarConfig(barConfig.id, {
autoHide: false
});
return "BAR_MANUAL_HIDE_SUCCESS";
}
function toggleAutoHide(selector: string, value: string): string {
const { barConfig, error } = getBarConfig(selector, value);
if (error) return error;
SettingsData.updateBarConfig(barConfig.id, {autoHide: !barConfig.autoHide});
return barConfig.autoHide ? "BAR_MANUAL_HIDE_SUCCESS": "BAR_AUTO_HIDE_SUCCESS";
const {
barConfig,
error
} = getBarConfig(selector, value);
if (error)
return error;
SettingsData.updateBarConfig(barConfig.id, {
autoHide: !barConfig.autoHide
});
return barConfig.autoHide ? "BAR_MANUAL_HIDE_SUCCESS" : "BAR_AUTO_HIDE_SUCCESS";
}
target: "bar"
@@ -570,20 +622,20 @@ Item {
}
function autoHide(): string {
SettingsData.dockAutoHide = true
SettingsData.saveSettings()
SettingsData.dockAutoHide = true;
SettingsData.saveSettings();
return "BAR_AUTO_HIDE_SUCCESS";
}
function manualHide(): string {
SettingsData.dockAutoHide = false
SettingsData.saveSettings()
SettingsData.dockAutoHide = false;
SettingsData.saveSettings();
return "BAR_MANUAL_HIDE_SUCCESS";
}
function toggleAutoHide(): string {
SettingsData.dockAutoHide = !SettingsData.dockAutoHide
SettingsData.saveSettings()
SettingsData.dockAutoHide = !SettingsData.dockAutoHide;
SettingsData.saveSettings();
return SettingsData.dockAutoHide ? "BAR_AUTO_HIDE_SUCCESS" : "BAR_MANUAL_HIDE_SUCCESS";
}
@@ -612,49 +664,51 @@ Item {
}
function get(key: string): string {
return JSON.stringify(SettingsData?.[key])
return JSON.stringify(SettingsData?.[key]);
}
function set(key: string, value: string): string {
if (!(key in SettingsData)) {
console.warn("Cannot set property, not found:", key)
return "SETTINGS_INVALID_KEY"
console.warn("Cannot set property, not found:", key);
return "SETTINGS_INVALID_KEY";
}
const typeName = typeof SettingsData?.[key]
const typeName = typeof SettingsData?.[key];
try {
switch (typeName) {
case "boolean":
if (value === "true" || value === "false") value = (value === "true")
else throw `${value} is not a Boolean`
break
if (value === "true" || value === "false")
value = (value === "true");
else
throw `${value} is not a Boolean`;
break;
case "number":
value = Number(value)
if (isNaN(value)) throw `${value} is not a Number`
break
value = Number(value);
if (isNaN(value))
throw `${value} is not a Number`;
break;
case "string":
value = String(value)
break
value = String(value);
break;
case "object":
// NOTE: Parsing lists is messed up upstream and not sure if we want
// to make sure objects are well structured or just let people set
// whatever they want but risking messed up settings.
// Objects & Arrays are disabled for now
// https://github.com/quickshell-mirror/quickshell/pull/22
throw "Setting Objects and Arrays not supported"
throw "Setting Objects and Arrays not supported";
default:
throw "Unsupported type"
throw "Unsupported type";
}
console.warn("Setting:", key, value)
SettingsData[key] = value
SettingsData.saveSettings()
return "SETTINGS_SET_SUCCESS"
console.warn("Setting:", key, value);
SettingsData[key] = value;
SettingsData.saveSettings();
return "SETTINGS_SET_SUCCESS";
} catch (e) {
console.warn("Failed to set property:", key, "error:", e)
return "SETTINGS_SET_FAILURE"
console.warn("Failed to set property:", key, "error:", e);
return "SETTINGS_SET_FAILURE";
}
}

View File

@@ -97,14 +97,12 @@ DankModal {
const bind = binds[i];
if (bind.subcat) {
hasSubcats = true;
if (!subcats[bind.subcat]) {
if (!subcats[bind.subcat])
subcats[bind.subcat] = [];
}
subcats[bind.subcat].push(bind);
} else {
if (!subcats["_root"]) {
if (!subcats["_root"])
subcats["_root"] = [];
}
subcats["_root"].push(bind);
}
}
@@ -121,12 +119,10 @@ DankModal {
function distributeCategories(cols) {
const columns = [];
for (let i = 0; i < cols; i++) {
for (let i = 0; i < cols; i++)
columns.push([]);
}
for (let i = 0; i < categoryKeys.length; i++) {
for (let i = 0; i < categoryKeys.length; i++)
columns[i % cols].push(categoryKeys[i]);
}
return columns;
}

View File

@@ -57,13 +57,32 @@ FocusScope {
}
Loader {
id: topBarLoader
id: keybindsLoader
anchors.fill: parent
active: root.currentIndex === 2
visible: active
focus: active
sourceComponent: KeybindsTab {
parentModal: root.parentModal
}
onActiveChanged: {
if (active && item) {
Qt.callLater(() => item.forceActiveFocus());
}
}
}
Loader {
id: topBarLoader
anchors.fill: parent
active: root.currentIndex === 3
visible: active
focus: active
sourceComponent: DankBarTab {
parentModal: root.parentModal
}
@@ -79,7 +98,7 @@ FocusScope {
id: widgetsLoader
anchors.fill: parent
active: root.currentIndex === 3
active: root.currentIndex === 4
visible: active
focus: active
@@ -96,7 +115,7 @@ FocusScope {
id: dockLoader
anchors.fill: parent
active: root.currentIndex === 4
active: root.currentIndex === 5
visible: active
focus: active
@@ -115,7 +134,7 @@ FocusScope {
id: displaysLoader
anchors.fill: parent
active: root.currentIndex === 5
active: root.currentIndex === 6
visible: active
focus: active
@@ -132,7 +151,7 @@ FocusScope {
id: networkLoader
anchors.fill: parent
active: root.currentIndex === 6
active: root.currentIndex === 7
visible: active
focus: active
@@ -149,7 +168,7 @@ FocusScope {
id: printerLoader
anchors.fill: parent
active: root.currentIndex === 7
active: root.currentIndex === 8
visible: active
focus: active
@@ -166,7 +185,7 @@ FocusScope {
id: launcherLoader
anchors.fill: parent
active: root.currentIndex === 8
active: root.currentIndex === 9
visible: active
focus: active
@@ -183,7 +202,7 @@ FocusScope {
id: themeColorsLoader
anchors.fill: parent
active: root.currentIndex === 9
active: root.currentIndex === 10
visible: active
focus: active
@@ -200,7 +219,7 @@ FocusScope {
id: powerLoader
anchors.fill: parent
active: root.currentIndex === 10
active: root.currentIndex === 11
visible: active
focus: active
@@ -217,7 +236,7 @@ FocusScope {
id: pluginsLoader
anchors.fill: parent
active: root.currentIndex === 11
active: root.currentIndex === 12
visible: active
focus: active
@@ -236,7 +255,7 @@ FocusScope {
id: aboutLoader
anchors.fill: parent
active: root.currentIndex === 12
active: root.currentIndex === 13
visible: active
focus: active

View File

@@ -22,62 +22,68 @@ Rectangle {
"icon": "schedule",
"tabIndex": 1
},
{
"text": I18n.tr("Keyboard Shortcuts"),
"icon": "keyboard",
"shortcutsOnly": true,
"tabIndex": 2
},
{
"text": I18n.tr("Dank Bar"),
"icon": "toolbar",
"tabIndex": 2
"tabIndex": 3
},
{
"text": I18n.tr("Widgets"),
"icon": "widgets",
"tabIndex": 3
"tabIndex": 4
},
{
"text": I18n.tr("Dock"),
"icon": "dock_to_bottom",
"tabIndex": 4
"tabIndex": 5
},
{
"text": I18n.tr("Displays"),
"icon": "monitor",
"tabIndex": 5
"tabIndex": 6
},
{
"text": I18n.tr("Network"),
"icon": "wifi",
"dmsOnly": true,
"tabIndex": 6
"tabIndex": 7
},
{
"text": I18n.tr("Printers"),
"icon": "print",
"cupsOnly": true,
"tabIndex": 7
"tabIndex": 8
},
{
"text": I18n.tr("Launcher"),
"icon": "apps",
"tabIndex": 8
"tabIndex": 9
},
{
"text": I18n.tr("Theme & Colors"),
"icon": "palette",
"tabIndex": 9
"tabIndex": 10
},
{
"text": I18n.tr("Power & Security"),
"icon": "power",
"tabIndex": 10
"tabIndex": 11
},
{
"text": I18n.tr("Plugins"),
"icon": "extension",
"tabIndex": 11
"tabIndex": 12
},
{
"text": I18n.tr("About"),
"icon": "info",
"tabIndex": 12
"tabIndex": 13
}
]
readonly property var sidebarItems: allSidebarItems.filter(item => {
@@ -85,6 +91,8 @@ Rectangle {
return false;
if (item.cupsOnly && !CupsService.cupsAvailable)
return false;
if (item.shortcutsOnly && !KeybindsService.available)
return false;
return true;
})

View File

@@ -0,0 +1,548 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: keybindsTab
property var parentModal: null
property string selectedCategory: ""
property string searchQuery: ""
property string expandedKey: ""
property bool showingNewBind: false
property int _lastDataVersion: -1
property var _cachedCategories: []
property var _filteredBinds: []
function _updateFiltered() {
const allBinds = KeybindsService.getFlatBinds();
if (!searchQuery && !selectedCategory) {
_filteredBinds = allBinds;
return;
}
const q = searchQuery.toLowerCase();
const isOverrideFilter = selectedCategory === "__overrides__";
const result = [];
for (let i = 0; i < allBinds.length; i++) {
const group = allBinds[i];
if (q) {
let keyMatch = false;
for (let k = 0; k < group.keys.length; k++) {
if (group.keys[k].key.toLowerCase().indexOf(q) !== -1) {
keyMatch = true;
break;
}
}
if (!keyMatch && group.desc.toLowerCase().indexOf(q) === -1 && group.action.toLowerCase().indexOf(q) === -1)
continue;
}
if (isOverrideFilter) {
let hasOverride = false;
for (let k = 0; k < group.keys.length; k++) {
if (group.keys[k].isOverride) {
hasOverride = true;
break;
}
}
if (!hasOverride)
continue;
} else if (selectedCategory && group.category !== selectedCategory) {
continue;
}
result.push(group);
}
_filteredBinds = result;
}
function _updateCategories() {
_cachedCategories = ["__overrides__"].concat(KeybindsService.getCategories());
}
function getCategoryLabel(cat) {
if (cat === "__overrides__")
return I18n.tr("Overrides");
return cat;
}
function toggleExpanded(action) {
expandedKey = expandedKey === action ? "" : action;
}
function startNewBind() {
showingNewBind = true;
expandedKey = "";
}
function cancelNewBind() {
showingNewBind = false;
}
function saveNewBind(bindData) {
KeybindsService.saveBind("", bindData);
showingNewBind = false;
}
function scrollToTop() {
flickable.contentY = 0;
}
Timer {
id: searchDebounce
interval: 150
onTriggered: keybindsTab._updateFiltered()
}
Connections {
target: KeybindsService
function onBindsLoaded() {
keybindsTab._lastDataVersion = KeybindsService._dataVersion;
keybindsTab._updateCategories();
keybindsTab._updateFiltered();
}
}
function _ensureNiriProvider() {
if (!KeybindsService.available)
return;
const cachedProvider = KeybindsService.keybinds?.provider;
if (cachedProvider !== "niri" || KeybindsService._dataVersion === 0) {
KeybindsService.currentProvider = "niri";
KeybindsService.loadBinds();
return;
}
if (_lastDataVersion !== KeybindsService._dataVersion) {
_lastDataVersion = KeybindsService._dataVersion;
_updateCategories();
_updateFiltered();
}
}
Component.onCompleted: _ensureNiriProvider()
onVisibleChanged: {
if (!visible)
return;
Qt.callLater(scrollToTop);
_ensureNiriProvider();
}
DankFlickable {
id: flickable
anchors.fill: parent
clip: true
contentWidth: width
contentHeight: contentColumn.implicitHeight
Column {
id: contentColumn
width: flickable.width
spacing: Theme.spacingL
topPadding: Theme.spacingXL
bottomPadding: Theme.spacingXL
StyledRect {
width: Math.min(650, parent.width - Theme.spacingL * 2)
height: headerSection.implicitHeight + Theme.spacingL * 2
anchors.horizontalCenter: parent.horizontalCenter
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.width: 0
Column {
id: headerSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "keyboard"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM * 2
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Keyboard Shortcuts")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Click any shortcut to edit. Changes save to dms/binds.kdl")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
}
Row {
width: parent.width
spacing: Theme.spacingM
DankTextField {
id: searchField
width: parent.width - addButton.width - Theme.spacingM
height: 44
placeholderText: I18n.tr("Search keybinds...")
leftIconName: "search"
onTextChanged: {
keybindsTab.searchQuery = text;
searchDebounce.restart();
}
}
DankActionButton {
id: addButton
width: 44
height: 44
circular: false
iconName: "add"
iconSize: Theme.iconSize
iconColor: Theme.primary
anchors.verticalCenter: parent.verticalCenter
enabled: !keybindsTab.showingNewBind
opacity: enabled ? 1 : 0.5
onClicked: keybindsTab.startNewBind()
}
}
}
}
StyledRect {
width: Math.min(650, parent.width - Theme.spacingL * 2)
height: warningSection.implicitHeight + Theme.spacingL * 2
anchors.horizontalCenter: parent.horizontalCenter
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.error, 0.15)
border.color: Theme.withAlpha(Theme.error, 0.3)
border.width: 1
visible: !KeybindsService.dmsBindsIncluded && !KeybindsService.loading
Column {
id: warningSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "warning"
size: Theme.iconSize
color: Theme.error
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - 100 - Theme.spacingM * 2
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Binds Include Missing")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.error
}
StyledText {
text: I18n.tr("dms/binds.kdl is not included in config.kdl. Custom keybinds will not work until this is fixed.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
Rectangle {
id: fixButton
width: fixButtonText.implicitWidth + Theme.spacingL * 2
height: 36
radius: Theme.cornerRadius
color: KeybindsService.fixing ? Theme.withAlpha(Theme.error, 0.6) : Theme.error
anchors.verticalCenter: parent.verticalCenter
StyledText {
id: fixButtonText
text: KeybindsService.fixing ? I18n.tr("Fixing...") : I18n.tr("Fix Now")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surface
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: !KeybindsService.fixing
onClicked: KeybindsService.fixDmsBindsInclude()
}
}
}
}
}
StyledRect {
width: Math.min(650, parent.width - Theme.spacingL * 2)
height: categorySection.implicitHeight + Theme.spacingL * 2
anchors.horizontalCenter: parent.horizontalCenter
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.width: 0
Column {
id: categorySection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Flow {
width: parent.width
spacing: Theme.spacingS
Rectangle {
width: allChip.implicitWidth + Theme.spacingL
height: 32
radius: 16
color: !keybindsTab.selectedCategory ? Theme.primary : Theme.surfaceContainerHighest
StyledText {
id: allChip
text: I18n.tr("All")
font.pixelSize: Theme.fontSizeSmall
color: !keybindsTab.selectedCategory ? Theme.primaryText : Theme.surfaceVariantText
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
keybindsTab.selectedCategory = "";
keybindsTab._updateFiltered();
}
}
}
Repeater {
model: keybindsTab._cachedCategories
delegate: Rectangle {
required property string modelData
required property int index
width: catText.implicitWidth + Theme.spacingL
height: 32
radius: 16
color: keybindsTab.selectedCategory === modelData ? Theme.primary : (modelData === "__overrides__" ? Theme.withAlpha(Theme.primary, 0.15) : Theme.surfaceContainerHighest)
StyledText {
id: catText
text: keybindsTab.getCategoryLabel(modelData)
font.pixelSize: Theme.fontSizeSmall
color: keybindsTab.selectedCategory === modelData ? Theme.primaryText : (modelData === "__overrides__" ? Theme.primary : Theme.surfaceVariantText)
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
keybindsTab.selectedCategory = modelData;
keybindsTab._updateFiltered();
}
}
}
}
}
}
}
StyledRect {
width: Math.min(650, parent.width - Theme.spacingL * 2)
height: newBindSection.implicitHeight + Theme.spacingL * 2
anchors.horizontalCenter: parent.horizontalCenter
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Theme.outlineVariant
border.width: 1
visible: keybindsTab.showingNewBind
Column {
id: newBindSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "add"
size: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("New Keybind")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
KeybindItem {
width: parent.width
isNew: true
isExpanded: true
bindData: ({
keys: [
{
key: "",
source: "dms",
isOverride: true
}
],
action: "",
desc: ""
})
panelWindow: keybindsTab.parentModal
onSaveBind: (originalKey, newData) => keybindsTab.saveNewBind(newData)
onCancelEdit: keybindsTab.cancelNewBind()
}
}
}
StyledRect {
width: Math.min(650, parent.width - Theme.spacingL * 2)
height: bindsListHeader.implicitHeight + Theme.spacingL * 2
anchors.horizontalCenter: parent.horizontalCenter
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.width: 0
Column {
id: bindsListHeader
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "list"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: KeybindsService.loading ? I18n.tr("Shortcuts") : I18n.tr("Shortcuts") + " (" + keybindsTab._filteredBinds.length + ")"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
width: parent.width
spacing: Theme.spacingM
visible: KeybindsService.loading
DankIcon {
id: loadingIcon
name: "sync"
size: 20
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
RotationAnimation on rotation {
from: 0
to: 360
duration: 1000
loops: Animation.Infinite
running: KeybindsService.loading
}
}
StyledText {
text: I18n.tr("Loading keybinds...")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: I18n.tr("No keybinds found")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
visible: !KeybindsService.loading && keybindsTab._filteredBinds.length === 0
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
Repeater {
model: ScriptModel {
values: keybindsTab._filteredBinds
objectProp: "action"
}
delegate: Item {
required property var modelData
required property int index
width: parent.width
height: bindItem.height
KeybindItem {
id: bindItem
width: Math.min(650, parent.width - Theme.spacingL * 2)
anchors.horizontalCenter: parent.horizontalCenter
bindData: modelData
isExpanded: keybindsTab.expandedKey === modelData.action
panelWindow: keybindsTab.parentModal
onToggleExpand: keybindsTab.toggleExpanded(modelData.action)
onSaveBind: (originalKey, newData) => {
KeybindsService.saveBind(originalKey, newData);
keybindsTab.expandedKey = modelData.action;
}
onRemoveBind: key => KeybindsService.removeBind(key)
}
}
}
}
}
}
}

View File

@@ -2,8 +2,6 @@ import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Widgets
@@ -22,13 +20,13 @@ PanelWindow {
target: ToastService
function onToastVisibleChanged() {
if (ToastService.toastVisible) {
shouldBeVisible = true
visible = true
shouldBeVisible = true;
visible = true;
} else {
// Freeze the width before starting exit animation
frozenWidth = toast.width
shouldBeVisible = false
closeTimer.restart()
frozenWidth = toast.width;
shouldBeVisible = false;
closeTimer.restart();
}
}
}
@@ -38,7 +36,7 @@ PanelWindow {
interval: Theme.mediumDuration + 50
onTriggered: {
if (!shouldBeVisible) {
visible = false
visible = false;
}
}
}
@@ -65,7 +63,7 @@ PanelWindow {
Connections {
target: ToastService
function onResetToastState() {
toast.expanded = false
toast.expanded = false;
}
}
@@ -76,13 +74,13 @@ PanelWindow {
color: {
switch (ToastService.currentLevel) {
case ToastService.levelError:
return Theme.error
return Theme.error;
case ToastService.levelWarn:
return Theme.warning
return Theme.warning;
case ToastService.levelInfo:
return Theme.surfaceContainer
return Theme.surfaceContainer;
default:
return Theme.surfaceContainer
return Theme.surfaceContainer;
}
}
radius: Theme.cornerRadius
@@ -109,13 +107,13 @@ PanelWindow {
name: {
switch (ToastService.currentLevel) {
case ToastService.levelError:
return "error"
return "error";
case ToastService.levelWarn:
return "warning"
return "warning";
case ToastService.levelInfo:
return "info"
return "info";
default:
return "info"
return "info";
}
}
size: Theme.iconSize
@@ -123,9 +121,9 @@ PanelWindow {
switch (ToastService.currentLevel) {
case ToastService.levelError:
case ToastService.levelWarn:
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
return SessionData.isLightMode ? Theme.surfaceText : Theme.background;
default:
return Theme.surfaceText
return Theme.surfaceText;
}
}
anchors.left: parent.left
@@ -140,9 +138,9 @@ PanelWindow {
switch (ToastService.currentLevel) {
case ToastService.levelError:
case ToastService.levelWarn:
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
return SessionData.isLightMode ? Theme.surfaceText : Theme.background;
default:
return Theme.surfaceText
return Theme.surfaceText;
}
}
font.weight: Font.Medium
@@ -163,9 +161,9 @@ PanelWindow {
switch (ToastService.currentLevel) {
case ToastService.levelError:
case ToastService.levelWarn:
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
return SessionData.isLightMode ? Theme.surfaceText : Theme.background;
default:
return Theme.surfaceText
return Theme.surfaceText;
}
}
buttonSize: Theme.iconSize + 8
@@ -175,11 +173,11 @@ PanelWindow {
visible: ToastService.hasDetails
onClicked: {
toast.expanded = !toast.expanded
toast.expanded = !toast.expanded;
if (toast.expanded) {
ToastService.stopTimer()
ToastService.stopTimer();
} else {
ToastService.restartTimer()
ToastService.restartTimer();
}
}
}
@@ -192,9 +190,9 @@ PanelWindow {
switch (ToastService.currentLevel) {
case ToastService.levelError:
case ToastService.levelWarn:
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
return SessionData.isLightMode ? Theme.surfaceText : Theme.background;
default:
return Theme.surfaceText
return Theme.surfaceText;
}
}
buttonSize: Theme.iconSize + 8
@@ -203,7 +201,7 @@ PanelWindow {
visible: ToastService.hasDetails || ToastService.currentLevel === ToastService.levelError
onClicked: {
ToastService.hideToast()
ToastService.hideToast();
}
}
}
@@ -211,7 +209,7 @@ PanelWindow {
Rectangle {
width: parent.width
height: detailsColumn.height + Theme.spacingS * 2
color: Qt.rgba(0, 0, 0, 0.2)
color: ToastService.currentDetails.length > 0 ? Qt.rgba(0, 0, 0, 0.2) : "transparent"
radius: Theme.cornerRadius / 2
visible: toast.expanded && ToastService.hasDetails
anchors.horizontalCenter: parent.horizontalCenter
@@ -232,9 +230,9 @@ PanelWindow {
switch (ToastService.currentLevel) {
case ToastService.levelError:
case ToastService.levelWarn:
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
return SessionData.isLightMode ? Theme.surfaceText : Theme.background;
default:
return Theme.surfaceText
return Theme.surfaceText;
}
}
visible: ToastService.currentDetails.length > 0
@@ -259,9 +257,9 @@ PanelWindow {
switch (ToastService.currentLevel) {
case ToastService.levelError:
case ToastService.levelWarn:
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
return SessionData.isLightMode ? Theme.surfaceText : Theme.background;
default:
return Theme.surfaceText
return Theme.surfaceText;
}
}
isMonospace: true
@@ -281,9 +279,9 @@ PanelWindow {
switch (ToastService.currentLevel) {
case ToastService.levelError:
case ToastService.levelWarn:
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
return SessionData.isLightMode ? Theme.surfaceText : Theme.background;
default:
return Theme.surfaceText
return Theme.surfaceText;
}
}
buttonSize: Theme.iconSizeSmall + 8
@@ -295,9 +293,9 @@ PanelWindow {
property bool showTooltip: false
onClicked: {
Quickshell.execDetached(["wl-copy", ToastService.currentCommand])
showTooltip = true
tooltipTimer.start()
Quickshell.execDetached(["wl-copy", ToastService.currentCommand]);
showTooltip = true;
tooltipTimer.start();
}
Timer {
@@ -346,7 +344,6 @@ PanelWindow {
shadowOpacity: 0.3
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration

View File

@@ -422,7 +422,6 @@ Singleton {
isLabwc = false;
compositor = "niri";
console.info("CompositorService: Detected Niri with socket:", niriSocket);
NiriService.generateNiriBinds();
NiriService.generateNiriBlurrule();
}
}, 0);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,59 +0,0 @@
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+Y hotkey-overlay-title="Browse Wallpapers" {
spawn "dms" "ipc" "call" "dankdash" "wallpaper";
}
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" "";
}
}

File diff suppressed because it is too large Load Diff