diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 1c9eb413..daa0845f 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -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 diff --git a/.config/quickshell/modules/common/functions/icons.js b/.config/quickshell/modules/common/functions/icons.js deleted file mode 100644 index 6bead3d5..00000000 --- a/.config/quickshell/modules/common/functions/icons.js +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/.config/quickshell/modules/common/widgets/notification_utils.js b/.config/quickshell/modules/common/widgets/notification_utils.js index 01bf3d87..9b151055 100644 --- a/.config/quickshell/modules/common/widgets/notification_utils.js +++ b/.config/quickshell/modules/common/widgets/notification_utils.js @@ -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"); -}; +}; \ No newline at end of file diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index 67faa962..658342dd 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -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 diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml index 78b48be2..97745977 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml @@ -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"); } } diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml index 742126e0..4e32eb30 100644 --- a/.config/quickshell/services/AppSearch.qml +++ b/.config/quickshell/services/AppSearch.qml @@ -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 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; + } }