diff --git a/scripts/screen_record.sh b/scripts/screen_record.sh index 869bfc5..f203689 100755 --- a/scripts/screen_record.sh +++ b/scripts/screen_record.sh @@ -30,8 +30,8 @@ startRecording() { w=$(echo $monitor_info | jq -r '.width') h=$(echo $monitor_info | jq -r '.height') scale=$(echo $monitor_info | jq -r '.scale') - scaled_width=$(echo "${w} / ${scale}" | bc) - scaled_height=$(echo "${h} / ${scale}" | bc) + scaled_width=$(awk "BEGIN {print $w / $scale}") + scaled_height=$(awk "BEGIN {print $h / $scale}") x=$(echo $monitor_info | jq -r '.x') y=$(echo $monitor_info | jq -r '.y') wf-recorder $WF_RECORDER_OPTS --geometry "${x},${y} ${scaled_width}x${scaled_height}" --file "$outputPath" & diff --git a/src/components/bar/modules/cpu/index.tsx b/src/components/bar/modules/cpu/index.tsx index 6208064..f581bd9 100644 --- a/src/components/bar/modules/cpu/index.tsx +++ b/src/components/bar/modules/cpu/index.tsx @@ -17,12 +17,8 @@ const cpuPoller = new FunctionPoller(cpuUsage, [bind(round)], bind(p cpuPoller.initialize('cpu'); export const Cpu = (): BarBoxChild => { - const renderLabel = (cpuUsg: number, rnd: boolean): string => { - return rnd ? `${Math.round(cpuUsg)}%` : `${cpuUsg.toFixed(2)}%`; - }; - - const labelBinding = Variable.derive([bind(cpuUsage), bind(round)], (cpuUsg, rnd) => { - return renderLabel(cpuUsg, rnd); + const labelBinding = Variable.derive([bind(cpuUsage), bind(round)], (cpuUsg: number, round: boolean) => { + return round ? `${Math.round(cpuUsg)}%` : `${cpuUsg.toFixed(2)}%`; }); const cpuModule = Module({ diff --git a/src/components/bar/modules/window_title/helpers/title.ts b/src/components/bar/modules/window_title/helpers/title.ts index c81e173..04fb3e6 100644 --- a/src/components/bar/modules/window_title/helpers/title.ts +++ b/src/components/bar/modules/window_title/helpers/title.ts @@ -1,12 +1,16 @@ import options from 'src/options'; import { capitalizeFirstLetter } from 'src/lib/utils'; +import { defaultWindowTitleMap } from 'src/lib/constants/appIcons'; import AstalHyprland from 'gi://AstalHyprland?version=0.1'; import { bind, Variable } from 'astal'; +const { title_map: userDefinedTitles } = options.bar.windowtitle; + const hyprlandService = AstalHyprland.get_default(); -export const clientTitle = Variable(''); let clientBinding: Variable | undefined; +export const clientTitle = Variable(''); + function trackClientUpdates(client: AstalHyprland.Client): void { clientBinding?.drop(); 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. * 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. */ -export const getWindowMatch = (client: AstalHyprland.Client): Record => { - const windowTitleMap = [ - // 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) { +export const getWindowMatch = (hyprlandClient: AstalHyprland.Client): Record => { + if (!hyprlandClient?.class) { return { icon: '󰇄', label: 'Desktop', }; } - const foundMatch = windowTitleMap.find((wt) => RegExp(wt[0]).test(client?.class.toLowerCase())); - - if (!foundMatch || foundMatch.length !== 3) { - return { - icon: windowTitleMap[windowTitleMap.length - 1][1], - label: windowTitleMap[windowTitleMap.length - 1][2], - }; - } + const clientClass = hyprlandClient.class.toLowerCase(); + const potentialWindowTitles = [...userDefinedTitles.get(), ...defaultWindowTitleMap]; + const windowMatch = potentialWindowTitles.find((title) => RegExp(title[0]).test(clientClass)); return { - icon: foundMatch[1], - label: foundMatch[2], + icon: windowMatch ? windowMatch[1] : '󰣆', + label: windowMatch ? windowMatch[2] : `${capitalizeFirstLetter(hyprlandClient.class ?? 'Unknown')}`, }; }; diff --git a/src/components/bar/modules/workspaces/helpers/utils.ts b/src/components/bar/modules/workspaces/helpers/utils.ts index 866b8c6..122a6d7 100644 --- a/src/components/bar/modules/workspaces/helpers/utils.ts +++ b/src/components/bar/modules/workspaces/helpers/utils.ts @@ -1,5 +1,5 @@ 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 { isValidGjsColor } from 'src/lib/utils'; import options from 'src/options'; @@ -38,22 +38,23 @@ const isWorkspaceActiveOnMonitor = (monitor: number, i: number): boolean => { */ const getWsIcon = (wsIconMap: WorkspaceIconMap, i: number): string => { const iconEntry = wsIconMap[i]; + const defaultIcon = `${i}`; if (!iconEntry) { - return `${i}`; + return defaultIcon; } - const hasIcon = typeof iconEntry === 'object' && 'icon' in iconEntry && iconEntry.icon !== ''; - if (typeof iconEntry === 'string' && iconEntry !== '') { return iconEntry; } + const hasIcon = typeof iconEntry === 'object' && 'icon' in iconEntry && iconEntry.icon !== ''; + if (hasIcon) { return iconEntry.icon; } - return `${i}`; + return defaultIcon; }; /** @@ -119,51 +120,48 @@ export const getAppIcon = ( removeDuplicateIcons: boolean, { iconMap: userDefinedIconMap, defaultIcon, emptyIcon }: AppIconOptions, ): string => { - const iconMap = { ...userDefinedIconMap, ...defaultApplicationIcons }; - - const clients = hyprlandService + const workspaceClients = hyprlandService .get_clients() .filter((client) => client?.workspace?.id === workspaceIndex) .map((client) => [client.class, client.title]); - if (!clients.length) { + if (!workspaceClients.length) { return emptyIcon; } - let icons = clients - .map(([clientClass, clientTitle]) => { - const maybeIcon = Object.entries(iconMap).find(([matcher]) => { - try { - if (matcher.startsWith('class:')) { - const re = matcher.substring(6); - return new RegExp(re).test(clientClass); - } + const findIconForClient = (clientClass: string, clientTitle: string): string | undefined => { + const appIconMap = { ...defaultApplicationIconMap, ...userDefinedIconMap }; - if (matcher.startsWith('title:')) { - const re = matcher.substring(6); - - return new RegExp(re).test(clientTitle); - } - - return new RegExp(matcher, 'i').test(clientClass); - } catch { - return false; - } - }); - - if (!maybeIcon) { - return undefined; + const iconEntry = Object.entries(appIconMap).find(([matcher]) => { + if (matcher.startsWith('class:')) { + return new RegExp(matcher.substring(6)).test(clientClass); } - return maybeIcon.at(1); - }) - .filter((x) => x); + if (matcher.startsWith('title:')) { + return new RegExp(matcher.substring(6)).test(clientTitle); + } - if (removeDuplicateIcons) { - icons = [...new Set(icons)]; - } + return new RegExp(matcher, 'i').test(clientClass); + }); + + 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 (removeDuplicateIcons) { + icons = [...new Set(icons)]; + } + return icons.join(' '); } diff --git a/src/lib/constants/appIcons.ts b/src/lib/constants/appIcons.ts new file mode 100644 index 0000000..f7d0589 --- /dev/null +++ b/src/lib/constants/appIcons.ts @@ -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, windowTitles) => { + const appName: string = windowTitles[0]; + const appIcon: string = windowTitles[1]; + + if (!(appName in iconMapAccumulator)) { + iconMapAccumulator[appName] = appIcon; + } + + return iconMapAccumulator; + }, + overrides, +); diff --git a/src/lib/constants/workspaces.ts b/src/lib/constants/workspaces.ts deleted file mode 100644 index ffa94b1..0000000 --- a/src/lib/constants/workspaces.ts +++ /dev/null @@ -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: '󰕼', -};