volume is cracked

This commit is contained in:
Zacharias-Brohn
2026-01-26 23:38:34 +01:00
parent e2a5eef008
commit b41b8467bf
2 changed files with 472 additions and 52 deletions
+286
View File
@@ -0,0 +1,286 @@
pragma Singleton
import QtQuick
import Quickshell
Singleton {
id: root
property real scoreThreshold: 0.2
// Manual overrides for tricky apps
property var substitutions: ({
"code-url-handler": "visual-studio-code",
"Code": "visual-studio-code",
"gnome-tweaks": "org.gnome.tweaks",
"pavucontrol-qt": "pavucontrol",
"wps": "wps-office2019-kprometheus",
"wpsoffice": "wps-office2019-kprometheus",
"footclient": "foot"
})
// Dynamic fixups
property var regexSubstitutions: [
{
"regex": /^steam_app_(\d+)$/,
"replace": "steam_icon_$1"
},
{
"regex": /Minecraft.*/,
"replace": "minecraft-launcher"
},
{
"regex": /.*polkit.*/,
"replace": "system-lock-screen"
},
{
"regex": /gcr.prompter/,
"replace": "system-lock-screen"
}
]
property list<DesktopEntry> entryList: []
property var preppedNames: []
property var preppedIcons: []
property var preppedIds: []
Component.onCompleted: refreshEntries()
Connections {
target: DesktopEntries.applications
function onValuesChanged() {
refreshEntries();
}
}
function refreshEntries() {
if (typeof DesktopEntries === 'undefined')
return;
const values = Array.from(DesktopEntries.applications.values);
if (values) {
entryList = values.sort((a, b) => a.name.localeCompare(b.name));
updatePreppedData();
}
}
function updatePreppedData() {
if (typeof FuzzySort === 'undefined')
return;
const list = Array.from(entryList);
preppedNames = list.map(a => ({
name: FuzzySort.prepare(`${a.name} `), entry: a}));
preppedIcons = list.map(a => ({
name: FuzzySort.prepare(`${a.icon} `),
entry: a
}));
preppedIds = list.map(a => ({
name: FuzzySort.prepare(`${a.id} `),
entry: a
}));
}
function iconForAppId(appId, fallbackName) {
const fallback = fallbackName || "application-x-executable";
if (!appId)
return iconFromName(fallback, fallback);
const entry = findAppEntry(appId);
if (entry) {
return iconFromName(entry.icon, fallback);
}
return iconFromName(appId, fallback);
}
// Robust lookup strategy
function findAppEntry(str) {
if (!str || str.length === 0)
return null;
let result = null;
if (result = checkHeuristic(str))
return result;
if (result = checkSubstitutions(str))
return result;
if (result = checkRegex(str))
return result;
if (result = checkSimpleTransforms(str))
return result;
if (result = checkFuzzySearch(str))
return result;
if (result = checkCleanMatch(str))
return result;
return null;
}
function iconFromName(iconName, fallbackName) {
const fallback = fallbackName || "application-x-executable";
try {
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
const p = Quickshell.iconPath(iconName, fallback);
if (p && p !== "")
return p;
}
} catch (e) {}
try {
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "";
} catch (e2) {
return "";
}
}
function distroLogoPath() {
try {
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "";
} catch (e) {
return "";
}
}
// --- Lookup Helpers ---
function checkHeuristic(str) {
if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) {
const entry = DesktopEntries.heuristicLookup(str);
if (entry)
return entry;
}
return null;
}
function checkSubstitutions(str) {
let effectiveStr = substitutions[str];
if (!effectiveStr)
effectiveStr = substitutions[str.toLowerCase()];
if (effectiveStr && effectiveStr !== str) {
return findAppEntry(effectiveStr);
}
return null;
}
function checkRegex(str) {
for (let i = 0; i < regexSubstitutions.length; i++) {
const sub = regexSubstitutions[i];
const replaced = str.replace(sub.regex, sub.replace);
if (replaced !== str) {
return findAppEntry(replaced);
}
}
return null;
}
function checkSimpleTransforms(str) {
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return null;
const lower = str.toLowerCase();
const variants = [str, lower, getFromReverseDomain(str), getFromReverseDomain(str)?.toLowerCase(), normalizeWithHyphens(str), str.replace(/_/g, '-').toLowerCase(), str.replace(/-/g, '_').toLowerCase()];
for (let i = 0; i < variants.length; i++) {
const variant = variants[i];
if (variant) {
const entry = DesktopEntries.byId(variant);
if (entry)
return entry;
}
}
return null;
}
function checkFuzzySearch(str) {
if (typeof FuzzySort === 'undefined')
return null;
// Check filenames (IDs) first
if (preppedIds.length > 0) {
let results = fuzzyQuery(str, preppedIds);
if (results.length === 0) {
const underscored = str.replace(/-/g, '_').toLowerCase();
if (underscored !== str)
results = fuzzyQuery(underscored, preppedIds);
}
if (results.length > 0)
return results[0];
}
// Then icons
if (preppedIcons.length > 0) {
const results = fuzzyQuery(str, preppedIcons);
if (results.length > 0)
return results[0];
}
// Then names
if (preppedNames.length > 0) {
const results = fuzzyQuery(str, preppedNames);
if (results.length > 0)
return results[0];
}
return null;
}
function checkCleanMatch(str) {
if (!str || str.length <= 3)
return null;
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return null;
// Aggressive fallback: strip all separators
const cleanStr = str.toLowerCase().replace(/[\.\-_]/g, '');
const list = Array.from(entryList);
for (let i = 0; i < list.length; i++) {
const entry = list[i];
const cleanId = (entry.id || "").toLowerCase().replace(/[\.\-_]/g, '');
if (cleanId.includes(cleanStr) || cleanStr.includes(cleanId)) {
return entry;
}
}
return null;
}
function fuzzyQuery(search, preppedData) {
if (!search || !preppedData || preppedData.length === 0)
return [];
return FuzzySort.go(search, preppedData, {
all: true,
key: "name"
}).map(r => r.obj.entry);
}
function iconExists(iconName) {
if (!iconName || iconName.length === 0)
return false;
if (iconName.startsWith("/"))
return true;
const path = Quickshell.iconPath(iconName, true);
return path && path.length > 0 && !path.includes("image-missing");
}
function getFromReverseDomain(str) {
if (!str)
return "";
return str.split('.').slice(-1)[0];
}
function normalizeWithHyphens(str) {
if (!str)
return "";
return str.toLowerCase().replace(/\s+/g, "-");
}
// Deprecated shim
function guessIcon(str) {
const entry = findAppEntry(str);
return entry ? entry.icon : "image-missing";
}
}
+146 -12
View File
@@ -9,6 +9,7 @@ import QtQuick.Controls
import qs.Config import qs.Config
import qs.Components import qs.Components
import qs.Daemons import qs.Daemons
import qs.Helpers
Item { Item {
id: root id: root
@@ -125,29 +126,45 @@ Item {
component VolumesTab: ColumnLayout { component VolumesTab: ColumnLayout {
spacing: 12 spacing: 12
RowLayout {
Layout.topMargin: 10
spacing: 15
Rectangle {
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.tPalette.m3primaryContainer
radius: 1000
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.palette.m3onPrimaryContainer
text: "volume_up"
font.pointSize: 22
}
}
ColumnLayout {
Layout.fillWidth: true
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true
CustomText { CustomText {
text: "Output Volume" text: "Output Volume"
elide: Text.ElideRight
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
} }
CustomText { CustomText {
text: qsTr("%1").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`); text: qsTr("%1").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`);
font.bold: true font.bold: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
} }
} }
CustomMouseArea { CustomMouseArea {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: 10 Layout.preferredHeight: 10
Layout.bottomMargin: 5
CustomSlider { CustomSlider {
anchors.fill: parent anchors.fill: parent
@@ -157,29 +174,50 @@ Item {
Behavior on value { Anim {} } Behavior on value { Anim {} }
} }
} }
}
}
RowLayout {
Layout.topMargin: 10
spacing: 15
Rectangle {
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.tPalette.m3primaryContainer
radius: 1000
MaterialIcon {
anchors.centerIn: parent
anchors.alignWhenCentered: false
color: DynamicColors.palette.m3onPrimaryContainer
text: "mic"
font.pointSize: 22
}
}
ColumnLayout {
Layout.fillWidth: true
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
CustomText { CustomText {
text: "Input Volume" text: "Input Volume"
elide: Text.ElideRight
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
} }
CustomText { CustomText {
text: qsTr("%1").arg(Audio.sourceMuted ? qsTr("Muted") : `${Math.round(Audio.sourceVolume * 100)}%`); text: qsTr("%1").arg(Audio.sourceMuted ? qsTr("Muted") : `${Math.round(Audio.sourceVolume * 100)}%`);
font.bold: true font.bold: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
} }
} }
CustomMouseArea { CustomMouseArea {
Layout.fillWidth: true Layout.fillWidth: true
Layout.bottomMargin: 5
implicitHeight: 10 implicitHeight: 10
CustomSlider { CustomSlider {
@@ -190,6 +228,16 @@ Item {
Behavior on value { Anim {} } Behavior on value { Anim {} }
} }
} }
}
}
Rectangle {
Layout.topMargin: 10
Layout.fillWidth: true
Layout.preferredHeight: 1
color: DynamicColors.tPalette.m3outline
}
Repeater { Repeater {
model: Audio.appStreams model: Audio.appStreams
@@ -203,6 +251,90 @@ Item {
visible: !isCaptureStream visible: !isCaptureStream
required property PwNode modelData required property PwNode modelData
function isValidMatch(searchTerm, entry) {
if (!entry)
return false;
var search = searchTerm.toLowerCase();
var id = (entry.id || "").toLowerCase();
var name = (entry.name || "").toLowerCase();
var icon = (entry.icon || "").toLowerCase();
// Match is valid if search term appears in entry or entry appears in search
return id.includes(search) || name.includes(search) || icon.includes(search) || search.includes(id.split('.').pop()) || search.includes(name.replace(/\s+/g, ''));
}
readonly property string appName: {
if (!modelData)
return "Unknown App";
var props = modelData.properties;
var desc = modelData.description || "";
var name = modelData.name || "";
var mediaName = props["media.name"] || "";
if ( mediaName !== "playStream" ) {
return mediaName;
}
if (!props) {
if (desc)
return desc;
if (name) {
var nameParts = name.split(/[-_]/);
if (nameParts.length > 0 && nameParts[0])
return nameParts[0].charAt(0).toUpperCase() + nameParts[0].slice(1);
return name;
}
return "Unknown App";
}
var binaryName = props["application.process.binary"] || "";
// Try binary name first (fixes Electron apps like vesktop)
if (binaryName) {
var binParts = binaryName.split("/");
if (binParts.length > 0) {
var binName = binParts[binParts.length - 1].toLowerCase();
var entry = ThemeIcons.findAppEntry(binName);
// Only use entry if it's actually related to binary name
if (entry && entry.name && isValidMatch(binName, entry))
return entry.name;
}
}
var computedAppName = props["application.name"] || "";
var mediaName = props["media.name"] || "";
var appId = props["application.id"] || "";
if (appId) {
var entry = ThemeIcons.findAppEntry(appId);
if (entry && entry.name && isValidMatch(appId, entry))
return entry.name;
if (!computedAppName) {
var parts = appId.split(".");
if (parts.length > 0 && parts[0])
computedAppName = parts[0].charAt(0).toUpperCase() + parts[0].slice(1);
}
}
if (!computedAppName && binaryName) {
var binParts = binaryName.split("/");
if (binParts.length > 0 && binParts[binParts.length - 1])
computedAppName = binParts[binParts.length - 1].charAt(0).toUpperCase() + binParts[binParts.length - 1].slice(1);
}
var result = computedAppName || mediaTitle || mediaName || binaryName || desc || name;
if (!result || result === "" || result === "Unknown App") {
if (name) {
var nameParts = name.split(/[-_]/);
if (nameParts.length > 0 && nameParts[0])
result = nameParts[0].charAt(0).toUpperCase() + nameParts[0].slice(1);
}
}
return result || "Unknown App";
}
PwObjectTracker { PwObjectTracker {
objects: appBox.modelData ? [appBox.modelData] : [] objects: appBox.modelData ? [appBox.modelData] : []
} }
@@ -233,8 +365,10 @@ Item {
spacing: 15 spacing: 15
IconImage { IconImage {
property string iconPath: Quickshell.iconPath(DesktopEntries.byId(appBox.modelData.name).icon) id: icon
source: iconPath !== "" ? iconPath : Quickshell.iconPath("application-x-executable") property string iconPath1: Quickshell.iconPath(DesktopEntries.byId(appBox.modelData.name).icon);
property string iconPath2: Quickshell.iconPath(DesktopEntries.byId(appBox.appName).icon);
source: iconPath1 !== "" ? iconPath1 : iconPath2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
implicitSize: 42 implicitSize: 42
} }
@@ -245,7 +379,7 @@ Item {
TextMetrics { TextMetrics {
id: metrics id: metrics
text: appBox.modelData.properties["media.name"] text: appBox.appName
elide: Text.ElideRight elide: Text.ElideRight
elideWidth: root.width - 50 elideWidth: root.width - 50
} }
@@ -280,7 +414,7 @@ Item {
value: appBox.modelData.audio.volume value: appBox.modelData.audio.volume
onMoved: { onMoved: {
Audio.setAppAudioVolume(appBox.modelData, value) Audio.setAppAudioVolume(appBox.modelData, value)
console.log(layoutVolume.implicitHeight) console.log(icon.iconPath1, icon.iconPath2)
} }
} }
} }