improve icon guessing

This commit is contained in:
end-4 2025-05-29 23:27:46 +02:00
parent f942fb086a
commit 8ecf9a2597
6 changed files with 80 additions and 117 deletions

View file

@ -2,7 +2,6 @@ import "root:/"
import "root:/services/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/icons.js" as Icons
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@ -178,7 +177,7 @@ Item {
return winArea > maxArea ? win : maxWin
}, null)
}
property var mainAppIconSource: Quickshell.iconPath(Icons.noKnowledgeIconGuess(biggestWindow?.class), "image-missing")
property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing")
StyledText {
opacity: (ConfigOptions.bar.workspaces.alwaysShowNumbers || GlobalStates.workspaceShowNumbers || !workspaceButtonBackground.biggestWindow) ? 1 : 0

View file

@ -1,92 +0,0 @@
.pragma library
/**
* @type {{[key: string]: string}}
*/
const substitutions = {
"code-url-handler": "visual-studio-code",
"Code": "visual-studio-code",
"GitHub Desktop": "github-desktop",
"Minecraft* 1.20.1": "minecraft",
"gnome-tweaks": "org.gnome.tweaks",
"pavucontrol-qt": "pavucontrol",
"wps": "wps-office2019-kprometheus",
"wpsoffice": "wps-office2019-kprometheus",
"footclient": "foot",
"zen": "zen-browser",
"": "image-missing"
}
/**
* @type {{[key: string]: string}}
*/
const regexSubstitutions = [
{
"regex": "/^steam_app_(\\d+)$/",
"replace": "steam_icon_$1"
}
]
/**
* @param { string } iconName
* @returns { boolean }
*/
function iconExists(iconName) {
return false; // TODO: Make this work without Gtk
}
/**
* @param { string } str
* @returns { string }
*/
function substitute(str) {
// Normal substitutions
if (substitutions[str])
return substitutions[str];
// Regex substitutions
for (let i = 0; i < regexSubstitutions.length; i++) {
const substitution = regexSubstitutions[i];
const replacedName = str.replace(
substitution.regex,
substitution.replace,
);
if (replacedName != str) return replacedName;
}
// Guess: convert to kebab case
if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, "-");
// Original string
return str;
}
/**
* @param { string | undefined } str
* @returns { string }
*/
function noKnowledgeIconGuess(str) {
if (!str) return "image-missing";
// Normal substitutions
if (substitutions[str])
return substitutions[str];
// Regex substitutions
for (let i = 0; i < regexSubstitutions.length; i++) {
const substitution = regexSubstitutions[i];
const replacedName = str.replace(
substitution.regex,
substitution.replace,
);
if (replacedName != str) return replacedName;
}
// Guess: convert to kebab case if it's not reverse domain name notation
if (!str.includes('.')) {
str = str.toLowerCase().replace(/\s+/g, "-");
}
// Original string
return str;
}

View file

