quickshell-toki-night/util/Icons.qml
Kiana Sheibani 31254372c8
fix: use app icon precedence for individual app categories
Not only is precedence used to choose which window is used to represent
the workspace, it is also now used to choose which category of each
window is used to represent the app itself.
2026-02-08 07:07:23 -05:00

282 lines
8.6 KiB
QML

pragma Singleton
import qs.config
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import Quickshell.Services.Notifications
Singleton {
id: root
readonly property var weatherIcons: ({
"113": "clear_day",
"116": "partly_cloudy_day",
"119": "cloud",
"122": "cloud",
"143": "foggy",
"176": "rainy_light",
"179": "rainy_snow",
"182": "rainy_snow",
"185": "rainy_snow",
"200": "electric_bolt",
"227": "snowing",
"230": "snowing_heavy",
"248": "foggy",
"260": "foggy",
"263": "rainy_light",
"266": "rainy_light",
"281": "rainy_snow",
"284": "rainy_snow",
"293": "rainy_light",
"296": "rainy_light",
"299": "rainy_heavy",
"302": "rainy_heavy",
"305": "rainy_heavy",
"308": "rainy_heavy",
"311": "rainy_snow",
"314": "rainy_snow",
"317": "rainy_snow",
"320": "snowing",
"323": "rainy_snow",
"326": "rainy_snow",
"329": "snowing_heavy",
"332": "snowing_heavy",
"335": "snowing",
"338": "snowing_heavy",
"350": "rainy_snow",
"353": "rainy_light",
"356": "rainy_heavy",
"359": "rainy_heavy",
"362": "rainy_snow",
"365": "rainy_snow",
"368": "snowing",
"371": "snowing_heavy",
"374": "rainy_snow",
"377": "rainy_snow",
"386": "electric_bolt",
"389": "electric_bolt",
"392": "electric_bolt",
"395": "snowing_heavy"
})
readonly property var weatherIconColors: ({
"clear_day": Config.colors.yellow,
"partly_cloudy_day": Config.colors.primary,
"cloud": Config.colors.tertiary,
"foggy": Config.colors.tertiary,
"electric_bolt": Config.colors.yellow,
"rainy_light": Config.colors.blue,
"rainy_heavy": Config.colors.blue,
"rainy_snow": Config.colors.blue,
"snowing": Config.colors.cyan,
"snowing_heavy": Config.colors.cyan,
"air": Config.colors.primary
})
readonly property var desktopEntrySubs: ({
"gimp-3.0": ["gimp"],
"discord": ["discord", "discord-canary"],
// Libreoffice
"libreoffice-startcenter": ["libreoffice-startcenter", "startcenter"],
"libreoffice-writer": ["libreoffice-writer", "writer"],
"libreoffice-draw": ["libreoffice-draw", "draw"],
"libreoffice-impress": ["libreoffice-impress", "impress"],
"libreoffice-math": ["libreoffice-math", "math"],
"libreoffice-base": ["libreoffice-base", "base"],
"libreoffice-calc": ["libreoffice-calc", "calc"]
})
readonly property var categoryIcons: ({
WebBrowser: "language",
Printing: "print",
Security: "security",
Network: "business_messages",
Archiving: "archive",
Compression: "archive",
Development: "code",
IDE: "code",
TextEditor: "edit_note",
Audio: "music_note",
Music: "music_note",
Player: "music_note",
Recorder: "mic",
Game: "sports_esports",
FileTools: "files",
FileManager: "files",
Filesystem: "files",
FileTransfer: "files",
Settings: "settings",
DesktopSettings: "settings",
HardwareSettings: "settings",
TerminalEmulator: "terminal",
ConsoleOnly: "terminal",
Utility: "build",
Monitor: "monitor_heart",
Midi: "graphic_eq",
Mixer: "graphic_eq",
AudioVideoEditing: "video_settings",
AudioVideo: "music_video",
Video: "videocam",
Building: "construction",
Graphics: "photo_library",
"2DGraphics": "photo_library",
RasterGraphics: "photo_library",
TV: "tv",
System: "host",
Office: "content_paste"
})
// App icon precedence
// Used to preferentially pick app icon for display
readonly property var appIconPrec: ({
sports_esports: 10,
code: 9,
music_note: 9,
content_paste: 9,
graphic_eq: 8,
tv: 8,
edit_note: 6,
language: 5,
business_messages: 5,
files: 4,
mic: 4,
construction: 4,
terminal: 3,
monitor_heart: 3,
security: 3,
photo_library: 3,
archive: 2,
settings: 2,
build: 1,
host: 0
})
function getDesktopEntry(name: string): DesktopEntry {
name = name.toLowerCase().replace(/ /g, "-");
let names = [];
if (desktopEntrySubs.hasOwnProperty(name))
names = desktopEntrySubs[name];
else
names = [name];
return DesktopEntries.applications.values.find(a => names.includes(a.id.toLowerCase())) ?? null;
}
function getAppIcon(name: string): string {
return Quickshell.iconPath(getDesktopEntry(name)?.icon, "icon-missing");
}
function getAppCategoryIcon(name: string, fallback: string): string {
const categories = getDesktopEntry(name)?.categories;
let values = [];
if (categories)
for (const [key, value] of Object.entries(categoryIcons))
if (categories.includes(key) && value in appIconPrec)
values.push(value);
if (values.length !== 0) {
return values.reduce((a, b) => appIconPrec[a] > appIconPrec[b] ? a : b);
} else {
return fallback;
}
}
function getWorkspaceIcon(workspace: HyprlandWorkspace): string {
if (!workspace || workspace.toplevels.values.length === 0) return "add";
return [...workspace.toplevels.values]
.map(tl => getAppCategoryIcon(tl.lastIpcObject.class, "terminal"))
.reduce((a, b) => appIconPrec[a] > appIconPrec[b] ? a : b);
}
function getNetworkIcon(strength: int): string {
if (strength >= 80)
return "signal_wifi_4_bar";
if (strength >= 60)
return "network_wifi_3_bar";
if (strength >= 40)
return "network_wifi_2_bar";
if (strength >= 20)
return "network_wifi_1_bar";
return "signal_wifi_0_bar";
}
function getBluetoothIcon(icon: string): string {
if (icon.includes("headset") || icon.includes("headphones"))
return "headphones";
if (icon.includes("audio"))
return "speaker";
if (icon.includes("phone"))
return "smartphone";
if (icon.includes("mouse"))
return "mouse";
return "bluetooth";
}
function getWeatherIcon(code: string): string {
if (weatherIcons.hasOwnProperty(code))
return weatherIcons[code];
return "air";
}
function getHumidityIcon(humidity: real): string {
if (humidity >= 0.66)
return "humidity_high";
if (humidity >= 0.33)
return "humidity_mid";
return "humidity_low";
}
function getVolumeIcon(volume: real, isMuted: bool): string {
if (isMuted)
return "no_sound";
if (volume >= 0.5)
return "volume_up";
if (volume > 0)
return "volume_down";
return "volume_mute";
}
function getMicVolumeIcon(volume: real, isMuted: bool): string {
if (!isMuted && volume > 0)
return "mic";
return "mic_off";
}
function getBrightnessIcon(brightness: real): string {
return `brightness_${(Math.round(brightness * 6) + 1)}`;
}
function getNotifIcon(summary: string, urgency: int): string {
summary = summary.toLowerCase();
if (summary.includes("reboot"))
return "restart_alt";
if (summary.includes("record"))
return "screen_record";
if (summary.includes("battery"))
return "power";
if (summary.includes("screenshot"))
return "screenshot_monitor";
if (summary.includes("welcome"))
return "waving_hand";
if (summary.includes("time") || summary.includes("a break"))
return "schedule";
if (summary.includes("installed"))
return "download";
if (summary.includes("update"))
return "update";
if (summary.includes("unable to"))
return "deployed_code_alert";
if (summary.includes("profile"))
return "person";
if (summary.includes("image"))
return "image";
if (summary.includes("file"))
return "folder_copy";
if (urgency === NotificationUrgency.Critical)
return "release_alert";
return "chat";
}
}