Add up-to-date default workspace labels (#709)

* Add up-to-date workspace labels

* Use iterators instead of creating copies of the mappings

* Re-add guard clause

Signed-off-by: davfsa <davfsa@gmail.com>

* merge origin

* Fix duplicate icons and simplify implementation.

---------

Signed-off-by: davfsa <davfsa@gmail.com>
Co-authored-by: Jas Singh <jaskiratpal.singh@outlook.com>
This commit is contained in:
davfsa
2025-03-18 03:05:14 +01:00
committed by GitHub
parent 14b17c0667
commit 7fe89ac5eb
5 changed files with 187 additions and 197 deletions

View File

@@ -17,12 +17,8 @@ const cpuPoller = new FunctionPoller<number, []>(cpuUsage, [bind(round)], bind(p
cpuPoller.initialize('cpu'); cpuPoller.initialize('cpu');
export const Cpu = (): BarBoxChild => { export const Cpu = (): BarBoxChild => {
const renderLabel = (cpuUsg: number, rnd: boolean): string => { const labelBinding = Variable.derive([bind(cpuUsage), bind(round)], (cpuUsg: number, round: boolean) => {
return rnd ? `${Math.round(cpuUsg)}%` : `${cpuUsg.toFixed(2)}%`; return round ? `${Math.round(cpuUsg)}%` : `${cpuUsg.toFixed(2)}%`;
};
const labelBinding = Variable.derive([bind(cpuUsage), bind(round)], (cpuUsg, rnd) => {
return renderLabel(cpuUsg, rnd);
}); });
const cpuModule = Module({ const cpuModule = Module({

View File

@@ -1,12 +1,16 @@
import options from 'src/options'; import options from 'src/options';
import { capitalizeFirstLetter } from 'src/lib/utils'; import { capitalizeFirstLetter } from 'src/lib/utils';
import { defaultWindowTitleMap } from 'src/lib/constants/appIcons';
import AstalHyprland from 'gi://AstalHyprland?version=0.1'; import AstalHyprland from 'gi://AstalHyprland?version=0.1';
import { bind, Variable } from 'astal'; import { bind, Variable } from 'astal';
const { title_map: userDefinedTitles } = options.bar.windowtitle;
const hyprlandService = AstalHyprland.get_default(); const hyprlandService = AstalHyprland.get_default();
export const clientTitle = Variable('');
let clientBinding: Variable<void> | undefined; let clientBinding: Variable<void> | undefined;
export const clientTitle = Variable('');
function trackClientUpdates(client: AstalHyprland.Client): void { function trackClientUpdates(client: AstalHyprland.Client): void {
clientBinding?.drop(); clientBinding?.drop();
clientBinding = undefined; clientBinding = undefined;
@@ -30,141 +34,25 @@ Variable.derive([bind(hyprlandService, 'focusedClient')], (client) => {
* This function searches for a matching window title in the predefined `windowTitleMap` based on the class of the provided window. * This function searches for a matching window title in the predefined `windowTitleMap` based on the class of the provided window.
* If a match is found, it returns an object containing the icon and label for the window. If no match is found, it returns a default icon and label. * If a match is found, it returns an object containing the icon and label for the window. If no match is found, it returns a default icon and label.
* *
* @param client The window object containing the class information. * @param hyprlandClient The window object containing the class information.
* *
* @returns An object containing the icon and label for the window. * @returns An object containing the icon and label for the window.
*/ */
export const getWindowMatch = (client: AstalHyprland.Client): Record<string, string> => { export const getWindowMatch = (hyprlandClient: AstalHyprland.Client): Record<string, string> => {
const windowTitleMap = [ if (!hyprlandClient?.class) {
// user provided values
...options.bar.windowtitle.title_map.get(),
// Original Entries
['kitty', '󰄛', 'Kitty Terminal'],
['firefox', '󰈹', 'Firefox'],
['microsoft-edge', '󰇩', 'Edge'],
['discord', '', 'Discord'],
['vesktop', '', 'Vesktop'],
['org.kde.dolphin', '', 'Dolphin'],
['plex', '󰚺', 'Plex'],
['steam', '', 'Steam'],
['spotify', '󰓇', 'Spotify'],
['ristretto', '󰋩', 'Ristretto'],
['obsidian', '󱓧', 'Obsidian'],
// Browsers
['google-chrome', '', 'Google Chrome'],
['brave-browser', '󰖟', 'Brave Browser'],
['chromium', '', 'Chromium'],
['opera', '', 'Opera'],
['vivaldi', '󰖟', 'Vivaldi'],
['waterfox', '󰖟', 'Waterfox'],
['thorium', '󰖟', 'Thorium'],
['tor-browser', '', 'Tor Browser'],
['floorp', '󰈹', 'Floorp'],
['zen', '', 'Zen Browser'],
// Terminals
['gnome-terminal', '', 'GNOME Terminal'],
['konsole', '', 'Konsole'],
['alacritty', '', 'Alacritty'],
['wezterm', '', 'Wezterm'],
['foot', '󰽒', 'Foot Terminal'],
['tilix', '', 'Tilix'],
['xterm', '', 'XTerm'],
['urxvt', '', 'URxvt'],
['com.mitchellh.ghostty', '󰊠', 'Ghostty'],
['st', '', 'st Terminal'],
// Development Tools
['code', '󰨞', 'Visual Studio Code'],
['vscode', '󰨞', 'VS Code'],
['sublime-text', '', 'Sublime Text'],
['atom', '', 'Atom'],
['android-studio', '󰀴', 'Android Studio'],
['intellij-idea', '', 'IntelliJ IDEA'],
['pycharm', '󱃖', 'PyCharm'],
['webstorm', '󱃖', 'WebStorm'],
['phpstorm', '󱃖', 'PhpStorm'],
['eclipse', '', 'Eclipse'],
['netbeans', '', 'NetBeans'],
['docker', '', 'Docker'],
['vim', '', 'Vim'],
['neovim', '', 'Neovim'],
['neovide', '', 'Neovide'],
['emacs', '', 'Emacs'],
// Communication Tools
['slack', '󰒱', 'Slack'],
['telegram-desktop', '', 'Telegram'],
['org.telegram.desktop', '', 'Telegram'],
['whatsapp', '󰖣', 'WhatsApp'],
['teams', '󰊻', 'Microsoft Teams'],
['skype', '󰒯', 'Skype'],
['thunderbird', '', 'Thunderbird'],
// File Managers
['nautilus', '󰝰', 'Files (Nautilus)'],
['thunar', '󰝰', 'Thunar'],
['pcmanfm', '󰝰', 'PCManFM'],
['nemo', '󰝰', 'Nemo'],
['ranger', '󰝰', 'Ranger'],
['doublecmd', '󰝰', 'Double Commander'],
['krusader', '󰝰', 'Krusader'],
// Media Players
['vlc', '󰕼', 'VLC Media Player'],
['mpv', '', 'MPV'],
['rhythmbox', '󰓃', 'Rhythmbox'],
// Graphics Tools
['gimp', '', 'GIMP'],
['inkscape', '', 'Inkscape'],
['krita', '', 'Krita'],
['blender', '󰂫', 'Blender'],
// Video Editing
['kdenlive', '', 'Kdenlive'],
// Games and Gaming Platforms
['lutris', '󰺵', 'Lutris'],
['heroic', '󰺵', 'Heroic Games Launcher'],
['minecraft', '󰍳', 'Minecraft'],
['csgo', '󰺵', 'CS:GO'],
['dota2', '󰺵', 'Dota 2'],
// Office and Productivity
['evernote', '', 'Evernote'],
['sioyek', '', 'Sioyek'],
// Cloud Services and Sync
['dropbox', '󰇣', 'Dropbox'],
// Desktop
['^$', '󰇄', 'Desktop'],
// Fallback icon
['(.+)', '󰣆', `${capitalizeFirstLetter(client?.class ?? 'Unknown')}`],
];
if (!client?.class) {
return { return {
icon: '󰇄', icon: '󰇄',
label: 'Desktop', label: 'Desktop',
}; };
} }
const foundMatch = windowTitleMap.find((wt) => RegExp(wt[0]).test(client?.class.toLowerCase())); const clientClass = hyprlandClient.class.toLowerCase();
const potentialWindowTitles = [...userDefinedTitles.get(), ...defaultWindowTitleMap];
if (!foundMatch || foundMatch.length !== 3) { const windowMatch = potentialWindowTitles.find((title) => RegExp(title[0]).test(clientClass));
return {
icon: windowTitleMap[windowTitleMap.length - 1][1],
label: windowTitleMap[windowTitleMap.length - 1][2],
};
}
return { return {
icon: foundMatch[1], icon: windowMatch ? windowMatch[1] : '󰣆',
label: foundMatch[2], label: windowMatch ? windowMatch[2] : `${capitalizeFirstLetter(hyprlandClient.class ?? 'Unknown')}`,
}; };
}; };

View File

@@ -1,5 +1,5 @@
import AstalHyprland from 'gi://AstalHyprland?version=0.1'; import AstalHyprland from 'gi://AstalHyprland?version=0.1';
import { defaultApplicationIcons } from 'src/lib/constants/workspaces'; import { defaultApplicationIconMap } from 'src/lib/constants/appIcons';
import { AppIconOptions, WorkspaceIconMap } from 'src/lib/types/workspace'; import { AppIconOptions, WorkspaceIconMap } from 'src/lib/types/workspace';
import { isValidGjsColor } from 'src/lib/utils'; import { isValidGjsColor } from 'src/lib/utils';
import options from 'src/options'; import options from 'src/options';
@@ -38,22 +38,23 @@ const isWorkspaceActiveOnMonitor = (monitor: number, i: number): boolean => {
*/ */
const getWsIcon = (wsIconMap: WorkspaceIconMap, i: number): string => { const getWsIcon = (wsIconMap: WorkspaceIconMap, i: number): string => {
const iconEntry = wsIconMap[i]; const iconEntry = wsIconMap[i];
const defaultIcon = `${i}`;
if (!iconEntry) { if (!iconEntry) {
return `${i}`; return defaultIcon;
} }
const hasIcon = typeof iconEntry === 'object' && 'icon' in iconEntry && iconEntry.icon !== '';
if (typeof iconEntry === 'string' && iconEntry !== '') { if (typeof iconEntry === 'string' && iconEntry !== '') {
return iconEntry; return iconEntry;
} }
const hasIcon = typeof iconEntry === 'object' && 'icon' in iconEntry && iconEntry.icon !== '';
if (hasIcon) { if (hasIcon) {
return iconEntry.icon; return iconEntry.icon;
} }
return `${i}`; return defaultIcon;
}; };
/** /**
@@ -119,51 +120,48 @@ export const getAppIcon = (
removeDuplicateIcons: boolean, removeDuplicateIcons: boolean,
{ iconMap: userDefinedIconMap, defaultIcon, emptyIcon }: AppIconOptions, { iconMap: userDefinedIconMap, defaultIcon, emptyIcon }: AppIconOptions,
): string => { ): string => {
const iconMap = { ...userDefinedIconMap, ...defaultApplicationIcons }; const workspaceClients = hyprlandService
const clients = hyprlandService
.get_clients() .get_clients()
.filter((client) => client?.workspace?.id === workspaceIndex) .filter((client) => client?.workspace?.id === workspaceIndex)
.map((client) => [client.class, client.title]); .map((client) => [client.class, client.title]);
if (!clients.length) { if (!workspaceClients.length) {
return emptyIcon; return emptyIcon;
} }
let icons = clients const findIconForClient = (clientClass: string, clientTitle: string): string | undefined => {
.map(([clientClass, clientTitle]) => { const appIconMap = { ...defaultApplicationIconMap, ...userDefinedIconMap };
const maybeIcon = Object.entries(iconMap).find(([matcher]) => {
try {
if (matcher.startsWith('class:')) {
const re = matcher.substring(6);
return new RegExp(re).test(clientClass);
}
if (matcher.startsWith('title:')) { const iconEntry = Object.entries(appIconMap).find(([matcher]) => {
const re = matcher.substring(6); if (matcher.startsWith('class:')) {
return new RegExp(matcher.substring(6)).test(clientClass);
return new RegExp(re).test(clientTitle);
}
return new RegExp(matcher, 'i').test(clientClass);
} catch {
return false;
}
});
if (!maybeIcon) {
return undefined;
} }
return maybeIcon.at(1); if (matcher.startsWith('title:')) {
}) return new RegExp(matcher.substring(6)).test(clientTitle);
.filter((x) => x); }
if (removeDuplicateIcons) { return new RegExp(matcher, 'i').test(clientClass);
icons = [...new Set(icons)]; });
}
return iconEntry?.[1];
};
let icons = workspaceClients.reduce((iconAccumulator, [clientClass, clientTitle]) => {
const icon = findIconForClient(clientClass, clientTitle);
if (icon) {
iconAccumulator.push(icon);
}
return iconAccumulator;
}, []);
if (icons.length) { if (icons.length) {
if (removeDuplicateIcons) {
icons = [...new Set(icons)];
}
return icons.join(' '); return icons.join(' ');
} }

View File

@@ -0,0 +1,137 @@
export const defaultWindowTitleMap = [
// Misc
['kitty', '󰄛', 'Kitty Terminal'],
['firefox', '󰈹', 'Firefox'],
['microsoft-edge', '󰇩', 'Edge'],
['discord', '', 'Discord'],
['vesktop', '', 'Vesktop'],
['org.kde.dolphin', '', 'Dolphin'],
['plex', '󰚺', 'Plex'],
['steam', '', 'Steam'],
['spotify', '󰓇', 'Spotify'],
['ristretto', '󰋩', 'Ristretto'],
['obsidian', '󱓧', 'Obsidian'],
['rofi', '', 'Rofi'],
['qBittorrent$', '', 'QBittorrent'],
// Browsers
['google-chrome', '', 'Google Chrome'],
['brave-browser', '󰖟', 'Brave Browser'],
['chromium', '', 'Chromium'],
['opera', '', 'Opera'],
['vivaldi', '󰖟', 'Vivaldi'],
['waterfox', '󰖟', 'Waterfox'],
['thorium', '󰖟', 'Thorium'],
['tor-browser', '', 'Tor Browser'],
['floorp', '󰈹', 'Floorp'],
['zen', '', 'Zen Browser'],
// Terminals
['gnome-terminal', '', 'GNOME Terminal'],
['konsole', '', 'Konsole'],
['alacritty', '', 'Alacritty'],
['wezterm', '', 'Wezterm'],
['foot', '󰽒', 'Foot Terminal'],
['tilix', '', 'Tilix'],
['xterm', '', 'XTerm'],
['urxvt', '', 'URxvt'],
['com.mitchellh.ghostty', '󰊠', 'Ghostty'],
['^st$', '', 'st Terminal'],
// Development Tools
['code', '󰨞', 'Visual Studio Code'],
['vscode', '󰨞', 'VS Code'],
['sublime-text', '', 'Sublime Text'],
['atom', '', 'Atom'],
['android-studio', '󰀴', 'Android Studio'],
['jetbrains-idea', '', 'IntelliJ IDEA'],
['jetbrains-pycharm', '', 'PyCharm'],
['jetbrains-webstorm', '', 'WebStorm'],
['jetbrains-phpstorm', '', 'PhpStorm'],
['eclipse', '', 'Eclipse'],
['netbeans', '', 'NetBeans'],
['docker', '', 'Docker'],
['vim', '', 'Vim'],
['neovim', '', 'Neovim'],
['neovide', '', 'Neovide'],
['emacs', '', 'Emacs'],
// Communication Tools
['slack', '󰒱', 'Slack'],
['telegram-desktop', '', 'Telegram'],
['org.telegram.desktop', '', 'Telegram'],
['whatsapp', '󰖣', 'WhatsApp'],
['teams', '󰊻', 'Microsoft Teams'],
['skype', '󰒯', 'Skype'],
['thunderbird', '', 'Thunderbird'],
// File Managers
['nautilus', '󰝰', 'Files (Nautilus)'],
['thunar', '󰝰', 'Thunar'],
['pcmanfm', '󰝰', 'PCManFM'],
['nemo', '󰝰', 'Nemo'],
['ranger', '󰝰', 'Ranger'],
['doublecmd', '󰝰', 'Double Commander'],
['krusader', '󰝰', 'Krusader'],
// Media Players
['vlc', '󰕼', 'VLC Media Player'],
['mpv', '', 'MPV'],
['rhythmbox', '󰓃', 'Rhythmbox'],
// Graphics Tools
['gimp', '', 'GIMP'],
['inkscape', '', 'Inkscape'],
['krita', '', 'Krita'],
['blender', '󰂫', 'Blender'],
// Video Editing
['kdenlive', '', 'Kdenlive'],
// Games and Gaming Platforms
['lutris', '󰺵', 'Lutris'],
['heroic', '󰺵', 'Heroic Games Launcher'],
['minecraft', '󰍳', 'Minecraft'],
['csgo', '󰺵', 'CS:GO'],
['dota2', '󰺵', 'Dota 2'],
// Office and Productivity
['evernote', '', 'Evernote'],
['sioyek', '', 'Sioyek'],
// Cloud Services and Sync
['dropbox', '󰇣', 'Dropbox'],
];
const overrides = {
kitty: '',
};
/**
* Generates a mapping of application names to their corresponding icons.
* Uses the defaultWindowTitleMap to create the base mapping and applies any overrides.
*
* @returns An object where keys are application names and values are icon names.
* If an application name exists in the overrides, that value is used instead of the default.
*
* @example
* // Given:
* defaultWindowTitleMap = [['kitty', '󰄛', 'Kitty Terminal'], ['firefox', '󰈹', 'Firefox']]
* overrides = { 'kitty': '' }
*
* // Returns:
* { 'kitty': '', 'firefox': '󰈹' }
*/
export const defaultApplicationIconMap = defaultWindowTitleMap.reduce(
(iconMapAccumulator: Record<string, string>, windowTitles) => {
const appName: string = windowTitles[0];
const appIcon: string = windowTitles[1];
if (!(appName in iconMapAccumulator)) {
iconMapAccumulator[appName] = appIcon;
}
return iconMapAccumulator;
},
overrides,
);

View File

@@ -1,29 +0,0 @@
export const defaultApplicationIcons = {
'[dD]iscord': '󰙯',
'^thunderbird': '',
'class:wezterm$': '',
'draw.io': '󰇟',
'firefox-developer-edition': '',
'google-chrome': '',
'title:YouTube ': '',
Spotify: '󰓇',
chromium: '',
code: '󰨞',
dbeaver: '',
edge: '󰇩',
evince: '',
firefox: '',
foot: '',
keepassxc: '',
keymapp: '',
kitty: '',
obsidian: '󰠮',
password$: '',
qBittorrent$: '',
rofi: '',
slack: '',
spotube: '󰓇',
steam: '',
telegram: '',
vlc: '󰕼',
};