@ -42,19 +42,6 @@ function findSuitableMaterialSymbol(summary = "") {
return defaultType;
}
// const getFriendlyNotifTimeString = (timeObject) => {
// const messageTime = GLib.DateTime.new_from_unix_local(timeObject);
// const oneMinuteAgo = GLib.DateTime.new_now_local().add_seconds(-60);
// if (messageTime.compare(oneMinuteAgo) > 0)
// return getString('Now');
// else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year())
// return messageTime.format(userOptions.time.format);
// else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year() - 1)
// return getString('Yesterday');
// else
// return messageTime.format(userOptions.time.dateFormat);
// }
/**
* @param { number | string | Date } timestamp
* @returns { string }
@ -63,13 +50,28 @@ const getFriendlyNotifTimeString = (timestamp) => {
if (!timestamp) return '';
const messageTime = new Date(timestamp);
const now = new Date();
const oneMinuteAgo = new Date(now.getTime() - 60000);
const diffMs = now.getTime() - messageTime.getTime();
if (messageTime > oneMinuteAgo)
// Less than 1 minute
if (diffMs < 60000)
return 'Now';
if (messageTime.toDateString() === now.toDateString())
return Qt.formatDateTime(messageTime, "hh:mm");
// Same day - show relative time
if (messageTime.toDateString() === now.toDateString()) {
const diffMinutes = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
if (diffHours > 0) {
return `${diffHours}h`;
} else {
return `${diffMinutes}m`;
}
}
// Yesterday
if (messageTime.toDateString() === new Date(now.getTime() - 86400000).toDateString())
return 'Yesterday';
// Older dates
return Qt.formatDateTime(messageTime, "MMMM dd");
};
};

View file

@ -1,7 +1,6 @@
import "root:/services/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/icons.js" as Icons
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import Qt5Compat.GraphicalEffects
import QtQuick
@ -33,7 +32,7 @@ Rectangle { // Window
property var iconToWindowRatio: 0.35
property var xwaylandIndicatorToIconRatio: 0.35
property var iconToWindowRatioCompact: 0.6
property var iconPath: Quickshell.iconPath(Icons.noKnowledgeIconGuess(windowData?.class), "image-missing")
property var iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), "image-missing")
property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth
x: initX

View file

@ -1,7 +1,6 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/icons.js" as Icons
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
@ -48,7 +47,7 @@ Item {
sourceSize.width: size
sourceSize.height: size
source: {
const icon = Icons.noKnowledgeIconGuess(root.node.properties["application.icon-name"]);
const icon = AppSearch.guessIcon(root.node.properties["application.icon-name"]);
return Quickshell.iconPath(icon, "image-missing");
}
}

View file

@ -7,12 +7,32 @@ import Quickshell
import Quickshell.Io
/**
* Eases searching for applications by name.
* - Eases fuzzy searching for applications by name
* - Guesses icon name for window class name with normalization, possibly with desktop entry searching later
*/
Singleton {
id: root
property bool sloppySearch: ConfigOptions?.search.sloppy ?? false
property real scoreThreshold: 0.2
property var substitutions: ({
"code-url-handler": "visual-studio-code",
"Code": "visual-studio-code",
"GitHub Desktop": "github-desktop",
"Minecraft* 1.20.1": "minecraft",
"gnome-tweaks": "org.gnome.tweaks",
"pavucontrol-qt": "pavucontrol",
"wps": "wps-office2019-kprometheus",
"wpsoffice": "wps-office2019-kprometheus",
"footclient": "foot",
"zen": "zen-browser",
"": "image-missing"
})
property var regexSubstitutions: [
{
"regex": "/^steam_app_(\\d+)$/",
"replace": "steam_icon_$1"
}
]
readonly property list<DesktopEntry> list: Array.from(DesktopEntries.applications.values)
.sort((a, b) => a.name.localeCompare(b.name))
@ -40,4 +60,40 @@ Singleton {
return r.obj.entry
});
}
function iconExists(iconName) {
return Quickshell.iconPath(iconName, true).length > 0;
}
function guessIcon(str) {
if (!str) return "image-missing";
// Normal substitutions
if (substitutions[str])
return substitutions[str];
// Regex substitutions
for (let i = 0; i < regexSubstitutions.length; i++) {
const substitution = regexSubstitutions[i];
const replacedName = str.replace(
substitution.regex,
substitution.replace,
);
if (replacedName != str) return replacedName;
}
// If it gets detected normally, no need to guess
if (iconExists(str)) return str;
let guessStr = str;
// Guess: Take only app name of reverse domain name notation
guessStr = str.split('.').slice(-1)[0].toLowerCase();
if (iconExists(guessStr)) return guessStr;
// Guess: normalize to kebab case
guessStr = str.toLowerCase().replace(/\s+/g, "-");
if (iconExists(guessStr)) return guessStr;
// Give up
return str;
}
}