From 1aec7ac5d46e2f5de9864950fe751d359814c151 Mon Sep 17 00:00:00 2001 From: casglistro Date: Fri, 19 Apr 2024 13:51:51 +0800 Subject: [PATCH 1/2] feat: :sparkles: automatically guess icons for overview --- .config/ags/modules/.miscutils/icons.js | 89 ++++++++++++++++++- .config/ags/modules/dock/dock.js | 28 +----- .config/ags/modules/dock/icons.js | 63 ------------- .../ags/modules/overview/overview_hyprland.js | 4 +- 4 files changed, 93 insertions(+), 91 deletions(-) delete mode 100644 .config/ags/modules/dock/icons.js diff --git a/.config/ags/modules/.miscutils/icons.js b/.config/ags/modules/.miscutils/icons.js index fb1e20da3..d414c7799 100644 --- a/.config/ags/modules/.miscutils/icons.js +++ b/.config/ags/modules/.miscutils/icons.js @@ -1,4 +1,7 @@ -const { Gtk } = imports.gi; +const { Gtk, Gio } = imports.gi; + +import { fileExists } from "./files.js"; +import Applications from 'resource:///com/github/Aylur/ags/service/applications.js'; export function iconExists(iconName) { let iconTheme = Gtk.IconTheme.get_default(); @@ -10,4 +13,86 @@ export function substitute(str) { if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, '-'); // Turn into kebab-case return str; -} \ No newline at end of file +} + +export const levenshteinDistance = (a, b) => { + if (!a.length) { return b.length } + if (!b.length) { return a.length } + + let f = Array.from(new Array(a.length + 1), + () => new Array(b.length + 1).fill(0)) + + for (let i = 0; i <= b.length; i++) { f[0][i] = i; } + for (let i = 0; i <= a.length; i++) { f[i][0] = i; } + + for (let i = 1; i <= a.length; i++) { + for (let j = 1; j <= b.length; j++) { + if (a.charAt(i - 1) === b.charAt(j - 1)) { + f[i][j] = f[i-1][j-1] + } else { + f[i][j] = Math.min(f[i-1][j-1], Math.min(f[i][j-1], f[i-1][j])) + 1 + } + } + } + + return f[a.length][b.length] +} + +export const getAllFiles = (dir, files = []) => { + if (!fileExists(dir)) { return [] } + const file = Gio.File.new_for_path(dir); + const enumerator = file.enumerate_children('standard::name,standard::type', + Gio.FileQueryInfoFlags.NONE, null); + + for (const info of enumerator) { + if (info.get_file_type() === Gio.FileType.DIRECTORY) { + files.push(getAllFiles(`${dir}/${info.get_name()}`)) + } else { + files.push(`${dir}/${info.get_name()}`) + } + } + + return files.flat(1); +} + +export const searchIcons = (appClass) => { + const appClassLower = appClass.toLowerCase() + let path = searchIconsByAppName(appClassLower) + if (path === "") { + if (cachePath[appClassLower]) { path = cachePath[appClassLower] } + else { + path = searchIconsInPath(appClass.toLowerCase(), icon_files) + cachePath[appClassLower] = path + } + } + if (path === "") { path = substitute(appClass) } + return path +} + +export const searchIconsInPath = (appClass, files) => { + appClass = appClass.toLowerCase() + + if (!files.length) { return "" } + + let appro = 0x3f3f3f3f + let path = "" + + for (const item of files) { + let score = levenshteinDistance(item.split("/").pop().toLowerCase().split(".")[0], appClass) + + if (score < appro) { + appro = score + path = item + } + } + + return path +} + +export const searchIconsByAppName = (appName) => { + let app = Applications.query(appName)?.[0] + return app ? app.icon_name : '' +} + +export const icon_files = userOptions.icons.searchPaths.map(e => getAllFiles(e)).flat(1) +export let cachePath = new Map() \ No newline at end of file diff --git a/.config/ags/modules/dock/dock.js b/.config/ags/modules/dock/dock.js index 4e6cc326b..52b7439b1 100644 --- a/.config/ags/modules/dock/dock.js +++ b/.config/ags/modules/dock/dock.js @@ -9,14 +9,10 @@ import Applications from 'resource:///com/github/Aylur/ags/service/applications. const { execAsync, exec } = Utils; const { Box, Revealer } = Widget; import { setupCursorHover } from '../.widgetutils/cursorhover.js'; -import { getAllFiles, searchIcons } from './icons.js' +import { searchIcons } from '../.miscutils/icons.js' import { MaterialIcon } from '../.commonwidgets/materialicon.js'; -const icon_files = userOptions.icons.searchPaths.map(e => getAllFiles(e)).flat(1) - let isPinned = false -let cachePath = new Map() - let timers = [] function clearTimes() { @@ -134,16 +130,8 @@ const Taskbar = (monitor) => Widget.Box({ // if (appClass.includes(appName.toLowerCase())) // return null; // } - let appClassLower = appClass.toLowerCase() - let path = '' - if (cachePath[appClassLower]) { path = cachePath[appClassLower] } - else { - path = searchIcons(appClass.toLowerCase(), icon_files) - cachePath[appClassLower] = path - } - if (path === '') { path = substitute(appClass) } const newButton = AppButton({ - icon: path, + icon: searchIcons(appClass), tooltipText: `${client.title} (${appClass})`, onClicked: () => focus(client), }); @@ -162,17 +150,9 @@ const Taskbar = (monitor) => Widget.Box({ return client.address == address; }); if (ExclusiveWindow(newClient)) { return } - let appClass = newClient.class - let appClassLower = appClass.toLowerCase() - let path = '' - if (cachePath[appClassLower]) { path = cachePath[appClassLower] } - else { - path = searchIcons(appClassLower, icon_files) - cachePath[appClassLower] = path - } - if (path === '') { path = substitute(appClass) } + let appClass = substitute(newClient.class) const newButton = AppButton({ - icon: path, + icon: searchIcons(appClass), tooltipText: `${newClient.title} (${appClass})`, onClicked: () => focus(newClient), }) diff --git a/.config/ags/modules/dock/icons.js b/.config/ags/modules/dock/icons.js deleted file mode 100644 index 60f01a3a1..000000000 --- a/.config/ags/modules/dock/icons.js +++ /dev/null @@ -1,63 +0,0 @@ -const { Gio, GLib } = imports.gi - -const exists = (path) => Gio.File.new_for_path(path).query_exists(null); - -export const levenshteinDistance = (a, b) => { - if (!a.length) { return b.length } - if (!b.length) { return a.length } - - let f = Array.from(new Array(a.length + 1), - () => new Array(b.length + 1).fill(0)) - - for (let i = 0; i <= b.length; i++) { f[0][i] = i; } - for (let i = 0; i <= a.length; i++) { f[i][0] = i; } - - for (let i = 1; i <= a.length; i++) { - for (let j = 1; j <= b.length; j++) { - if (a.charAt(i - 1) === b.charAt(j - 1)) { - f[i][j] = f[i-1][j-1] - } else { - f[i][j] = Math.min(f[i-1][j-1], Math.min(f[i][j-1], f[i-1][j])) + 1 - } - } - } - - return f[a.length][b.length] -} - -export const getAllFiles = (dir, files = []) => { - if (!exists(dir)) { return [] } - const file = Gio.File.new_for_path(dir); - const enumerator = file.enumerate_children('standard::name,standard::type', - Gio.FileQueryInfoFlags.NONE, null); - - for (const info of enumerator) { - if (info.get_file_type() === Gio.FileType.DIRECTORY) { - files.push(getAllFiles(`${dir}/${info.get_name()}`)) - } else { - files.push(`${dir}/${info.get_name()}`) - } - } - - return files.flat(1); -} - -export const searchIcons = (appClass, files) => { - appClass = appClass.toLowerCase() - - if (!files.length) { return "" } - - let appro = 0x3f3f3f3f - let path = "" - - for (const item of files) { - let score = levenshteinDistance(item.split("/").pop().toLowerCase().split(".")[0], appClass) - - if (score < appro) { - appro = score - path = item - } - } - - return path -} \ No newline at end of file diff --git a/.config/ags/modules/overview/overview_hyprland.js b/.config/ags/modules/overview/overview_hyprland.js index 1419ee172..a0e495190 100644 --- a/.config/ags/modules/overview/overview_hyprland.js +++ b/.config/ags/modules/overview/overview_hyprland.js @@ -14,7 +14,7 @@ import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; const { execAsync, exec } = Utils; import { setupCursorHoverGrab } from '../.widgetutils/cursorhover.js'; import { dumpToWorkspace, swapWorkspace } from "./actions.js"; -import { substitute } from "../.miscutils/icons.js"; +import { substitute, searchIcons } from "../.miscutils/icons.js"; const NUM_OF_WORKSPACES_SHOWN = userOptions.overview.numOfCols * userOptions.overview.numOfRows; const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)]; @@ -65,7 +65,7 @@ export default () => { if (y + h > SCREEN_HEIGHT) h = SCREEN_HEIGHT - y; const appIcon = Widget.Icon({ - icon: substitute(c), + icon: searchIcons(substitute(c)), size: Math.min(w, h) * userOptions.overview.scale / 2.5, }); return Widget.Button({ From c8877f30889ed0aa5896b06f27fa5f4ff062c9a1 Mon Sep 17 00:00:00 2001 From: casglistro Date: Mon, 22 Apr 2024 12:18:34 +0800 Subject: [PATCH 2/2] fix: :bug: Fixed PinnedApps icon guessing and removed searchPinnedAppIcons --- .config/ags/modules/.configuration/user_options.js | 1 - .config/ags/modules/dock/dock.js | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.config/ags/modules/.configuration/user_options.js b/.config/ags/modules/.configuration/user_options.js index 6ade5e1b7..6720ef3c8 100644 --- a/.config/ags/modules/.configuration/user_options.js +++ b/.config/ags/modules/.configuration/user_options.js @@ -82,7 +82,6 @@ let configOptions = { 'pinnedApps': ['firefox', 'org.gnome.Nautilus'], 'layer': 'top', 'monitorExclusivity': true, // Dock will move to other monitor along with focus if enabled - 'searchPinnedAppIcons': false, // Try to search for the correct icon if the app class isn't an icon name 'trigger': ['client-added', 'client-removed'], // client_added, client_move, workspace_active, client_active // Automatically hide dock after `interval` ms since trigger 'autoHide': [ diff --git a/.config/ags/modules/dock/dock.js b/.config/ags/modules/dock/dock.js index 52b7439b1..810941da0 100644 --- a/.config/ags/modules/dock/dock.js +++ b/.config/ags/modules/dock/dock.js @@ -190,10 +190,7 @@ const PinnedApps = () => Widget.Box({ .filter(({ app }) => app) .map(({ app, term = true }) => { const newButton = AppButton({ - // different icon, emm... - icon: userOptions.dock.searchPinnedAppIcons ? - searchIcons(app.name, icon_files) : - app.icon_name, + icon: searchIcons(app.name), onClicked: () => { for (const client of Hyprland.clients) { if (client.class.toLowerCase().includes(term))