Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

automatically guess icons for overview #427

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .config/ags/modules/.configuration/user_options.js
Original file line number Diff line number Diff line change
Expand Up @@ -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': [
Expand Down
89 changes: 87 additions & 2 deletions .config/ags/modules/.miscutils/icons.js
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -10,4 +13,86 @@ export function substitute(str) {

if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, '-'); // Turn into kebab-case
return str;
}
}

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()
33 changes: 5 additions & 28 deletions .config/ags/modules/dock/dock.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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),
});
Expand All @@ -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),
})
Expand Down Expand Up @@ -210,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))
Expand Down
63 changes: 0 additions & 63 deletions .config/ags/modules/dock/icons.js

This file was deleted.

4 changes: 2 additions & 2 deletions .config/ags/modules/overview/overview_hyprland.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)];
Expand Down Expand Up @@ -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({
Expand Down