mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -05:00
feat: configurable app id substitutions (#1317)
* feat: add configurable app ID substitutions setting * feat: add live icon updates when substitutions change * fix: cursor not showing on headerActions in non-collapsible cards * fix: address PR review feedback - add tags for search index - remove hardcoded height from text fields
This commit is contained in:
@@ -46,22 +46,20 @@ Singleton {
|
||||
}
|
||||
|
||||
function moddedAppId(appId: string): string {
|
||||
if (appId === "Spotify")
|
||||
return "spotify";
|
||||
if (appId === "beepertexts")
|
||||
return "beeper";
|
||||
if (appId === "home assistant desktop")
|
||||
return "homeassistant-desktop";
|
||||
if (appId.includes("com.transmissionbt.transmission")) {
|
||||
if (DesktopEntries.heuristicLookup("transmission-gtk"))
|
||||
return "transmission-gtk";
|
||||
if (DesktopEntries.heuristicLookup("transmission"))
|
||||
return "transmission";
|
||||
return "transmission-gtk";
|
||||
const subs = SettingsData.appIdSubstitutions || [];
|
||||
for (let i = 0; i < subs.length; i++) {
|
||||
const sub = subs[i];
|
||||
if (sub.type === "exact" && appId === sub.pattern) {
|
||||
return sub.replacement;
|
||||
} else if (sub.type === "contains" && appId.includes(sub.pattern)) {
|
||||
return sub.replacement;
|
||||
} else if (sub.type === "regex") {
|
||||
const match = appId.match(new RegExp(sub.pattern));
|
||||
if (match) {
|
||||
return sub.replacement.replace(/\$(\d+)/g, (_, n) => match[n] || "");
|
||||
}
|
||||
}
|
||||
}
|
||||
const steamMatch = appId.match(/^steam_app_(\d+)$/);
|
||||
if (steamMatch)
|
||||
return `steam_icon_${steamMatch[1]}`;
|
||||
return appId;
|
||||
}
|
||||
|
||||
@@ -71,7 +69,7 @@ Singleton {
|
||||
}
|
||||
|
||||
const moddedId = moddedAppId(appId);
|
||||
if (moddedId.startsWith("steam_icon_")) {
|
||||
if (moddedId !== appId) {
|
||||
return Quickshell.iconPath(moddedId, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -221,6 +221,7 @@ Singleton {
|
||||
property bool keyboardLayoutNameCompactMode: false
|
||||
property bool runningAppsCurrentWorkspace: false
|
||||
property bool runningAppsGroupByApp: false
|
||||
property var appIdSubstitutions: []
|
||||
property string centeringMode: "index"
|
||||
property string clockDateFormat: ""
|
||||
property string lockDateFormat: ""
|
||||
@@ -1842,6 +1843,40 @@ Singleton {
|
||||
return workspaceNameIcons[workspaceName] || null;
|
||||
}
|
||||
|
||||
function addAppIdSubstitution(pattern, replacement, type) {
|
||||
var subs = JSON.parse(JSON.stringify(appIdSubstitutions));
|
||||
subs.push({ pattern: pattern, replacement: replacement, type: type });
|
||||
appIdSubstitutions = subs;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function updateAppIdSubstitution(index, pattern, replacement, type) {
|
||||
var subs = JSON.parse(JSON.stringify(appIdSubstitutions));
|
||||
if (index < 0 || index >= subs.length)
|
||||
return;
|
||||
subs[index] = { pattern: pattern, replacement: replacement, type: type };
|
||||
appIdSubstitutions = subs;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function removeAppIdSubstitution(index) {
|
||||
var subs = JSON.parse(JSON.stringify(appIdSubstitutions));
|
||||
if (index < 0 || index >= subs.length)
|
||||
return;
|
||||
subs.splice(index, 1);
|
||||
appIdSubstitutions = subs;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function getDefaultAppIdSubstitutions() {
|
||||
return Spec.SPEC.appIdSubstitutions.def;
|
||||
}
|
||||
|
||||
function resetAppIdSubstitutions() {
|
||||
appIdSubstitutions = JSON.parse(JSON.stringify(Spec.SPEC.appIdSubstitutions.def));
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function getRegistryThemeVariant(themeId, defaultVariant) {
|
||||
var stored = registryThemeVariants[themeId];
|
||||
if (typeof stored === "string")
|
||||
|
||||
@@ -115,6 +115,13 @@ var SPEC = {
|
||||
keyboardLayoutNameCompactMode: { def: false },
|
||||
runningAppsCurrentWorkspace: { def: false },
|
||||
runningAppsGroupByApp: { def: false },
|
||||
appIdSubstitutions: { def: [
|
||||
{ pattern: "Spotify", replacement: "spotify", type: "exact" },
|
||||
{ pattern: "beepertexts", replacement: "beeper", type: "exact" },
|
||||
{ pattern: "home assistant desktop", replacement: "homeassistant-desktop", type: "exact" },
|
||||
{ pattern: "com.transmissionbt.transmission", replacement: "transmission-gtk", type: "contains" },
|
||||
{ pattern: "^steam_app_(\\d+)$", replacement: "steam_icon_$1", type: "regex" }
|
||||
]},
|
||||
centeringMode: { def: "index" },
|
||||
clockDateFormat: { def: "" },
|
||||
lockDateFormat: { def: "" },
|
||||
|
||||
@@ -56,6 +56,13 @@ BasePill {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onAppIdSubstitutionsChanged() {
|
||||
root.updateDesktopEntry();
|
||||
}
|
||||
}
|
||||
|
||||
function updateDesktopEntry() {
|
||||
if (activeWindow && activeWindow.appId) {
|
||||
const moddedId = Paths.moddedAppId(activeWindow.appId);
|
||||
|
||||
@@ -69,6 +69,7 @@ Item {
|
||||
|
||||
property int _desktopEntriesUpdateTrigger: 0
|
||||
property int _toplevelsUpdateTrigger: 0
|
||||
property int _appIdSubstitutionsTrigger: 0
|
||||
|
||||
readonly property var sortedToplevels: {
|
||||
_toplevelsUpdateTrigger;
|
||||
@@ -95,6 +96,13 @@ Item {
|
||||
_desktopEntriesUpdateTrigger++;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onAppIdSubstitutionsChanged() {
|
||||
_appIdSubstitutionsTrigger++;
|
||||
}
|
||||
}
|
||||
readonly property var groupedWindows: {
|
||||
if (!SettingsData.runningAppsGroupByApp) {
|
||||
return [];
|
||||
@@ -364,6 +372,7 @@ Item {
|
||||
height: Theme.barIconSize(root.barThickness)
|
||||
source: {
|
||||
root._desktopEntriesUpdateTrigger;
|
||||
root._appIdSubstitutionsTrigger;
|
||||
if (!appId)
|
||||
return "";
|
||||
const moddedId = Paths.moddedAppId(appId);
|
||||
@@ -596,6 +605,7 @@ Item {
|
||||
height: Theme.barIconSize(root.barThickness)
|
||||
source: {
|
||||
root._desktopEntriesUpdateTrigger;
|
||||
root._appIdSubstitutionsTrigger;
|
||||
if (!appId)
|
||||
return "";
|
||||
const moddedId = Paths.moddedAppId(appId);
|
||||
|
||||
@@ -265,7 +265,8 @@ Item {
|
||||
|
||||
if (!byApp[key]) {
|
||||
const isQuickshell = keyBase === "org.quickshell";
|
||||
const desktopEntry = DesktopEntries.heuristicLookup(keyBase);
|
||||
const moddedId = Paths.moddedAppId(keyBase);
|
||||
const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
|
||||
const icon = Paths.getAppIcon(keyBase, desktopEntry);
|
||||
byApp[key] = {
|
||||
"type": "icon",
|
||||
@@ -1367,6 +1368,9 @@ Item {
|
||||
function onWorkspaceNameIconsChanged() {
|
||||
delegateRoot.updateAllData();
|
||||
}
|
||||
function onAppIdSubstitutionsChanged() {
|
||||
delegateRoot.updateAllData();
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: DwlService
|
||||
|
||||
@@ -49,6 +49,13 @@ Item {
|
||||
updateDesktopEntry();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onAppIdSubstitutionsChanged() {
|
||||
updateDesktopEntry();
|
||||
}
|
||||
}
|
||||
property bool isWindowFocused: {
|
||||
if (!appData) {
|
||||
return false;
|
||||
|
||||
@@ -32,6 +32,159 @@ Item {
|
||||
onToggled: checked => SettingsData.set("runningAppsCurrentWorkspace", checked)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "find_replace"
|
||||
title: I18n.tr("App ID Substitutions")
|
||||
settingKey: "appIdSubstitutions"
|
||||
tags: ["app", "icon", "substitution", "replacement", "pattern", "window", "class", "regex"]
|
||||
|
||||
headerActions: [
|
||||
DankActionButton {
|
||||
buttonSize: 36
|
||||
iconName: "restart_alt"
|
||||
iconSize: 20
|
||||
visible: JSON.stringify(SettingsData.appIdSubstitutions) !== JSON.stringify(SettingsData.getDefaultAppIdSubstitutions())
|
||||
backgroundColor: Theme.surfaceContainer
|
||||
iconColor: Theme.surfaceVariantText
|
||||
onClicked: SettingsData.resetAppIdSubstitutions()
|
||||
},
|
||||
DankActionButton {
|
||||
buttonSize: 36
|
||||
iconName: "add"
|
||||
iconSize: 20
|
||||
backgroundColor: Theme.surfaceContainer
|
||||
iconColor: Theme.primary
|
||||
onClicked: SettingsData.addAppIdSubstitution("", "", "exact")
|
||||
}
|
||||
]
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Map window class names to icon names for proper icon display")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
bottomPadding: Theme.spacingS
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: SettingsData.appIdSubstitutions
|
||||
|
||||
delegate: Rectangle {
|
||||
id: subItem
|
||||
width: parent.width
|
||||
height: subColumn.implicitHeight + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, 0.5)
|
||||
|
||||
Column {
|
||||
id: subColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Column {
|
||||
width: (parent.width - deleteBtn.width - Theme.spacingS) / 2
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Pattern")
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: patternField
|
||||
width: parent.width
|
||||
text: modelData.pattern
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
onEditingFinished: SettingsData.updateAppIdSubstitution(index, text, replacementField.text, modelData.type)
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: (parent.width - deleteBtn.width - Theme.spacingS) / 2
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Replacement")
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: replacementField
|
||||
width: parent.width
|
||||
text: modelData.replacement
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
onEditingFinished: SettingsData.updateAppIdSubstitution(index, patternField.text, text, modelData.type)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: deleteBtn
|
||||
width: 32
|
||||
height: 40
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: deleteArea.containsMouse ? Theme.withAlpha(Theme.error, 0.2) : "transparent"
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "delete"
|
||||
size: 18
|
||||
color: deleteArea.containsMouse ? Theme.error : Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deleteArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: SettingsData.removeAppIdSubstitution(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: 120
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Type")
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
compactMode: true
|
||||
dropdownWidth: 120
|
||||
currentValue: modelData.type
|
||||
options: ["exact", "contains", "regex"]
|
||||
onValueChanged: value => SettingsData.updateAppIdSubstitution(index, modelData.pattern, modelData.replacement, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ StyledRect {
|
||||
|
||||
Row {
|
||||
id: headerActionsRow
|
||||
anchors.right: caretIcon.left
|
||||
anchors.right: root.collapsible ? caretIcon.left : parent.right
|
||||
anchors.rightMargin: root.collapsible ? Theme.spacingS : 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
@@ -172,6 +172,7 @@ StyledRect {
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
visible: root.collapsible
|
||||
anchors.left: caretIcon.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
@@ -33,8 +33,8 @@ Item {
|
||||
|
||||
property var iconToWindowRatio: 0.25
|
||||
property var iconToWindowRatioCompact: 0.45
|
||||
property var entry: DesktopEntries.heuristicLookup(windowData?.class)
|
||||
property var iconPath: Quickshell.iconPath(entry?.icon ?? windowData?.class ?? "application-x-executable", "image-missing")
|
||||
property var entry: DesktopEntries.heuristicLookup(Paths.moddedAppId(windowData?.class ?? ""))
|
||||
property var iconPath: Paths.getAppIcon(windowData?.class ?? "", entry) || Quickshell.iconPath("application-x-executable", "image-missing")
|
||||
property bool compactMode: Theme.fontSizeSmall * 4 > targetWindowHeight || Theme.fontSizeSmall * 4 > targetWindowWidth
|
||||
|
||||
x: initX
|
||||
|
||||
@@ -3153,33 +3153,6 @@
|
||||
"icon": "lock",
|
||||
"description": "If the field is hidden, it will appear as soon as a key is pressed."
|
||||
},
|
||||
{
|
||||
"section": "lockScreenNotificationMode",
|
||||
"label": "Notification Display",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock Screen",
|
||||
"keywords": [
|
||||
"alert",
|
||||
"control",
|
||||
"display",
|
||||
"information",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"monitor",
|
||||
"notif",
|
||||
"notification",
|
||||
"notifications",
|
||||
"output",
|
||||
"password",
|
||||
"privacy",
|
||||
"screen",
|
||||
"security",
|
||||
"shown",
|
||||
"what"
|
||||
],
|
||||
"description": "Control what notification information is shown on the lock screen"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenShowPasswordField",
|
||||
"label": "Show Password Field",
|
||||
@@ -3915,6 +3888,35 @@
|
||||
],
|
||||
"description": "Timeout for normal priority notifications"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenNotificationMode",
|
||||
"label": "Notification Display",
|
||||
"tabIndex": 17,
|
||||
"category": "Notifications",
|
||||
"keywords": [
|
||||
"alert",
|
||||
"alerts",
|
||||
"control",
|
||||
"display",
|
||||
"information",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"messages",
|
||||
"monitor",
|
||||
"notif",
|
||||
"notification",
|
||||
"notifications",
|
||||
"output",
|
||||
"privacy",
|
||||
"screen",
|
||||
"security",
|
||||
"shown",
|
||||
"toast",
|
||||
"what"
|
||||
],
|
||||
"description": "Control what notification information is shown on the lock screen"
|
||||
},
|
||||
{
|
||||
"section": "notificationOverlayEnabled",
|
||||
"label": "Notification Overlay",
|
||||
@@ -4036,6 +4038,29 @@
|
||||
"icon": "tune",
|
||||
"description": "Choose where on-screen displays appear on screen"
|
||||
},
|
||||
{
|
||||
"section": "appIdSubstitutions",
|
||||
"label": "App ID Substitutions",
|
||||
"tabIndex": 19,
|
||||
"category": "Running Apps",
|
||||
"keywords": [
|
||||
"active",
|
||||
"app",
|
||||
"apps",
|
||||
"class",
|
||||
"icon",
|
||||
"pattern",
|
||||
"regex",
|
||||
"replacement",
|
||||
"running",
|
||||
"substitution",
|
||||
"substitutions",
|
||||
"tasks",
|
||||
"window",
|
||||
"windows"
|
||||
],
|
||||
"icon": "find_replace"
|
||||
},
|
||||
{
|
||||
"section": "runningApps",
|
||||
"label": "Running Apps Settings",
|
||||
|
||||
Reference in New Issue
Block a user